本文讲解设备开发者如何通过阿里云IoT已认证的Wi-Fi模组上开发产品功能,并将设备连接到生活物联网平台。
前提条件
已完成开发环境的安装,请参见编译SDK。
获取SDK代码及编译
- 获取SDK代码,请参见获取SDK。
- 解压下载的zip包。
- 验证aos是否可以正常编译。
以在某一模组上编译一个living_platform程序为例。
./build.sh example living_platform mk3080
以模组名为mk3080为例,完成编译后,会在out\living_platform@mk3080\binary\目录下生成living_platform@mk3080.all.bin文件,该文件即为需要烧写的文件,用户可以将其烧写到模组上查看程序是否可以正常运行。
说明 可以在board目录,查看已支持的模组。
烧写固件到模组
在编译得到固件后,即可以将固件烧写到模组中执行。不同的模组烧写过程不一样,请联系模组厂商获取烧写工具以及烧写说明。
在生活物联网平台定义产品
产品功能开发
- 开发产品模型。对于Wi-Fi设备来说,首先需要通过Wi-Fi配网来获得Wi-Fi热点的SSID/密码。而Wi-Fi配网的移植和调试比较耗费时间,提供以下两种方式,可以实现在产品开发阶段,同时进行产品功能开发和Wi-Fi配网功能移植调试。
- 让设备直接连接一个指定的热点,然后连接到生活物联网平台,从而可以开始开发与调试产品物模型功能。如下示例代码中注释掉配网的代码。 说明 被修改的
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命令进行配网。当设备连接到Wi-Fi热点,获得一个IP地址之后会自动去连接生活物联网平台。
netmgr connect ssid password
- 让设备直接连接一个指定的热点,然后连接到生活物联网平台,从而可以开始开发与调试产品物模型功能。如下示例代码中注释掉配网的代码。
- 配置设备身份信息。在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"
设备开发者将设备身份信息设置到程序之后,可以将代码进行编译并遵循模组商提供的烧写工具将固件写入模组,确保设备可以连接到阿里云物联网平台。如果模组提供串口打印输出,当模组连接到阿里云物联网平台后将会输出类似如下的提示信息。
如果模组可以正常连接阿里云物联网平台,在生活物联网平台的商家后台可以看到该设备已激活,以及设备连接到物联网平台的时间信息。
- 上报产品属性。产品的属性发生变化时,需要将变化后的数值上报到物联网平台。属性变化的检测以及上报是由设备开发者定义和实现的。
示例代码中对应的产品具有一个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); }
- 上报产品事件。如果产品定义了事件,当事件发生时也需要向云端发送事件。事件的检测以及上报由设备开发者实现。
例如产品定义了一个标识符为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_REQUEST 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收到可用远程配置文件的通知时 - 主循环处理。在
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 Property 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 Property Example */ if (user_master_dev_available()) { user_post_property(); } /* Post Event Example */ if (user_master_dev_available()) { user_post_event(); } }
设备商完成自己物模型功能的代码编写之后,可以将固件编译出来并根据相关模组的烧写方法把固件烧写到模组上进行功能验证和调试。
Wi-Fi配网
配网支持如下所示。
- 一键配网(Smartconfig):App直接给设备配网
- 手机热点配网(phone-config):App直接给设备配网
- 路由器热点配网(router-config):输出到路由器厂商/运营商
- 零配(zero-config):已配网设备为待配网设备配网
- 设备热点配网(dev-ap):设备开热点,手机连接设备热点完成为设备配网
- 蓝牙配网(ble-config):借助BT/BLE为设备配网
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);
}
详细的开发过程如下步骤所示。
- 配网对外披露的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 Wi-Fi setup service * * @retval -1 : Wi-Fi setup fail * @retval 0 : sucess * @note: awss_config_press must been called to enable Wi-Fi 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 Wi-Fi 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 Wi-Fi 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 Wi-Fi 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
- 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_start或netmgr_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; }
- 特定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); }
更多的Wi-Fi配网的描述可参见配网开发文档。
- 从设备热点配网切换到其他配网模式,调用do_awss。
云端解绑与恢复出厂默认设置通知
设备被解绑后,云端会下发一个解绑事件通知:{"identifier":"awss.BindNotify","value":{"Operation":"Unbind"}}
设备收到此消息可以做重置配网、清空本地数据等处理。
如果通过App将设备恢复出厂默认设置,云端会下发一个Reset事件通知:{"identifier":"awss.BindNotify","value":{"Operation":"Reset"}}
设备收到此消息可以做重置配网、清空本地数据等处理。设备开发者可以结合具体产品类型,决定收到解绑和恢复出厂默认设置通知后做哪些清空操作。
可以参考示例代码example/smart_outlet/smart_outlet_main.c中notify_msg_handle
函数,做如下改动。
static int notify_msg_handle(const char *request, const int request_len)
{
....
if (!strcmp(item->valuestring, "awss.BindNotify")) {
cJSON *value = cJSON_GetObjectItem(request_root, "value");
if (item == NULL || !cJSON_IsObject(value)) {
cJSON_Delete(request_root);
return -1;
}
cJSON *op = cJSON_GetObjectItem(value, "Operation");
if (op != NULL && cJSON_IsString(op)) {
if (!strcmp(op->valuestring, "Bind")) {
EXAMPLE_TRACE("Device Bind");
vendor_device_bind();
}
if (!strcmp(op->valuestring, "Unbind")) {
EXAMPLE_TRACE("Device unBind");
vendor_device_unbind();
}
if (!strcmp(op->valuestring, "Reset")) {
EXAMPLE_TRACE("Device Reset");
vendor_device_reset();
}
}
}
....
}
设备重置
对于生活物联网平台来说,建议产品设计一个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编程。