本文讲解设备开发者如何通过阿里云IoT已认证的WiFi模组上开发产品功能,并将设备连接到生活物联网平台。

前提条件

请参见开发环境设置,安装好开发环境。

获取SDK代码及编译

  1. 获取SDK代码,请参见生活物联网平台SDK
  2. 解压下载的zip包。
  3. 验证aos是否可以正常编译。

    以在某一模组上编译一个打印hello world程序为例。

    aos make helloworld@<模组名>

    以模组名为mk3080为例,完成编译后,会在out\helloworld@mk3080\binary_目录下生成helloworld@mk3080.all.bin文件,该文件即为需要烧写的文件,用户可以将其烧写到模组上查看程序是否可以正常运行。

    说明 可以在board目录,查看已支持的模组。

烧写固件到模组

在编译得到固件后,即可以将固件烧写到模组中执行。不同的模组烧写过程不一样,请联系模组厂商获取烧写工具以及烧写说明。

在生活物联网平台定义产品

  1. 创建产品。

    参见新建产品,并按如下要求设置参数。

    • 选择节点类型设备
    • 选择是否接入网关
    • 选择连网方式WiFi
    • 选择数据格式ICA标准数据格式(Alink JSON)
  2. 产品功能定义。
    参见功能概述来定义产品的功能。
  3. 调试设备。
    1. 选择已认证的模组。
      调试设备
    2. 新增测试设备,并获取设备激活凭证。
      详细操作参见新增测试设备新增调试设备成功
  4. 产品-人机交互页面,打开使用公版App控制产品的开关,使用生活物联网平台提供的公版App对设备进行控制。其余配置请参见人机交互
  5. 批量投产页面确认设备信息。
    说明 设备开发结束之后才会投产,开发阶段请不要单击完成开发

产品功能开发

  1. 开发产品模型。
    对于WiFi设备来说,首先需要通过WiFi配网来获得WiFi热点的SSID/密码,而WiFi配网的移植和调试比较耗费时间,所以做产品开发的时候可以并行实现产品功能开发和WiFi配网功能。

    如下示例代码中注释掉配网的代码,让设备直接连接一个指定的热点,然后连接到阿里云物联网平台,从而可以开始开发与调试产品物模型功能。

    说明 被修改的start_netmgr()函数位于AliOS-Things/example/linkkitapp/app_entry.c文件中。
    int application_start(int argc, char **argv)
    {
      ...
    #if 0
    #ifdef SUPPORT_DEV_AP
         aos_task_new("dap_open", awss_open_dev_ap, NULL, 4096);
    #else
        aos_task_new("netmgr_start", start_netmgr, NULL, 4096);
    #endif
    #endif
        //下面的代码让设备直接去连接一个指定SSID
        netmgr_ap_config_t config;
        strncpy(config.ssid, "your_ssid", sizeof(config.ssid) - 1);
        strncpy(config.pwd, "your_ssid_password", sizeof(config.pwd) - 1);
        netmgr_set_ap_config(&config);
        netmgr_start(false);
    ...
      }

    此外还有另一种方式,在调试阶段可以用cli命令进行配网。当设备连接到WiFi热点,获得一个IP地址之后会自动去连接阿里云物联网平台。

    netmgr connect ssid password
  2. 配置设备身份信息。
    linkkit_example_solo.c的代码中设置了设备的身份信息,设备开发者需要将测试设备的信息对其进行替换。
    // for demo only
    #define PRODUCT_KEY     "a15****PqM"
    #define PRODUCT_SECRET  "4uZsr*****zhjPM"
    #define DEVICE_NAME     "IFn6******OaI2cJy"
    #define DEVICE_SECRET   "qwvShyphC*******NZFjc8S"

    设备开发者将设备身份信息设置到程序之后,可以将代码进行编译并遵循模组商提供的烧写工具将固件写入模组,确保设备可以连接到阿里云物联网平台。如果模组提供串口打印输出,当模组连接到阿里云物联网平台后将会输出类似如下的提示信息。

    模组链接IoT成功

    如果模组可以正常连接阿里云物联网平台,在生活物联网平台的商家后台可以看到该设备已激活,以及设备连接到物联网平台的时间信息。

    模组设备在线
  3. 上报产品属性。
    产品的属性发生变化时,需要将变化后的数值上报到物联网平台。属性变化的检测以及上报是由设备开发者定义和实现的。

    示例代码中对应的产品具有一个LightSwitch的属性,类型为bool,还具有一个RGBColor的属性,类型为复合型。在linkkit_example()函数中调用了user_post_property()函数,用于上报相关属性,用户可以参照该代码上报产品的属性变化。

    void user_post_property(void)
    {
        static int example_index = 0;
        int res = 0;
        user_example_ctx_t *user_example_ctx = user_example_get_ctx();
        char *property_payload = "NULL";
    
        if (example_index == 0) {
            /* Normal Example */
            property_payload = "{\"LightSwitch\":1}";
            example_index++;
        } else if (example_index == 1) {
            /* Wrong Property ID */
            property_payload = "{\"LightSwitchxxxx\":1}";
            example_index++;
        } else if (example_index == 2) {
            /* Wrong Value Format */
            property_payload = "{\"LightSwitch\":\"test\"}";
            example_index++;
        } else if (example_index == 3) {
            /* Wrong Value Range */
            property_payload = "{\"LightSwitch\":10}";
            example_index++;
        } else if (example_index == 4) {
            /* Missing Property Item */
            property_payload = "{\"RGBColor\":{\"Red\":45,\"Green\":30}}";
            example_index++;
        } else if (example_index == 5) {
            /* Wrong Params Format */
            property_payload = "\"hello world\"";
            example_index++;
        } else if (example_index == 6) {
            /* Wrong Json Format */
            property_payload = "hello world";
            example_index = 0;
        }
    
        res = IOT_Linkkit_Report(user_example_ctx->master_devid, ITM_MSG_POST_PROPERTY,
                                 (unsigned char *)property_payload, strlen(property_payload));
    
        EXAMPLE_TRACE("Post Property Message ID: %d", res);
    }
  4. 上报产品事件。
    如果产品定义了事件,当事件发生时也需要向云端发送事件。事件的检测以及上报由设备开发者实现。

    例如产品定义了一个标识符为Error的事件,该事件还有一个标识符为ErrorCode的输出参数。如下示例代码描述了如何向物联网平台发送一个事件。

    void user_post_event(void)
    {
        static int example_index = 0;
        int res = 0;
        user_example_ctx_t *user_example_ctx = user_example_get_ctx();
        char *event_id = "Error";
        char *event_payload = "NULL";
    
        if (example_index == 0) {
            /* Normal Example */
            event_payload = "{\"ErrorCode\":0}";
            example_index++;
        } else if (example_index == 1) {
            /* Wrong Property ID */
            event_payload = "{\"ErrorCodexxx\":0}";
            example_index++;
        } else if (example_index == 2) {
            /* Wrong Value Format */
            event_payload = "{\"ErrorCode\":\"test\"}";
            example_index++;
        } else if (example_index == 3) {
            /* Wrong Value Range */
            event_payload = "{\"ErrorCode\":10}";
            example_index++;
        } else if (example_index == 4) {
            /* Wrong Value Range */
            event_payload = "\"hello world\"";
            example_index++;
        } else if (example_index == 5) {
            /* Wrong Json Format */
            event_payload = "hello world";
            example_index = 0;
        }
    
        res = IOT_Linkkit_TriggerEvent(user_example_ctx->master_devid, event_id, strlen(event_id),
                                       event_payload, strlen(event_payload));
        EXAMPLE_TRACE("Post Event Message ID: %d", res);
    }

    linkkit_example()函数中的linkkit_ops定义了系统的各种事件处理函数,处理产品回调函数。示例代码如下所示。

    int linkkit_example()
    {
    ...
          /* Register Callback */
        IOT_RegisterCallback(ITE_CONNECT_SUCC, user_connected_event_handler);
        IOT_RegisterCallback(ITE_DISCONNECTED, user_disconnected_event_handler);
        IOT_RegisterCallback(ITE_RAWDATA_ARRIVED, user_down_raw_data_arrived_event_handler);
        IOT_RegisterCallback(ITE_SERVICE_REQUST, user_service_request_event_handler);
        IOT_RegisterCallback(ITE_PROPERTY_SET, user_property_set_event_handler);
        IOT_RegisterCallback(ITE_PROPERTY_GET, user_property_get_event_handler);
        IOT_RegisterCallback(ITE_REPORT_REPLY, user_report_reply_event_handler);
        IOT_RegisterCallback(ITE_TRIGGER_EVENT_REPLY, user_trigger_event_reply_event_handler);
        IOT_RegisterCallback(ITE_TIMESTAMP_REPLY, user_timestamp_reply_event_handler);
        IOT_RegisterCallback(ITE_INITIALIZE_COMPLETED, user_initialized);
        IOT_RegisterCallback(ITE_FOTA, user_fota_event_handler);
        IOT_RegisterCallback(ITE_COTA, user_cota_event_handler);
        };

    关于回调函数的说明如下。

    事件 回调函数原型 事件触发条件说明
    ITE_CONNECT_SUCC
    int callback(void);
    与云端连接成功时
    ITE_DISCONNECTED
    int callback(void);
    与云端连接断开时
    ITE_RAWDATA_ARRIVED
    int callback(const int devid, const unsigned char *payload, const int payload_len);
    Linkkit收到收到raw data数据时
    ITE_SERVICE_REQUST
    int callback(const int devid, const char _serviceid, const int serviceid_len, const char request, const int request_len, char _response, int *response_len);
    Linkkit收到收到服务(同步/异步)调用请求时
    ITE_PROPERTY_SET
    int callback(const int devid, const char *request, const int request_len);
    Linkkit收到收到属性设置请求时
    ITE_PROPERTY_GET
    int callback(const int devid, const char _request, const int request_len, char _response, int response_len);
    Linkkit收到收到属性获取的请求时
    ITE_REPORT_REPLY
    int callback(const int devid, const int msgid, const int code, const char *reply, const int reply_len);
    Linkkit收到收到上报消息的应答时
    ITE_TRIGGER_EVENT_REPLY
    int callback(const int devid, const int msgid, const int code, const char eventid, const int eventid_len, const char message, const int message_len);
    Linkkit收到收到事件上报消息的应答时
    ITE_TIMESTAMP_REPLY
    int callback(const char *timestamp);
    当Linkkit收到收到查询时间戳请求的应答时
    ITE_TOPOLIST_REPLY
    int callback(const int devid, const int msgid, const int code, const char * payload, const int payload_len);
    Linkkit收到收到查询拓扑关系请求的应答时
    ITE_PERMIT_JOIN
    int callback(const char * product_key, const int time);
    Linkkit收到允许子设备入网的请求时
    ITE_INITIALIZE_COMPLETED
    int callback(const int devid);
    设备初始化完成时
    ITE_FOTA
    int callback(int type, const char *version);
    Linkkit收到可用固件的通知时
    ITE_COTA
    int callback(int type, const char config_id, int config_size, const char get_type, const char sign, const char sign_method, const char *url);
    Linkkit收到可用远程配置文件的通知时
  5. 主循环处理。
    linkkit_example()中存在一个循环,其中IOT_Linkkit_Yield必须周期调用,用于linkkit业务处理。代码如下所示。
    time_begin_sec = user_update_sec();
        while (1) {
            IOT_Linkkit_Yield(USER_EXAMPLE_YIELD_TIMEOUT_MS);
    
            time_now_sec = user_update_sec();
            if (time_prev_sec == time_now_sec) {
                continue;
            }
            if (max_running_seconds && (time_now_sec - time_begin_sec > max_running_seconds)) {
                EXAMPLE_TRACE("Example Run for Over %d Seconds, Break Loop!\n", max_running_seconds);
                break;
            }
    
            /* Post Proprety Example */
            if (time_now_sec % 11 == 0 && user_master_dev_available()) {
                user_post_property();
            }
            /* Post Event Example */
            if (time_now_sec % 17 == 0 && user_master_dev_available()) {
                user_post_event();
            }
    
            /* Device Info Update Example */
            if (time_now_sec % 23 == 0 && user_master_dev_available()) {
                user_deviceinfo_update();
            }
    
            /* Device Info Delete Example */
            if (time_now_sec % 29 == 0 && user_master_dev_available()) {
                user_deviceinfo_delete();
            }
    
            /* Post Raw Example */
            if (time_now_sec % 37 == 0 && user_master_dev_available()) {
                user_post_raw_data();
            }
    
            time_prev_sec = time_now_sec;
        }

    linkkit_example()中还包含了如下所示一段演示代码,用于上报所有的属性、事件等,在实际产品开发时需要将其修改为设备商自己的逻辑。

    while (1) {
            IOT_Linkkit_Yield(USER_EXAMPLE_YIELD_TIMEOUT_MS);
    
    
            /* Post Proprety Example */
            if (user_master_dev_available()) {
                user_post_property();
            }
            /* Post Event Example */
            if (user_master_dev_available()) {
                user_post_event();
            }
        }

    设备商完成自己物模型功能的代码编写之后,可以将固件编译出来并根据相关模组的烧写方法把固件烧写到模组上进行功能验证和调试。

WiFi配网

配网支持如下所示。

  • 一键配网(Smartconfig):App直接给设备配网
  • 手机热点配网(phone-config):App直接给设备配网
  • 路由器热点配网(router-config):输出到路由器厂商/运营商
  • 零配(zero-config):已配网设备为待配网设备配网
  • 设备热点配网(dev-ap):设备开热点,手机连接设备热点完成为设备配网
  • 蓝牙配网(ble-config):借助BT/BLE为设备配网
配网支持
注意 在开发WiFi配网时,请确保去除前面章节开发产品物模型时对start_netmgr()的修改。
static void start_netmgr(void *p) 
{

    /*
     * register event callback to detect event of AWSS
     */
    iotx_event_regist_cb(linkkit_event_monitor);
    netmgr_start(true);
    aos_task_exit(0);
}

详细的开发过程如下步骤所示。

  1. 配网对外披露的API列表。
    /*
     * Copyright (C) 2015-2018 Alibaba Group Holding Limited
     */
    
    #ifndef __IOT_EXPORT_AWSS_H__
    #define __IOT_EXPORT_AWSS_H__
    
    #if defined(__cplusplus)  /* If this is a C++ compiler, use C linkage */
    extern "C" {
    #endif
    
    /**
     * @brief   start wifi setup service
     *
     * @retval  -1 : wifi setup fail
     * @retval  0 : sucess
     * @note: awss_config_press must been called to enable wifi setup service
     */
    int awss_start();
    
    /**
     * @brief   stop wifi setup service
     *
     * @retval  -1 : failure
     * @retval  0 : sucess
     * @note
     *      if awss_stop is called before exit of awss_start, awss and notify will stop.
     *      it may cause failutre of awss and device bind.
     */
    int awss_stop();
    
    /**
     * @brief   make sure user touches device belong to themselves
     *
     * @retval  -1 : failure
     * @retval  0 : sucess
     * @note: AWSS dosen't parse awss packet until user touch device using this api.
     */
    int awss_config_press();
    
    /**
     * @brief   start wifi setup service with device ap
     *
     * @retval  -1 : failure
     * @retval  0 : sucess
     * @note
     *      1. if awss_stop or awss_dev_ap_stop is called before exit of awss_dev_ap_start
     *         awss with device ap and notify will stop, it may cause failutre of device ap
     *         and device bind.
     *      2. awss_dev_ap_start doesn't need to call awss_config_press to been enabled.
     */
    int awss_dev_ap_start();
    
    /**
     * @brief   stop wifi setup service with device ap
     *
     * @retval  -1 : failure
     * @retval  0 : sucess
     * @note
     *      if awss_dev_ap_stop is called before exit of awss_dev_ap_start
     *      awss with device ap and notify will stop, it may cause failutre of device ap
     */
    int awss_dev_ap_stop();
    
    /**
     * @brief   report token to cloud after wifi setup success
     *
     * @retval  -1 : failure
     * @retval  0 : sucess
     */
    int awss_report_cloud();
    
    /**
     * @brief   report reset to cloud.
     *
     * @retval  -1 : failure
     * @retval  0 : sucess
     * @note
     *      device will save reset flag if device dosen't connect cloud, device will fails to send reset to cloud.
     *      when connection between device and cloud is ready, device will retry to report reset to cloud.
     */
    int awss_report_reset();
    
    enum awss_event_t {
        AWSS_START = 0x1000,       // AWSS start without enbale, just supports device discover
        AWSS_ENABLE,               // AWSS enable
        AWSS_LOCK_CHAN,            // AWSS lock channel(Got AWSS sync packet)
        AWSS_CS_ERR,               // AWSS AWSS checksum is error
        AWSS_PASSWD_ERR,           // AWSS decrypt passwd error
        AWSS_GOT_SSID_PASSWD,      // AWSS parse ssid and passwd successfully
        AWSS_CONNECT_ADHA,         // AWSS try to connnect adha (device discover, router solution)
        AWSS_CONNECT_ADHA_FAIL,    // AWSS fails to connect adha
        AWSS_CONNECT_AHA,          // AWSS try to connect aha (AP solution)
        AWSS_CONNECT_AHA_FAIL,     // AWSS fails to connect aha
        AWSS_SETUP_NOTIFY,         // AWSS sends out device setup information (AP and router solution)
        AWSS_CONNECT_ROUTER,       // AWSS try to connect destination router
        AWSS_CONNECT_ROUTER_FAIL,  // AWSS fails to connect destination router.
        AWSS_GOT_IP,               // AWSS connects destination successfully and got ip address
        AWSS_SUC_NOTIFY,           // AWSS sends out success notify (AWSS sucess)
        AWSS_BIND_NOTIFY,          // AWSS sends out bind notify information to support bind between user and device
        AWSS_ENABLE_TIMEOUT,       // AWSS enable timeout(user needs to call awss_config_press again to enable awss)
        AWSS_RESET = 0x3000,       // Linkkit reset success (just got reset response from cloud without any other operation)
    };
    
    #if defined(__cplusplus)  /* If this is a C++ compiler, use C linkage */
    }
    #endif
    
    #endif
  2. App中调用配网。
    /*
     * application_start is application entrance based on sdk.
     */
    int application_start(int argc, char **argv)
    {
        ......
    
        /*
         * set device triple ID information before AWSS, otherwise AWSS will fail.
         * HAL_SetProductKey(product_key);
         * HAL_SetProductSecret(product_secret);
         * HAL_SetDeviceName(dev_name);
         * HAL_SetDeviceSecret(dev_secret);
         */
        set_iotx_info();
    
        /*
         * set log level to print debug information about AWSS
         */
        LITE_set_loglevel(5);  // 5 for debug level
    
        /*
         * Start netmgr task for AWSS
         * default stack size of netmgr task is 4096B,
         * if the module or die takes more stack size,
         * please set larger stack size (maybe, 6KB)
         */
    #ifdef SUPPORT_DEV_AP
        aos_task_new("dap_open", awss_open_dev_ap, NULL, 4096);
    #else
        aos_task_new("netmgr_start", start_netmgr, NULL, 4096);
    #endif
    
        aos_loop_run();
    
        return 0;
    }
    #ifdef SUPPORT_DEV_AP
    void awss_open_dev_ap(void *p)
    {
        iotx_event_regist_cb(linkkit_event_monitor);
        LOG("%s\n", __func__);
        if (netmgr_start(false) != 0) {
            aos_msleep(2000);
            awss_dev_ap_start();
        }
        aos_task_exit(0);
    }
    #endif
    static void start_netmgr(void *p) 
    {
        /*
         * register event callback to detect event of AWSS
         */
        iotx_event_regist_cb(linkkit_event_monitor);
        netmgr_start(true);
        aos_task_exit(0);
    }
    说明
    • set_iotx_info一定要在awss_startnetmgr_start之前调用设备证书信息,否则设备无法解析路由器的PASSWORD,PASSWORD采用加密措施保证安全性,而密钥需要借助于设备证书信息计算。
    • LIET_set_loglevel函数如果需要调试打开log,该函数需要在awss_start前调用。
    • awss_start只是开始AWSS服务(发现周围的AP列表),并未使能AWSS服务。如果需要设备解析配网包,考虑安全问题,还需要调用awss_press_config来使能AWSS(设备热点配网除外)。
    • 关于调用awss_press_config的时机和策略,虽然Demo中采用按键触发,但产品厂商可以根据自己的产品的特点来自行设计。

    目前awss_start已经被AliOS封装在netmgr模块中(netmgr_start),具体可以阅读netmgr_start的代码。

    int netmgr_start(bool autoconfig)
    {
        ......
    
        /*
         * if the last AP information exists
         * try to connect the last AP.
         */
        if (has_valid_ap() == 1) {
            aos_post_event(EV_WIFI, CODE_WIFI_CMD_RECONNECT, 0); 
            return 0;
        } 
    
        ......
    
         /*
         * if the last AP information doesn't exist
         * start AWSS (Alibaba Wireless Setup Service)
         */
        if (autoconfig) {  
            netmgr_wifi_config_start();  // call awss_start()
            return 0;
        }
    
        ......
    
        return -1; 
      }
  3. 特定Demo说明。
    • 从设备热点配网切换到其他配网模式,调用do_awss。
      void do_awss()
      {
          aos_task_new("dap_close", awss_close_dev_ap, NULL, 2048);
          aos_task_new("netmgr_start", start_netmgr, NULL, 4096);
      }
      
      static void awss_close_dev_ap(void *p)
      {
          awss_dev_ap_stop();
          LOG("%s exit\n", __func__);
          aos_task_exit(0);
      }
      
      static void start_netmgr(void *p)
      {
          iotx_event_regist_cb(linkkit_event_monitor);
          LOG("%s\n", __func__);
          aos_msleep(2000);
          netmgr_start(true);
          aos_task_exit(0);
      }
    • 从其他配网模式切换到设备热点配网,调用do_awss_dev_ap
      void do_awss_dev_ap()
      {
          aos_task_new("netmgr_stop", stop_netmgr, NULL, 4096);
          aos_task_new("dap_open", awss_open_dev_ap, NULL, 4096);
      }
      
      static void stop_netmgr(void *p)
      {
          awss_stop();
          LOG("%s\n", __func__);
          aos_task_exit(0);
      }
      
      static void awss_open_dev_ap(void *p)
      {
          iotx_event_regist_cb(linkkit_event_monitor);
          LOG("%s\n", __func__);
          if (netmgr_start(false) != 0) {
              aos_msleep(2000);
              awss_dev_ap_start();
          }
          aos_task_exit(0);
      }

    更多的WiFi配网的描述可参见配网开发文档

云端解绑通知

设备被解绑后,云端会下发一个解绑事件通知:{"identifier":"awss.BindNotify","value":{"Operation":"Unbind"}} 设备收到此消息可以做重置配网、清空本地数据等处理。

设备重置

对于生活物联网平台来说,建议产品设计一个reset按键用于清除设备上的配置,将设备恢复到出厂状态,同时调用awss_report_reset()函数告知云端清除设备与用户的绑定关系。

因此,设备商需要在处理reset按键的逻辑中增加对awss_report_reset()的调用。

/*
 * 应用程序调用该API后,Linkkit首先往Flash里存储恢复出厂设置的标志,并向云端上报reset操作,
 * 在规定的时间内(3秒)如果没有收到云端的回复,设备会重新上传reset,直至收到云端的回复位置;
 * 有些产品希望发生reset时设备可以重新启动,如果重新启动之前reset没有上报成功,下一次连接云后,
 * 设备会首先检查Flash中恢复出厂标志是否设置,如果设置了则首先向云端上报reset,直至成功;
 */
int awss_report_reset();

开发OTA

若使能了OTA功能,请参见OTA编程