Connect to EdgeScale

Connect Linux devices

Build EdgeScale Client

Currently, EdgeScale only supports building EdgeScale client with flex-builder on LSDK based images. Please refer to 3. Deploy EdgeScale agents on the device for building instructions.

OTA: Auto Deployment

Introduction

A solution image can be deployed to one or more devices from the EdgeScale web page. This guide will introduce to you how to deploy a solution to the device.

  1. Click Software > Solution on the left navigation to go to solution page.
_images/ota1.png
  1. Click Deploy button of the selected solution (e.g. edgescalecli001-test) to go to the ‘Filter’ page.
_images/ota2.png
  1. Fill in the device filter conditions and click Query Devices button to go to the ‘Select devices’ page.
_images/ota3.png
  1. Select one device and click Next Step: Preview button to go to the ‘Preview’ Page.
_images/ota4.png
  1. Click Next Step: Deploy button to go to the ‘Status Monitor’ Page.

Non-OTA: Manual Deployment

In this mode, the image built by flex-builder is manually installed on SD/USB/SATA storage. After this, the docker based application can be deployed from the cloud. For more details about how to manually build and deploy the LSDK distro with EdgeScale client to storage, please refer to the LSDK doc: https://lsdk.github.io/.

  1. Log in to EdgeScale, create device, bind device model at the dashboard, and download the identification image like:bootstrap-enroll-<device-name>.sh

2. Boot up the board using the LSDK tiny itb image, format and create new partition on storage, then deploy the LSDK boot partition and rootfs into the storage. e.g. For ls1046a: In u-boot:

=> setenv bootargs root=/dev/ram0 rw console=ttyS0,115200 earlycon=uart8250,mmio,0x21c0500 ramdisk_size=0x10000000
=> tftp a0000000 lsdk_linux_arm64_tiny.itb
=> bootm 0xa0000000#ls1046ardb

In Linux:

$ flex-installer -i pf -d sd
$ cd /run/media/mmcblk0p3 # then download the boot partition and rootfs you generated using LSDK into this partition.
$ flex-installer -i install -b <bootpartition> -r <rootfs> -m ls1046ardb -d sd
$ reboot

3. Boot up the board form storage. e.g. For ls1046ardb In u-boot:

=> cpld reset sd
  1. After booting up board from storage, copy and run the identification image and start the enrollment process.
$ bash bootstrap-enroll-<device-name>.sh /dev/mmcblk0
$ startup.sh

Connect IoT endpoints

connect directly to cloud

For IP capable IoT device, EdgeScale provides a thin agent installed on the IoT devices to connect cloud directly.

This guide describes how to use the thin agent to connect an IMXRT1050 EVKB board with EdgeScale. After finishing these steps, we can monitor the board in the EdgeScale and perform OTA.

A software package can be downloaded here. It contains the source code of the thin agent and the boot loader.

1. Create a Device in EdgeScale

  • Login the web site of the EdgeScale with your account: https://console.edgescale.org/login
  • Go to the page, Device -> My Devices, and click the Create button to create a new device. For i.MX RT boards, select the model nxp--imxrt--generic--iot. For more details, refer to the Quick Start Guide
  • After clicking the Submit button, download a script for the new device. Its name looks like “bootstrap-enroll-26a141dcabff58d89230c83faccad03e.iot.generic.imxrt.nxp.sh”. From the file name, you can get
    client ID: 26a141dcabff58d89230c83faccad03e.iot.generic.imxrt.nxp
  • Use a tool named estclient in the directory /thin-agent/tools/ to get a device certificate from the EdgeScale. Put the downloaded file in the same directory of the tool. Execute the following command on the Ubuntu-based PC:
    ./estclient bootstrap-enroll-26a141dcabff58d89230c83faccad03e.iot.generic.imxrt.nxp.sh
    
    If everything goes well, you will get a device certificate file, cert.pem, and a device private key, priv.key. In the following steps, put them in the souce code of the EdgeScale thin agent.

2. Make the EdgeScale Thin Agent Application

On the Ubuntu-based PC, Install the MCUXpresso IDE. Get the installation file at: https://mcuxpresso.nxp.com/en/welcome

Add the device certificate and private key in the code and compile it:

  • Put the content of the device certificate, cert.pem, in the config file, “./edgescale/eds_config.h”. Replace the content of the macro, “EDS_DEVICE_CERT_PEM”.
  • Put the content of the private key, priv.key, in the config file. Replace the content of the macro, “EDS_DEVICE_PRIVATE_PEM”.
_images/edge_image3.png
  • Change the macro, “CLIENT_ID”, to the value got from the downloaded file:

    "26a141dcabff58d89230c83faccad03e.iot.generic.imxrt.nxp"
    
  • Connect the board to the PC with a USB cable (J28 port). Make sure J-Link firmware has been installed on the board. See the section 5. Install Jlink for more details.

  • Make sure python 3 has installed on the PC. Then install python modules needed by the image tool.

    cd ./thin-agent/tools/
    pip install -r requirements.txt
    cd ..
    
  • Compile the project:

    mkdir -p ./build/
    cd ./build/
    cmake ..
    make
    

    Will generate some binary files in the directory build. The binary file with the suffix “.app.bin” is the image that can be wrote into the flash.

  • Write the binary file into the on-board flash:

    cd ./build/
    make flash
    
  • Make sure the board has installed the boot loader. See 4. Boot-loader.

  • Reset the board. Once the EdgeScale agent is started, you can see its status is changed into “ONLINE” in the page of the device list on the console of the EdgeScale cloud.

_images/edge_image4.png
  • Click its name to see more details:
_images/edge_image5.png

3. OTA

To test the OTA feature, follow the below steps:

  • To show the change, change the version macro, “AGENT_VERSION”, to a new version in the file “CMakeLists.txt”.

  • Rebuild the project and create a new OTA image as the previous section described.

  • Create a new OTA entry. On the EdgeScale web page, enter the tab, Edge Software Store. Then enter the page, Solution Store, and click the Create button to create an OTA entry in the EdgeScale store.

    Model name:    nxp-imxrt-generic-iot
    Solution name: imxrt-ota
    Version:       1903
    

    Select Specify Firmware Location and specify the OTA image URL, or select Upload Firmware image and upload the OTA image.

  • On the web page of Solution Store, click the deploy icon of the imxrt-ota solution in the action column to create a OTA task to update the application image on the device. Click the button, Query Devices, and select the device you just created. Click Next Step, then click the Deploy button.

  • From the serial output of the board, OTA process will be shown. After the process, the board will reset and boot the new application image.

4. Boot-loader

See the document, README, in the boot loader for more details.

6. Flash memory layout

The flash memory will be divided into several partitions. The partition size can be customized. A reference flash layout is:

_images/edge_image6.png
  • The bootloader is in the most front of the flash memory, the “Bootloader” partition. It can work in the XIP mode.
  • Partition 1 and Partition 2 will be used to store application images. The Partition 1 is the main partition from which the bootloader always starts the application. The Partition 2 is a backup partition used for OTA.
  • The SWAP partition is required when swapping the content of two application partitions.
  • The “Custom Partition” can be used to store user data.

In the process of OTA, when booting, the bootloader will check Partition 2. If there is a newer application image in it, it will move the new image into Partition 1, and back up the old image in Partition 2. Then, jump to Partition 1 to boot the new image.

connect via gateway

For non-IP IoT device, EdgeScale supports to connect cloud via gateway software.

1. Gateway library introduction

Gateway library implements the function that help sub-device to communicate with edgescale cloud.

User can build their app that include gateway library file(libgateway.a), and set their sub-device attributes in app.

User can register their sub-device’s name id and attributes to gateway by invoking the function in the library, gateway will get sub-device’s information, and register sub-device to EdgeScale via json message.

When EdgeScale received registration, it will show the sub-device, so user can set or get sub-device’s attributes on EdgeScale user interface, and gateway will receive json message from EdgeScale, and forward the message to the related sub-device.

When gateway get the attribute value from sub-device, it will send the value to EdgeScale via json message.

User can also update their sub-device’s firmware on EdgeScale by gateway.

2. Sub-device main data structure

  1. Basic data type definition, in C programming language format
#define ES_MAX_VAL_NUM (16)
#define ES_MAX_VAL_LEN (64)
#define STR_VAL ("string")
#define INT_VAL ("int")
#define FLOAT_VAL ("float")
#define BOOL_VAL ("bool")

typedef char* uintptr;
typedef bool es_bool;
typedef int es_int;
typedef long es_long;
typedef double es_float;
typedef char es_char;
typedef char* es_str;
typedef unsigned int uint32;
  1. Sub-device data structure definition
typedef struct _Es_Dev
{
        es_char name[ES_MAX_VAL_LEN];
        es_char model[ES_MAX_VAL_LEN];
        es_char devid[ES_MAX_VAL_LEN];
        es_char gwid[ES_MAX_VAL_LEN];

        int link_type_num;
        Es_Link_Type *link_type;

        int in_attr_num;
        Es_Dev_Attr *in_attr;
        int out_attr_num;
        Es_Dev_Attr *out_attr;

        int ota_fw_num;
        Es_Dev_Ota *ota_fw;
        Es_Dev_Func *dev_func;

        void *priv;
}Es_Dev;
  1. Sub-device attribute structure definition
typedef struct _Es_Dev_Attr
{
        es_char name[ES_MAX_VAL_LEN];
        es_char type[ES_MAX_VAL_LEN];
        es_char unit[ES_MAX_VAL_LEN];
        es_char note[ES_MAX_VAL_LEN];
        int value_num;
        es_bool bv[ES_MAX_VAL_NUM];
        es_int iv[ES_MAX_VAL_NUM];
        es_float fv[ES_MAX_VAL_NUM];
        es_str sv[ES_MAX_VAL_NUM];
        void *priv;

        /*
         * The following will be used by SDK
         * */
        Es_Val *pval;
}Es_Dev_Attr;

Note

The user should NOT use the data item pval, it will be used by SDK in the library.

  1. Sub-device connection type definition
typedef struct _Es_Link_Type
{
        es_str name;
        es_str version;
        es_str note;
}Es_Link_Type;
  1. Sub-device data structure definition for OTA
typedef struct _Es_Dev_Ota
{
        es_str name;
        es_str version;
        es_int space_size;
        es_str space_unit;
        es_char file_type[ES_MAX_VAL_LEN];
        es_char check_type[ES_MAX_VAL_LEN];
        Es_Img_Type img_type;
        void *priv;

        /*
         * The following will be used by SDK
         * */
        es_int file_type_num;
        es_str file_types[ES_MAX_VAL_NUM];
        es_int check_type_num;
        es_str check_types[ES_MAX_VAL_NUM];
}Es_Dev_Ota;

Note

The user should NOT use the data item for SDK, like file_type_num, file_types etc …

  1. Sub-device set get and OTA function definition
typedef int (*es_set_attr_value)(Es_Dev *pdev, Es_Dev_Attr *pattr, Es_Val *pval);
typedef int (*es_get_attr_value)(Es_Dev *pdev, Es_Dev_Attr *pattr, Es_Val *pval);
typedef int (*es_update_firmware)(Es_Dev *pdev, es_str fw_name, Es_Ota_Buf *pbuf);

typedef struct _Es_Dev_Func
{
        es_set_attr_value set_attr_value;
        es_get_attr_value get_attr_value;
        es_update_firmware update_firmware;
}Es_Dev_Func;

3. Register sub-device to gateway

Take the following code as example, assume the code is in the file named “es_dev_example.c”.

  1. Define input attributes

The input attribute is that we can set its value by gateway.

In the following code, we define three attributes, they are:

name type unit note
power bool   Denote that if LED is powered
brightness float mcd LED brightness value
color string   LED color type

The code is in the following:

static Es_Dev_Attr sin_attr[] =
{
        {"power", BOOL_VAL, "", "Denote that if LED is powered", 2, {true, false}, {}, {}, {}, &sled0_power},
        {"brightness", FLOAT_VAL, "mcd", "LED brightness value", 3, {}, {}, {50.0, 100.6, 500.6}, {}, &sled0_bright},
        {"color", STR_VAL, "", "LED color type", 3, {}, {}, {}, {"red", "green", "blue"}, sled0_color},
};
  1. Define output attributes

The output attribute is that we can get its value by gateway.

In the following code, we define four attributes, they are:

name type unit note
power bool   Denote that if LED is powered
brightness float mcd LED brightness value
color string   LED color type
color_temp int K LED color temperature

The code is in the following:

static Es_Dev_Attr sout_attr[] =
{
        {"power", BOOL_VAL, "", "Denote that if LED is powered", 2, {true, false}, {}, {}, {}, &sled0_power},
        {"brightness", FLOAT_VAL, "mcd", "LED brightness value", 3, {}, {}, {50.0, 100.6, 500.6}, {}, &sled0_bright},
        {"color", STR_VAL, "", "LED color type", 3, {}, {}, {}, {"red", "green", "blue"}, sled0_color},
        {"color_temp", INT_VAL, "K", "LED color temperature", 3, {}, {2000, 2500, 3000}, {}, {}, &sled0_color_temp},
};
  1. Define the firmwares that can be updated OTA

In the following code, we define the firmware for OTA:

name version space size file type check type
system_image 1.0 2000KB tgz, tar, zip crc16, sha256, none

The code is in the following:

static Es_Dev_Ota sota_fw[] =
{
        {"system_image", "1.0", 2000, "KB", "tgz,tar,zip", "crc16,sha256,none"},
};
  1. Define connection type that sub-device communicates with gateway

In the following code, we define the connection type is wifi:

name version note
wifi 1.0 connection to gateway via wifi

The code is in the following:

static Es_Link_Type slink_type[] =
{
        {"wifi", "1.0", "connection to gateway via wifi"},
};
  1. Define set get and OTA callback function

The example code of set function is in the following:

static int set_attr_value(Es_Dev *pdev, Es_Dev_Attr *pattr, Es_Val *pval)
{
        printf("%s dev:%s attr:%s ", __FUNCTION__, pdev->name, pattr->name);

        switch (pval->type) {
                case ES_VAL_BOOL:
                        printf("es_bool val:%d\n", pval->bv);
                        break;

                case ES_VAL_INT:
                        printf("es_int val:%d\n", pval->iv);
                        break;

                case ES_VAL_FLOAT:
                        printf("es_float val:%.2f\n", pval->fv);
                        break;

                case ES_VAL_STR:
                        printf("es_str val:%s\n", pval->sv);
                        break;

                default:
                        break;
        }

        /*
         * call driver function, for example ble zigbee .. etc.
         * */

        es_strncpy(pval->status, "ok", ES_MAX_VAL_LEN);
        es_strncpy(pval->message, "status is OK", ES_MAX_VAL_LEN);

        return 0;
}

The example code of get function is in the following:

static int get_attr_value(Es_Dev *pdev, Es_Dev_Attr *pattr, Es_Val *pval)
{
        printf("%s dev:%s attr:%s ", __FUNCTION__, pdev->name, pattr->name);

        /*
         * call driver function, for example ble zigbee .. etc.
         * */

        switch (pval->type) {
                case ES_VAL_BOOL:
                        pval->bv = 10;
                        printf("es_bool val:%d\n", pval->bv);
                        break;

                case ES_VAL_INT:
                        pval->iv = 20;
                        printf("es_int val:%d\n", pval->iv);
                        break;

                case ES_VAL_FLOAT:
                        pval->fv = 30.0;
                        printf("es_float val:%.2f\n", pval->fv);
                        break;

                case ES_VAL_STR:
                        es_strncpy(pval->sbuf, "example string", ES_MAX_VAL_LEN);
                        pval->sv = pval->sbuf;
                        printf("es_str val:%s\n", pval->sbuf);
                        break;

                default:
                        break;
        }

        es_strncpy(pval->status, "ok", ES_MAX_VAL_LEN);
        es_strncpy(pval->message, "status is OK", ES_MAX_VAL_LEN);

        return 0;
}

The example code of OTA function is in the following:

static int update_firmware(Es_Dev *pdev, es_str fw_name, Es_Ota_Buf *pbuf)
{
        printf("\n%s @ %s dev:%s firmware:%s \n", __FILE__, __FUNCTION__, pdev->name, fw_name);

        if (pbuf->img_type == Es_Img_File) {
                printf("OK File:%s old_File:%s\n", pbuf->ptr, pbuf->old_ptr);

        } else if (pbuf->img_type == Es_Img_Buf) {
                printf("OK buf_len:%ld old_buf_len:%ld\n", pbuf->len, pbuf->old_len);

        } else if (pbuf->img_type == Es_Img_Url) {
                printf("OK URL:%s\n", pbuf->ptr);

        }

        /* update firmware to sub-device */

        if (pbuf->ota_callback)
          pbuf->ota_callback(pdev, fw_name, "ok", "ota is finished!");

        return 0;
}

Note

When sub-device has updated firmware, it should call ota_callback function to notify gateway.

The ota_callback function prototype is:

typedef int (*es_ota_callback)(Es_Dev *pdev, es_str fw_name, es_str status, es_str message);

The example code of sub-device function is in the following:

static Es_Dev_Func sdev_func =
{
        set_attr_value,
        get_attr_value,
        update_firmware,
};
  1. Define sub-device

In the following code, we define the sub-device that named “led0”:

name model device id gateway id connection input attr output attr ota-firmware function
“led0” “led” “led0_id” ES_GATEWAY_ID slink_type sin_attr sout_attr sota_fw sdev_func

The code is in the following:

static Es_Dev ssub_dev[] =
{
        ES_DEV_ENTRY("led0", "led", "led0_id", "gateway0_id", slink_type, sin_attr, sout_attr, sota_fw, &sdev_func, NULL),
};

4. Register entry exit and timer callback function

Take the following code as example.

  1. Callback function structure

In the following code, we define three callback functions:

typedef int (*gw_func_entry)(void *p);
typedef int (*gw_func_exit)(void *p);
typedef int (*gw_func_timer)(void *p, uint32 sec);

typedef struct _Es_Gw_Func
{
        char *name;
        gw_func_entry func_entry;
        gw_func_exit func_exit;
        gw_func_timer func_timer;
        void *priv;
}Es_Gw_Func;

the function description:

  1. “gw_func_entry” will be invoked when app calls “es_start_gateway” to start gateway SDK.
  2. “gw_func_exit” will be invoked when app calls “es_stop_gateway” to stop gateway SDK.
  3. “gw_func_timer” will be invoked periodically by gateway SDK, the interval is one second.
  1. Define callback function

In the following code, we define three callback functions as example:

static int example_entry(void *p)
{
        printf("%s\n", __FUNCTION__);
        es_register_dev(&ssub_dev[0]);
        return 0;
}

static int example_exit(void *p)
{
        printf("%s\n", __FUNCTION__);
        return 0;
}

static int example_timer(void *p, uint32 sec)
{
        printf("%s sec:%d\n", __FUNCTION__, sec);
        return 0;
}
  1. Register callback function to gateway

In the following code, we will invoke register_gw_func to register callback function to gateway:

static int sgw_flag = 0;
static Es_Gw_Func sgw_func =
{
        "example",
        example_entry,
        example_exit,
        example_timer,
        &sgw_flag,
};

int demo_example_main(void)
{
        register_gw_func(&sgw_func);

        return 0;
}
  1. App main function example

    The app main function is like this:

int main(int argc, char **argv)
{
        char *penv = NULL;
        bool bwait_flag = true;

        es_set_loglevel(1);

        penv = getenv("ES_ACCESSKEY");
        if (penv)
          es_set_accesskey(penv);

        penv = getenv("ES_DEVICEID");
        if (penv)
          es_set_gatewayid(penv);

        /* true: block mode -- false: normal mode */
        es_init_gateway(bwait_flag);

        /* register sub-device defined in es_dev_example.c */
        demo_example_main();

        /* start function will be blocked when bwait_flag is true */
        es_start_gateway();

        /* should call stop function when exit main function  */
        es_stop_gateway();

        return 0;
}

5. How to build app that includes gateway library

  1. About source code tree

Assume that main.c is user’s main file and es_dev_example.c is file for sub-device.

Makefile is script file that used by make.

All files is in the directory that named “example_dir”.

  1. How to get gateway library?

The gateway library is named “libgateway.a”, we can get it like this:

cd example_dir
wget https://s3-us-west-2.amazonaws.com/edgescale.images/1909/library/libgateway.a
  1. How to build and run app?
cd example_dir
make clean
make
./example

The Makefile is like this:

all: example

example: main.o es_dev_example.o
        $(CC) $^ -o $@ -L. -lgateway -lpthread -lpaho-mqtt3as -lssl -lcrypto

%.o: %.c
        $(CC) -c $(CFLAGS) $< -o $@
  1. About MQTT client

When building the source code, if there is issue that show “fatal error: MQTTAsync.h: No such file or directory”.

The reason is that host machine haven’t installed mqtt client library(paho), you should get and install it.

steps:

git clone https://github.com/eclipse/paho.mqtt.c.git
cd paho.mqtt.c
make
sudo make install