本文以TG7100C芯片为例,介绍基于生活物联网平台SDK(V1.6.6)中的smart_outlet应用示例,开发单孔Wi-Fi智能插座设备固件的流程。
背景信息
应用示例smart_outlet的功能介绍如下:
- 支持云智能App(V3.5.5以上)与天猫精灵App(4.13.0以上)蓝牙辅助配网。
- 支持通过云端、本地通信(目前仅云智能App支持)对设备进行控制的能力。
- 支持通过生活物联网平台进行设备OTA的能力。
- 支持恢复工厂设置。
- 支持断电用户设置记忆。
TG7100C概述
TG7100C是天猫精灵推出的Wi-Fi蓝牙Combo芯片。TG7100C芯片相关文档和软件工具介绍,请参见TG7100C。
说明
关于TG7100B芯片的驱动、产测、硬件设计、射频等使用问题,以及基于生活物联网平台蓝牙Mesh SDK的应用开发,例如产品配置、配网、连云、OTA等问题,您可以通过商务联系技术支持进行反馈。
固件编译
固件烧录与运行
- 在TG7100C开发板上烧录固件。详细操作,请参见TG7100C开发板用户手册。
- 短路接通开发板的第4个引脚与第5个引脚,并按开发板的复位键。开发板设置图如下。
- 打开下载好的烧录工具目录中的TGFlashEnv.exe,单击Finish,进入烧录界面。
- 将Interface选择Uart,并单击Refresh按键。
- 设置串口参数,以及选择好对应的烧录文件。串口参数的配置如下图所示。
- Partition Table、Boot 2 Bin与MFG Bin选择烧录工具目录下对应的文件即可,文件名如图。
- Firmware Bin选择编译出的固件。
- Chip Erase可以根据是否要擦除整片Flash选择True或者False,如调试中要保留之前写入过的设备证书,可以选择False。
- 单击Download按钮,并同时按下开发板上的复位键,开始烧录固件。
- 烧录完毕后,查看运行的日志。短路连接第3个引脚与第4个引脚(如下图所示),将串口工具波特率设置为2000000,并按下开发板复位键。
常用的cli指令如下。
- reset:设备重置,清除设备配网信息。
- free:查看内存使用情况。
- linkkey:写入与查看证书。
- mac:查看开发板Wi-Fi MAC地址。
smart_outlet应用代码结构介绍
smart_outlet应用示例中的文件结构如下。
├── Products
│ │ ├── example/smart_outlet
│ │ │ ├── app_entry.c
│ │ │ ├── app_entry.h
│ │ │ ├── combo_net.c
│ │ │ ├── device_state_manager.c
│ │ │ ├── device_state_manager.h
│ │ │ ├── factory.c
│ │ │ ├── factory.h
│ │ │ ├── makefile
│ │ │ ├── make.settings
│ │ │ ├── msg_process_center.c
│ │ │ ├── msg_process_center.h
│ │ │ ├── property_report.c
│ │ │ ├── property_report.h
│ │ │ ├── smart_outlet.h
│ │ │ ├── smart_outlet.json
│ │ │ ├── smart_outlet_main.c
│ │ │ ├── smart_outlet.mk
│ │ │ ├── vendor.c
│ │ │ └── vendor.h
详细的文件说明如下。
- 厂家需要适配的文件(设备初始化等):vendor.c与vendor.h
- 应用程序主入口:app_entry.c与smart_outlet_main.c
- 配网和连云状态管理:device_state_manager.c
- 设备控制指令处理:msg_process_center.c
- 设备属性上报:property_report.c
- 厂测模式:factory.c
- 蓝牙辅助配网:combo_net.c
固件适配说明
标品固件移植适配对单路智能插座应用,只需要较小的修改,就可以完成产品固件的输出。根据产品的不同需求,涉及到的调整项介绍如下。
- GPIO适配
单路插座需要两个GPIO分别控制继电器开关、LED亮灭和一个GPIO读取按键状态。那么只需要修改vendor.c中定义,实例如下。
...... #elif (defined (TG7100CEVB)) #define LED_GPIO 1 // 控制LED亮灭 #define RELAY_GPIO 5 // 控制继电器开关 #define KEY_GPIO 3 // 读取按键状态 ......
产品开发时,可以根据具体的原理图设计配置对应的GPIO。
- 状态LED显示适配
- 设备状态定义在文件Products/example/smart_outlet/device_state_manager.h 中。
typedef enum { RECONFIGED = 0, //reconfig with netconfig exist UNCONFIGED, //配网开始 AWSS_NOT_START, //配网超时 GOT_AP_SSID, //连接AP成功 CONNECT_CLOUD_SUCCESS, //连云成功 CONNECT_CLOUD_FAILED, //连云失败 CONNECT_AP_FAILED, //连接AP失败 CONNECT_AP_FAILED_TIMEOUT, //连接AP超时 APP_BIND_SUCCESS, //APP绑定成功 ... UNKNOW_STATE } eNetState;
- 状态显示的处理代码在文件Products/example/smart_outlet/device_state_manager.c 中的
indicate_net_state_task
函数中。可以根据产品的不同需求做调整。static void indicate_net_state_task(void *arg) { uint32_t nCount = 0; uint32_t duration = 0; int pre_state = UNKNOW_STATE; int cur_state = UNKNOW_STATE; int switch_stat = 0; while (1) { pre_state = cur_state; cur_state = get_net_state(); switch (cur_state) { case RECONFIGED: ... break; case UNCONFIGED: ... break; case AWSS_NOT_START: ... break; case GOT_AP_SSID: case CONNECT_CLOUD_FAILED: ... break; case CONNECT_AP_FAILED_TIMEOUT: ... break; case CONNECT_AP_FAILED: ... break; case CONNECT_CLOUD_SUCCESS: ... break; case APP_BIND_SUCCESS: ... break; ... default: break; } aos_msleep(100); } ... }
- 当前代码中实现的默认LED显示如下。
状态 默认LED显示 配网模式 插座LED反复闪烁,亮0.8秒,灭0.8秒。 恢复出厂设置 插座LED反复闪烁,亮0.2秒,灭0.2秒。 连接AP 超时/连接AP 认证失败(超时时间2分钟) 插座LED反复闪烁的模式更改为,亮0.5秒、灭0.5秒,闪烁两分钟之后停止闪烁。停止闪烁之后,如果插座配电使能则LED灯点亮,否则LED灯灭掉。 连接AP成功、尝试连云 插座LED反复闪烁,亮0.8秒,灭0.8秒,然后开始尝试连接云端。 连云失败 连接云端失败后,需要再次尝试连接,其间LED的显示与“连接AP成功、尝试连云”模式一样。 连云成功 当设备连接云端成功,则停止LED闪烁,若插座配电打开则LED点亮,若插座配电未打开则LED灭掉。
- 设备状态定义在文件Products/example/smart_outlet/device_state_manager.h 中。
- 按键处理适配标品固件根据用户按下按键的时长,确定用户的行为,目前按键有三种用户行为处理。代码Products/example/smart_outlet/device_state_manager.c文件中的
key_detect_event_task
函数负责按键处理。如下定义了各种行为的时间,如果需要调整各个行为的按键时长,可以自行修改。#define AWSS_REBOOT_TIMEOUT (4 * 1000) //长按4s 进入网络配置模式,开始重新配网 #define AWSS_RESET_TIMEOUT (6 * 1000) //长按6s 进入恢复出厂设置,(在设备已进入网络配置模式下) #define KEY_PRESSED_VALID_TIME_MIN 100 #define KEY_PRESSED_VALID_TIME_MAX 500 //按键按下超过100ms,小于500ms,表示有按键按下 #define KEY_DETECT_INTERVAL 50 //按键按下的检测时间间隔 50ms #define AWSS_REBOOT_CNT AWSS_REBOOT_TIMEOUT /KEY_DETECT_INTERVAL #define AWSS_RESET_CNT AWSS_RESET_TIMEOUT /KEY_DETECT_INTERVAL #define KEY_PRESSED_CNT KEY_PRESSED_VALID_TIME /KEY_DETECT_INTERVAL // 此函数处理插座按键检测 void key_detect_event_task(void *arg) { int nCount = 0, awss_mode = 0; int timeout = (AWSS_REBOOT_CNT < AWSS_RESET_TIMEOUT)? AWSS_REBOOT_CNT : AWSS_RESET_TIMEOUT; while (1) { if (!product_get_key()) { nCount++; LOG("nCount :%d", nCount); } else { if (nCount >= KEY_PRESSED_CNT && nCount < timeout) { // 按键控制 if (product_get_switch() == ON) { // 按键控制插座关闭继电器 product_set_switch(OFF); user_post_powerstate(OFF); } else { // 按键控制插座打开继电器 product_set_switch(ON); user_post_powerstate(ON); } } if ((awss_flag == 0) && (nCount >= AWSS_REBOOT_CNT)) { LOG("do awss reboot"); // 长按4s 进入网络配置模式,开始重新配网 do_awss_reboot(); break; } else if ((awss_flag == 1) && (nCount > AWSS_RESET_CNT)) { LOG("do awss reset"); // 长按6s 进入恢复出厂设置 do_awss_reset(); // 实际执行设备重置 break; } nCount = 0; } if ((awss_flag == 0) && (nCount >= AWSS_REBOOT_CNT && awss_mode == 0)) { set_net_state(RECONFIGED); // 设置相应的设备状态 awss_mode = 1; } else if ((awss_flag == 1) && (nCount > AWSS_RESET_CNT && awss_mode == 0)) { set_net_state(UNCONFIGED); // 设置相应的设备状态 awss_mode = 1; } aos_msleep(KEY_DETECT_INTERVAL); // 检测按键间隔为50ms } aos_task_exit(0); }
- 短按:如果按键按下时长长于100ms,小于500ms, 认为用户是进行按键开关。
- 长按:如果用户按下时间超过4s,认为用户触发设备进入网络配置模式。如果用户确认设备已经进入网络配置模式,此时继续按键6s,设备会进入恢复出厂模式。
设备端通用功能说明
以下功能在smart_outlet应用示例中已有相关实现,仅对设备端的通用功能做一些补充介绍。
- 事件回调在smart_outlet_main.c文件中定义了系统的各种事件处理函数,在
linkkit_main
函数中注册了回调函数。int linkkit_main() { ... /* 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_REQUEST, user_service_request_event_handler); IOT_RegisterCallback(ITE_PROPERTY_SET, user_property_set_event_handler); #ifdef ALCS_ENABLED /*Only for local communication service(ALCS) */ IOT_RegisterCallback(ITE_PROPERTY_GET, user_property_get_event_handler); #endif 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_INITIALIZE_COMPLETED, user_initialized); IOT_RegisterCallback(ITE_EVENT_NOTIFY, user_event_notify_handler); ... }
事件 事件触发条件说明 ITE_CONNECT_SUCC 与云端连接成功时 ITE_DISCONNECTED 与云端连接断开时 ITE_RAWDATA_ARRIVED SDK收到raw data数据时 ITE_SERVICE_REQUEST SDK收到服务(同步/异步)调用请求时 ITE_PROPERTY_SET SDK收到属性设置请求时 ITE_PROPERTY_GET SDK收到属性获取的请求时 ITE_REPORT_REPLY SDK收到上报消息的应答时 ITE_TRIGGER_EVENT_REPLY SDK收到事件上报消息的应答时 ITE_EVENT_NOTIFY SDK收到事件通知时 ITE_INITIALIZE_COMPLETED 设备初始化完成时 - 属性上报产品的属性发生变化时,需要将变化后的数值上报到物联网平台。可以根据产品需求增加属性变化的检测以及上报逻辑。
void user_post_property(property_report_msg_t * msg) { int res = 0; user_example_ctx_t *user_example_ctx = user_example_get_ctx(); char *property_payload = NULL; cJSON *response_root = NULL, *item_csr = NULL; response_root = cJSON_CreateObject(); if (response_root == NULL) { return; } if (msg->seq != NULL && strcmp(msg->seq, SPEC_SEQ)) { item_csr = cJSON_CreateObject(); if (item_csr == NULL) { cJSON_Delete(response_root); return; } cJSON_AddStringToObject(item_csr, "seq", msg->seq); cJSON_AddItemToObject(response_root, "CommonServiceResponse", item_csr); } #ifdef TSL_FY_SUPPORT //兼容旧版本开关PowerSwitch属性 cJSON_AddNumberToObject(response_root, "PowerSwitch", msg->powerswitch); #endif //处理新版本物模型开关powerstate属性 cJSON_AddNumberToObject(response_root, "powerstate", msg->powerswitch); //处理新版物模型allPowerstate属性 cJSON_AddNumberToObject(response_root, "allPowerstate", msg->all_powerstate); property_payload = cJSON_PrintUnformatted(response_root); cJSON_Delete(response_root); char *property_formated; uint32_t len; res = user_property_format(property_payload,strlen(property_payload),&property_formated,&len); #ifdef EN_COMBO_NET //对于Wi-Fi&BLE Combo设备可以同时通过蓝牙控制链路上报属性值。 if (combo_ble_conn_state()) { if (0 == res) { combo_status_report(property_formated, strlen(property_formated)); LOG_TRACE("Post Property Message ID: %d Payload %s", res, property_formated); } else { combo_status_report(property_payload, strlen(property_payload)); LOG_TRACE("Post Property Message ID: %d Payload %s", res, property_payload); } } #endif if (0 == res) { if (msg->seq != NULL && strcmp(msg->seq, SPEC_SEQ)) { res = IOT_Linkkit_Report_Ext(user_example_ctx->master_devid, ITM_MSG_POST_PROPERTY, (unsigned char *)property_formated, strlen(property_formated), msg->flag); } else { res = IOT_Linkkit_Report(user_example_ctx->master_devid, ITM_MSG_POST_PROPERTY, (unsigned char *)property_formated, strlen(property_formated)); } LOG_TRACE("Post Property Message ID: %d Payload %s", res, property_formated); example_free(property_formated); } else { if (msg->seq != NULL && strcmp(msg->seq, SPEC_SEQ)) { res = IOT_Linkkit_Report_Ext(user_example_ctx->master_devid, ITM_MSG_POST_PROPERTY, (unsigned char *)property_payload, strlen(property_payload), msg->flag); } else { res = IOT_Linkkit_Report(user_example_ctx->master_devid, ITM_MSG_POST_PROPERTY, (unsigned char *)property_payload, strlen(property_payload)); } LOG_TRACE("Post Property Message ID: %d Payload %s", res, property_payload); } example_free(property_payload); }
- 属性设置smart_outlet按对ITE_PROPERTY_SET注册的回调函数,在回调函数
user_property_set_event_handler
中获取云端设置的属性值,并原样将收到的数据发回给云端,这样可以更新在云端的设备属性值,用户可在此处对收到的属性值进行处理。static int user_property_set_event_handler(const int devid, const char *request, const int request_len) { ... property_setting_handle(request, request_len, &msg); ... } static int property_setting_handle(const char *request, const int request_len, recv_msg_t * msg) { ... if ((item = cJSON_GetObjectItem(root, "setPropsExtends")) != NULL && cJSON_IsObject(item)) { ... } if ((item = cJSON_GetObjectItem(root, "powerstate")) != NULL && cJSON_IsNumber(item)) { //设置powerstate属性处理 msg->powerswitch = item->valueint; msg->all_powerstate = msg->powerswitch; ret = 0; } #ifdef TSL_FY_SUPPORT /* 支持旧版本开关PowerSwitch属性 */ else if ((item = cJSON_GetObjectItem(root, "PowerSwitch")) != NULL && cJSON_IsNumber(item)) { msg->powerswitch = item->valueint; ret = 0; } #endif else if ((item = cJSON_GetObjectItem(root, "allPowerstate")) != NULL && cJSON_IsNumber(item)) { //设置allPowerstate属性处理 msg->powerswitch = item->valueint; msg->all_powerstate = msg->powerswitch; ret = 0; } #ifdef AOS_TIMER_SERVICE else if (((item = cJSON_GetObjectItem(root, "LocalTimer")) != NULL && cJSON_IsArray(item))|| \ ((item = cJSON_GetObjectItem(root, "CountDownList")) != NULL && cJSON_IsObject(item)) || \ ((item = cJSON_GetObjectItem(root, "PeriodTimer")) != NULL && cJSON_IsObject(item)) || \ ((item = cJSON_GetObjectItem(root, "RandomTimer")) != NULL && cJSON_IsObject(item))) { // Timer service 定时、倒计时相关属性设置的处理 cJSON_Delete(root); // Before LocalTimer Handle, Free Memory timer_service_property_set(request); user_example_ctx_t *user_example_ctx = user_example_get_ctx(); IOT_Linkkit_Report(user_example_ctx->master_devid, ITM_MSG_POST_PROPERTY, (unsigned char *)request, request_len); return 0; } #endif else { LOG_TRACE("property set payload is not JSON format"); ret = -1; } cJSON_Delete(root); if (ret != -1) send_msg_to_queue(msg); return ret; }
- 本地通信功能(目前仅云智能App支持)
本地通信功能介绍,请参见本地通信开发实践。
本地通信功能在文件make.settings中通过宏ALCS_ENABLED来管理。使用IOT_RegisterCallback
函数注册ITE_PROPERTY_GET事件,对应回调函数实现为user_property_get_event_handler
。此函数中目前已实现的本地通信请求的设备属性如下所示,如果产品需要增加功能,可以相应的增加新属性的处理case。#ifdef ALCS_ENABLED static int user_property_get_event_handler(const int devid, const char *request, const int request_len, char **response, int *response_len) { user_example_ctx_t *user_example_ctx = user_example_get_ctx(); device_status_t *device_status = &user_example_ctx->status; cJSON *request_root = NULL, *item_propertyid = NULL; cJSON *response_root = NULL; ... for (int index = 0; index < cJSON_GetArraySize(request_root); index++) { item_propertyid = cJSON_GetArrayItem(request_root, index); ... LOG_TRACE("Property ID, index: %d, Value: %s", index, item_propertyid->valuestring); if (strcmp("powerstate", item_propertyid->valuestring) == 0) { //处理新版物模型开关powerstate属性 cJSON_AddNumberToObject(response_root, "powerstate", device_status->powerswitch); } else if (strcmp("allPowerstate", item_propertyid->valuestring) == 0) { //处理新版物模型allPowerstate属性 cJSON_AddNumberToObject(response_root,"allPowerstate", device_status->all_powerstate); } #ifdef TSL_FY_SUPPORT /* support old feiyan TSL */ else if (strcmp("PowerSwitch", item_propertyid->valuestring) == 0) { //兼容旧版本开关PowerSwitch属性 cJSON_AddNumberToObject(response_root, "PowerSwitch", device_status->powerswitch); } #endif #ifdef AOS_TIMER_SERVICE else if (strcmp("LocalTimer", item_propertyid->valuestring) == 0) { ... //处理本地定时LocalTimer } else if (strcmp("CountDownList", item_propertyid->valuestring) == 0) { ... //处理倒计时 #endif } } ... } #endif
- 云端解绑与恢复出厂默认设置通知设备被解绑后,云端会下发一个解绑事件通知
{"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 (value == 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")) { //绑定通知 LOG_TRACE("Device Bind"); vendor_device_bind(); //设备绑定时需要完成的操作,设备应用可定义 } else if (!strcmp(op->valuestring, "Unbind")) { //解绑通知 LOG_TRACE("Device unBind"); vendor_device_unbind(); //设备解绑时需要完成的操作,设备应用可定义 } else if (!strcmp(op->valuestring, "Reset")) { //重置通知 LOG_TRACE("Device reset"); vendor_device_reset(); //设备重置时需要完成的操作,设备应用可定义 } } } .... }
- 蓝牙辅助配网蓝牙辅助配网设备端开发,请参见设备端开发。说明 SDK V1.6.6开始支持新的蓝牙辅助配网方案(配合天猫精灵App V4.13.0以上版本与云智能App V3.5.5以上版本使用),新方案要求设备证书的Device Name与Wi-Fi MAC保持一致。更多介绍,请参见开发自有品牌项目插座产品、开发天猫精灵生态项目插座产品。
- 设备端上定时功能
平台统一使用设备端上定时(DeviceTimer)开发设备端定时功能,详细操作,请参见开发设备端本地定时功能。