# 实战二|MQTT开发:如何实现联网控制? 你好,我是郭朝斌。 在上一节中,我们基于平头哥RVB2601开发板完成了智能电灯硬件的搭建和嵌入式应用的开发,但是打造一款物联网设备,我们还需要将硬件接入物联网平台。接下来,我就来讲解一下RVB2601开发板通过MQTT协议接入阿里云生活物联网平台的流程及方法。 在开始本节内容的阅读之前,你可以重新打开[第17讲](https://time.geekbang.org/column/article/322528),了解一下Python语言的实现代码。对比着本节的C语言代码,你将会对程序开发有更深入的理解。 ## 生活物联网平台的准备工作 阿里云生活物联网平台,又称为飞燕平台,是面向消费级产品的物联网开放平台。它具备完整的、面向家居物联网场景的功能定义,可以非常方便地完成智能设备的物联网接入工作。 接下来,我们就在这个平台上完成智能灯的联网控制实验。 ### 创建项目和产品 首先,登录[生活物联网平台](https://living.aliyun.com),我们进行第一个项目的创建。项目的名称,我们可以填写“智能电灯”。对于项目类型,你可以根据产品需求来决定,因为我们不计划接入天猫精灵生态,所以这里选择“自有品牌项目”。 ![](https://static001.geekbang.org/resource/image/e8/1c/e8c304c43ed2yyd0ac86c8b1ca50ff1c.png?wh=1014x754) 接着,我们为这个“智能电灯”项目创建一个新产品“Led\_1”。 ![](https://static001.geekbang.org/resource/image/15/be/15fac49fc02321208fc2c7e7f08d13be.png?wh=1858x554) 产品的参数可以这样设置: * 所属品类,选择“电工照明”–>“灯”。 * 节点设备,选择“设备”。是否接入网关,选择“否”。 * 连网方式,选择“WiFi”。 * 数据格式,选择“ICA标准数据格式(Alink JSON)”。 ![](https://static001.geekbang.org/resource/image/3b/72/3b07a69c927e1044ac15c4fd9fb02c72.png?wh=1220x1062) ![](https://static001.geekbang.org/resource/image/25/4b/25275157ebd091863bb2f6f79063a04b.png?wh=1220x892) ### 产品功能定义 创建完产品,我们进入产品的研发流程。物联网平台把流程分为4个阶段,分别是:功能定义、人机交互、设备调试和批量投产。 首先,我们来完成功能定义的部分,也就是物联网设备的物模型定义。 基于创建产品时我们选择的产品类型和数据格式,平台已经为智能电灯自动生成了一个标准的物模型。针对你开发的智能灯的功能需求,你可以对各项功能进行编辑、删除,或者新增标准模版没有的功能。比如像我这里展示的一样,保留“开关”、“亮度”和“色温”,删除其他功能项,同时增加“RGB调色”功能。“RGB调色”功能项,对应了我们智能灯的三色LED模块。 ![](https://static001.geekbang.org/resource/image/9c/87/9cc88d1efafd17b3df580c9beb9b9e87.png?wh=1752x968) ### 人机交互设计 完成功能定义后,我们进入下一步,人机交互。在人机交互中,我们主要完成配网方式和手机App相关界面的设计。 首先,我们选择使用公版App控制产品。这样可以省掉我们开发独立App的工作。 ![](https://static001.geekbang.org/resource/image/84/d1/840a382b8cd04459fc6d7b1e5ab57bd1.png?wh=2462x454) 在“产品展示”标签页中,设置一下产品名称。 ![](https://static001.geekbang.org/resource/image/f2/25/f277da06b5c5d5e7c4210f22cfe50725.png?wh=2458x896) 在“设备面板”中,你可以点击进入“选择面板”页面,选择一个智能灯在App上的展示和操作界面。因为默认面板中没有适配“RGB调色”的面板,所以,你需要编辑一下“灯泡冷暖灯”模版来替代使用。否则,平台会显示错误信息,提示面板与物模型的属性定义不一致。 ![](https://static001.geekbang.org/resource/image/27/67/274cd8dd412dd288b03d78568e221467.png?wh=1820x1284) 配网方式,我们保持默认设置即可。在“自动化与定时”标签页中,我们要勾选“开关”的“作为执行”选项。这样,在自动化场景的创建中,智能电灯的开关就可以作为执行动作起到控制的效果了。 ### 设备调试设置 在设备调试页面中,我们需要先选择产品使用的芯片或者模组。对于我们的实验项目,这里直接选择列表最后一项——未知芯片即可。 然后,我们新建一个测试设备。因为我们需要获得一个设备证书,也就是智能灯连接物联网平台的五元组信息。 点击“新建测试设备”,你需要为测试设备输入一个名称,比如,可以是“RVB2601HoloLed1”。然后,点击“确定”,页面就会新增一个设备条目。 ![](https://static001.geekbang.org/resource/image/65/64/652c6d5cd03a189fe9f81ab7d9aa6164.png?wh=1172x630) 在新增设备条目中,点击“设备证书”,你就可以看到设备五元组信息。这里要记得复制、保存这些字符串,因为我们在后面的应用代码中需要用到。 ![](https://static001.geekbang.org/resource/image/f3/27/f31999ae09b207dbfedc5244ceb7c027.png?wh=1176x756) 你可以看到,这个新增测试设备的状态显示“未激活”。因为只有当设备通过MQTT协议第一次连接到物联网平台后,这个测试设备才会被激活,并且可以发送消息进行在线调试。 ## 智能灯如何接入物联网 那么,智能灯如何接入物联网平台实现后续的调试、使用呢?下面,我们来开发一下智能灯的联网控制功能。 ### 联网功能开发 RVB2601开发板中的W800模组提供了Wi-Fi和BLE通信能力,而且模组还集成了连接阿里云生活物联网平台(飞燕)的功能。主控芯片CH2601通过SPI接口与W800模组通信,它只需要发送/接收W800定义的AT指令,就可以实现相应的功能。 W800模组的AT指令集可以参考[官方文档](https://occ-oss-prod.oss-cn-hangzhou.aliyuncs.com/userFiles/3717897501090217984/resource/3717897501090217984XBSRZBtccb.pdf)。我们就基于文档中飞燕平台的相关AT指令来实现与平台的通信。它的底层实现依然是MQTT协议,不过封装成了AT指令的接口形式。 这里,我们就需要修改W800的驱动代码,增加联网接口函数,其中具体包括: * 设置设备五元组接口 * 建立MQTT连接接口 * 物模型属性设置回调注册接口 * 物模型属性上报接口 具体要怎么做呢? 我们需要在项目中引入W800的驱动模块drv\_wifi\_at\_w800。在CDK中,点击右键打开“Packages for Led”,在模块窗口左侧找到drv\_wifi\_at\_w800模块,点击箭头导入右侧列表中。 ![](https://static001.geekbang.org/resource/image/6f/2f/6fd57acd5c77daa65d1484fde6a3652f.png?wh=620x458) ![](https://static001.geekbang.org/resource/image/ca/6f/ca8410036690753cd7fa27ceaa87c76f.png?wh=1247x846) 在W800的驱动模块drv\_wifi\_at\_w800中,打开w800\_api.h文件,增加函数接口定义。 ```c++ int w800_living_idmau(const char *mykey,const char *myname,const char *mysecret,const char *mypsecret); int w800_living_idmcon(void); void w800_living_recv_callback_register(const char *cmd, void *callback, void *context); int w800_living_send_attribute(const char *dev_id, const char *msg); ``` 在w800\_api.c文件中,增加函数接口的实现代码。 ```c++ int w800_living_idmau(const char *mykey,const char *myname,const char *mysecret,const char *mypsecret) { int ret = -1; aos_mutex_lock(&g_cmd_mutex,AOS_WAIT_FOREVER); atparser_clr_buf(g_atparser_uservice_t); if (atparser_send(g_atparser_uservice_t, "AT+IDMAU=\"%s\",\"%s\",\"%s\",\"%s\"", mykey, myname, mysecret, mypsecret) == 0) { if (atparser_recv(g_atparser_uservice_t, "OK\n") == 0) { ret = 0; } else { printf("Destination Host Unreachable!\r\n"); } } atparser_cmd_exit(g_atparser_uservice_t); if (ret == 0) { printf("key = %s name = %s secret = %s psecret = %s!\r\n", mykey, myname, mysecret, mypsecret); } aos_mutex_unlock(&g_cmd_mutex); return ret; } int w800_living_idmcon(void) { int ret = -1; aos_mutex_lock(&g_cmd_mutex,AOS_WAIT_FOREVER); atparser_clr_buf(g_atparser_uservice_t); if (atparser_send(g_atparser_uservice_t, "AT+IDMCON") == 0) { if (atparser_recv(g_atparser_uservice_t, "OK\n") == 0) { ret = 0; } else { printf("Destination Host Unreachable!\r\n"); } } atparser_cmd_exit(g_atparser_uservice_t); if (ret == 0) { printf("AT+IDMCON \r\n"); } aos_mutex_unlock(&g_cmd_mutex); return ret; } void w800_living_recv_callback_register(const char *cmd, void *callback, void *context) {     atparser_oob_create(g_atparser_uservice_t, cmd, callback, context); } int w800_living_send_attribute(const char *dev_id, const char *msg) {     int ret = -1;          if (!dev_id || !msg) {         return ret;     }     aos_mutex_lock(&g_cmd_mutex, AOS_WAIT_FOREVER);     atparser_clr_buf(g_atparser_uservice_t);     printf("Send msg: %s\r\n", msg);     if (atparser_send(g_atparser_uservice_t, "AT+IDMPP=0,\"%s\"", msg) == 0) {         if (atparser_recv(g_atparser_uservice_t, "OK\n") == 0) {             ret = 0;             printf("Send at cmd ok\n");         }     } else {         printf("Send at cmd err\n");     }     atparser_cmd_exit(g_atparser_uservice_t);     aos_mutex_unlock(&g_cmd_mutex);     return ret; } ``` 这里,物模型属性设置回调注册接口的实现采用了非侵入的方式,以尽量减少对原代码的修改。所以,这就需要接口调用者,在应用代码中明确地指定AT指令的代码。通常来说,更好的实现方式是通过消息机制来实现,但是这需要定义唯一的、不冲突的消息编号,并且在w800\_module\_init函数体中增加回调注册代码,侵入性太大,所以并没有选择这样的实现方式。 ### 智能灯平台交互的封装 对于智能灯与平台之间的交互逻辑,我们可以新建代码来封装实现。在C语言中,为方便接口函数的调用,我们需要先新建一个头文件—— app\_living.h 。 ```c++ #ifndef __APP_LIVING_H__ #define __APP_LIVING_H__ #include #ifdef __cplusplus extern "C" { #endif #define EVENT_LIVING_ATTR_POWER       (EVENT_USER + 1) #define EVENT_LIVING_ATTR_BRIGHTNESS  (EVENT_USER + 2) #define EVENT_LIVING_ATTR_COLOR       (EVENT_USER + 3) typedef struct RgbColor {     unsigned char r;     unsigned char g;     unsigned char b; } RgbColor; void update_attr(uint8_t powerstate, uint8_t bright, RgbColor rgb); int connect_iot_demo(void); #ifdef __cplusplus } #endif #endif /* __APP_LIVING_H__ */ ``` 然后,新建app\_living.c源文件来实现代码逻辑。为了解析从平台发送的JSON格式消息,我们引入了cJSON模块。 ```c++ #include #include #include #include #include #include "cJSON.h" #include "app_living.h" #define TAG "app_living" extern int w800_living_idmau(const char *mykey,const char *myname,const char *mysecret,const char *mypsecretconst); extern int w800_living_idmcon(void); extern void w800_living_recv_callback_register(const char *cmd, void *callback, void *context); extern int w800_living_send_attribute(const char *dev_id, const char *msg); void update_attr(uint8_t powerstate, uint8_t bright, RgbColor rgb) { printf("enter update \n"); const char *dev_id = "0"; char msg[128] = {0}; const char *msg_format = "{\\\"powerstate\\\":%d,\\\"brightness\\\":%d,\\\"RGBColor\\\":{\\\"Red\\\":%d,\\\"Green\\\":%d,\\\"Blue\\\":%d}}"; sprintf(msg, msg_format, powerstate, bright, rgb.r,rgb.g,rgb.b); w800_living_send_attribute(dev_id, msg); } static int parse_living_msg(const char *msg) {     cJSON *root = NULL;     /* Parse Root */     root = cJSON_Parse(msg);     if (root == NULL || !cJSON_IsObject(root)) {         printf("JSON Parse Error\n");         return -1;     }     cJSON *item = cJSON_GetObjectItem(root, "powerstate"); static uint8_t power_on;     if (item && cJSON_IsNumber(item)) {         if (item->valueint) { power_on = 1;         } else { power_on = 0;         } event_publish(EVENT_LIVING_ATTR_POWER, &power_on);     }     item = cJSON_GetObjectItem(root, "brightness"); static uint8_t bright;     if (item && cJSON_IsNumber(item)) { bright = item->valueint; event_publish(EVENT_LIVING_ATTR_BRIGHTNESS, &bright);     } item = cJSON_GetObjectItem(root, "RGBColor"); static RgbColor rgb; if (item && cJSON_IsObject(item)) { cJSON *sub_item = cJSON_GetObjectItem(item, "Red"); if (sub_item && cJSON_IsNumber(sub_item)) { rgb.r = sub_item->valueint; } sub_item = cJSON_GetObjectItem(item, "Green"); if (sub_item && cJSON_IsNumber(sub_item)) { rgb.g = sub_item->valueint; } sub_item = cJSON_GetObjectItem(item, "Blue"); if (sub_item && cJSON_IsNumber(sub_item)) { rgb.b = sub_item->valueint; } event_publish(EVENT_LIVING_ATTR_COLOR, &rgb); }     cJSON_Delete(root);     return 0; } static int living_set_attr_callback(atparser_uservice_t *at, void *priv, oob_data_t *oob_data) { int did = 0; int len = 0; char msg[128] = {0}; char *str = strchr(oob_data->buf, ':'); if (str != NULL) { sscanf(oob_data->buf, "%d,%d,%s\r\n", &did, &len, msg); LOGD(TAG,"==>recv data %d(%d):%s\r\n",did, len, msg); parse_living_msg(msg); oob_data->used_len = len; }     return 0; } int connect_iot_demo(void) { char *my_key = "a1AMULi68xV";//ProductKey char *my_name = "RVB2601GeekHoloLed1";//DeviceName char *my_secret = "fcdf55e206b907d649e2249aed8c948a";//DeviceSecret char *my_p_secret = "BReZtzPVrLcdY1H4";//Product Secret int ret2 = -1; int ret3 = -1; w800_living_recv_callback_register("+IDMPS:", living_set_attr_callback, NULL); ret2 = w800_living_idmau(my_key,my_name,my_secret,my_p_secret); if (ret2 == 0){ printf("AT+IDMAU:OK!\n"); } else { printf("AT+IDMAU:ERROR!\n"); } ret3 = w800_living_idmcon(); if (ret3 == 0){ printf("AT+IDMCON:OK!\n"); } else { printf("AT+IDMCON:ERROR!\n"); } if(ret2 == 0 && ret3 == 0){ return 0; }else{ return -1; } } ``` > 注意替换上面代码中connect\_iot\_demo函数使用的设备五元组信息。 ### LED颜色控制实现 为了控制亮度,我们也需要对上一节中的LED控制代码进行改造,具体代码如下。 ```c++ /*********************  *      INCLUDES  *********************/ #define _DEFAULT_SOURCE /* needed for usleep() */ #include #include #include #include #include #include "app_config.h" #include "app_main.h" #include "csi_config.h" #include "board_config.h" #include "drv/gpio_pin.h" #include #include #ifdef CONFIG_PWM_MODE static csi_pwm_t  r; void led_pinmux_init() {         //7     csi_error_t ret;     csi_pin_set_mux(PA7, PA7_PWM_CH7);     csi_pin_set_mux(PA25, PA25_PWM_CH2);     csi_pin_set_mux(PA4, PA4_PWM_CH4);     ret = csi_pwm_init(&r, 0);     if (ret != CSI_OK) {             printf("===%s, %d\n", __FUNCTION__, __LINE__);             return ;     } } void rgb_light(uint32_t red, uint32_t green, uint32_t blue, uint8_t brightness) { csi_error_t ret; ret = csi_pwm_out_config(&r, 7 / 2, 300, red*300*brightness/100/255, PWM_POLARITY_HIGH);     if (ret != CSI_OK) {             printf("===%s, %d\n", __FUNCTION__, __LINE__);             return ;     }     ret = csi_pwm_out_start(&r, 7 / 2);     if (ret != CSI_OK) {             printf("===%s, %d\n", __FUNCTION__, __LINE__);             return ;     }         //25     ret = csi_pwm_out_config(&r, 2 / 2, 300, green*300*brightness/100/255, PWM_POLARITY_HIGH);     if (ret != CSI_OK) {             printf("===%s, %d\n", __FUNCTION__, __LINE__);             return ;     }     ret = csi_pwm_out_start(&r, 2 / 2);     if (ret != CSI_OK) {             printf("===%s, %d\n", __FUNCTION__, __LINE__);             return ;     }         //4     ret = csi_pwm_out_config(&r, 4 / 2, 300, blue*300*brightness/100/255, PWM_POLARITY_HIGH);     if (ret != CSI_OK) {             printf("===%s, %d\n", __FUNCTION__, __LINE__);             return ;     }     ret = csi_pwm_out_start(&r, 4 / 2);     if (ret != CSI_OK) {             printf("===%s, %d\n", __FUNCTION__, __LINE__);             return ;     } } #endif  #ifdef CONFIG_GPIO_MODE static uint32_t g_ctr = 0; static csi_gpio_pin_t r; static csi_gpio_pin_t g; static csi_gpio_pin_t b; void led_pinmux_init() {     csi_pin_set_mux(PA7, PIN_FUNC_GPIO);     csi_pin_set_mux(PA25, PIN_FUNC_GPIO);     csi_pin_set_mux(PA4, PIN_FUNC_GPIO);     csi_gpio_pin_init(&r, PA7);     csi_gpio_pin_dir(&r, GPIO_DIRECTION_OUTPUT); csi_gpio_pin_mode(&r, GPIO_MODE_PUSH_PULL);     csi_gpio_pin_init(&g, PA25);     csi_gpio_pin_dir(&g, GPIO_DIRECTION_OUTPUT); csi_gpio_pin_mode(&g, GPIO_MODE_PUSH_PULL);     csi_gpio_pin_init(&b, PA4);     csi_gpio_pin_dir(&b, GPIO_DIRECTION_OUTPUT); csi_gpio_pin_mode(&b, GPIO_MODE_PUSH_PULL);     g_ctr = 0; } //fake rgb, because of only high or low state of gpio void rgb_light(uint32_t red, uint32_t green, uint32_t blue) { (red < 50)?csi_gpio_pin_write(&r, GPIO_PIN_LOW):csi_gpio_pin_write(&r, GPIO_PIN_HIGH); (green < 50)?csi_gpio_pin_write(&r, GPIO_PIN_LOW):csi_gpio_pin_write(&g, GPIO_PIN_HIGH); (blue < 50)?csi_gpio_pin_write(&r, GPIO_PIN_LOW):csi_gpio_pin_write(&b, GPIO_PIN_HIGH); } #endif ``` ### 继电器状态获取实现 为了获取继电器状态,也就是LED灯的开关状态,我们同样需要对继电器代码进行改造。具体代码如下。 ```c++ /*********************  *      INCLUDES  *********************/ #define _DEFAULT_SOURCE /* needed for usleep() */ #include #include #include #include #include #include "app_config.h" #include "csi_config.h" #include "app_main.h" #include "board_config.h" #include "drv/gpio_pin.h" #include static csi_gpio_pin_t relay; unsigned char get_state() { return csi_gpio_pin_read(&relay); }  void relay_pinmux_init()  { csi_pin_set_mux(PA26, PIN_FUNC_GPIO); csi_gpio_pin_init(&relay, PA26);     csi_gpio_pin_dir(&relay, GPIO_DIRECTION_OUTPUT); } void relay_toggle(bool on) { if(on) { csi_gpio_pin_write(&relay, GPIO_PIN_HIGH); }  else  { csi_gpio_pin_write(&relay, GPIO_PIN_LOW); } } ``` ### 网络初始化实现 在编写应用的主逻辑之前,我们需要先对RVB2601开发板进行初始化。其中,网络初始化是我们为实现联网新增的代码逻辑。 网络初始化分为两步。第一步是初始化W800模块:在设置好GPIO口、波特率和缓冲大小后,通过调用wifi\_w800\_register函数初始化;第二步是配置网络管理器(netmgr)模块:这里你需要将netmgr\_config\_wifi函数中的入参替换成自己的Wi-Fi网络SSID和密码,并且注意修改入参中的数字为SSID字符串和密码字符串的长度。 ```c++ #include #include #include #include #include #include #include #include #include "app_main.h" #include "board.h" #define TAG "init" netmgr_hdl_t app_netmgr_hdl; extern at_channel_t spi_channel; static void network_init() {     w800_wifi_param_t w800_param;     /* init wifi driver and network */     w800_param.reset_pin      = PA21;     w800_param.baud           = 1*1000000;     w800_param.cs_pin         = PA15;     w800_param.wakeup_pin     = PA25;     w800_param.int_pin        = PA22;     w800_param.channel_id     = 0;     w800_param.buffer_size    = 4*1024;     wifi_w800_register(NULL, &w800_param);     app_netmgr_hdl = netmgr_dev_wifi_init();     if (app_netmgr_hdl) {         utask_t *task = utask_new("netmgr", 2 * 1024, QUEUE_MSG_COUNT, AOS_DEFAULT_APP_PRI);         netmgr_service_init(task);         netmgr_config_wifi(app_netmgr_hdl, "你的wifi SSID", 11, "你的wifi AP密码", 11);         netmgr_start(app_netmgr_hdl);     } } void board_yoc_init(void) {     board_init();     event_service_init(NULL);     console_init(CONSOLE_UART_IDX, 115200, 512);     ulog_init();     aos_set_log_level(AOS_LL_DEBUG);     int ret = partition_init();     if (ret <= 0) {         LOGE(TAG, "partition init failed");     } else {         LOGI(TAG, "find %d partitions", ret);     }     aos_kv_init("kv");     network_init();     board_cli_init(); } ``` ### 智能灯主逻辑实现 在完成了W800驱动程序的增补、平台交互功能的封装以及LED模块、继电器代码的改造等一系列准备之后,我们就可以编写智能灯的主逻辑了。智能灯的主逻辑在app\_main.c文件中实现。 主逻辑包含几个模块:首先是初始化开发板、LED灯和继电器模块;然后是注册网络管理器事件的回调函数,和注册我们在平台交互模块中定义的属性设置事件的回调函数;最后,就是在while循环中建立物联网平台连接,并定期上报智能灯状态。 我们看一下具体的代码: ```c++ #include #include #include #include #include #include #include #include #include "drv/gpio_pin.h" #include #include #include "app_living.h" #include "app_main.h" #define TAG "APP" static bool g_wifi_ok; static uint8_t led_brightness; static RgbColor led_color; static void led_control(uint8_t power) { relay_toggle(power); } static void led_set_brightness(uint8_t bright) { led_brightness = bright; rgb_light(led_color.r, led_color.g, led_color.b, bright); } static void led_set_color(RgbColor color) { led_color.r = color.r; led_color.g = color.g; led_color.b = color.b; rgb_light(color.r, color.g, color.b, led_brightness); } static void living_event(uint32_t event_id, const void *param, void *context) { switch(event_id) {     case EVENT_LIVING_ATTR_POWER:         printf("set attr power:%d\n", *(uint8_t *)param); led_control(*(uint8_t *)param);         break;     case EVENT_LIVING_ATTR_BRIGHTNESS:         printf("set attr bright:%d\n", *(uint8_t *)param); led_set_brightness(*(uint8_t *)param);         break; case EVENT_LIVING_ATTR_COLOR: printf("set attr color\n"); led_set_color(*(RgbColor *)param); break;    }     /*do exception process */     app_exception_event(event_id); } static void network_event(uint32_t event_id, const void *param, void *context) {     switch(event_id) {     case EVENT_NETMGR_GOT_IP:         LOGD(TAG, "net got ip"); g_wifi_ok = true;         break;     case EVENT_NETMGR_NET_DISCON:         LOGD(TAG, "net disconnect");         break;    }     /*do exception process */     app_exception_event(event_id); } int main(void) { uint32_t time_cnt = 0; bool mqtt_conn = false;     board_yoc_init(); led_pinmux_init(); relay_pinmux_init(); led_color.r = 255; led_color.g = 255; led_color.b = 0; led_brightness = 100; rgb_light(led_color.r, led_color.g, led_color.b, led_brightness); relay_toggle(true);     /* Subscribe */     event_subscribe(EVENT_NETMGR_GOT_IP, network_event, NULL);     event_subscribe(EVENT_NETMGR_NET_DISCON, network_event, NULL); event_subscribe(EVENT_LIVING_ATTR_POWER, living_event, NULL); event_subscribe(EVENT_LIVING_ATTR_BRIGHTNESS, living_event, NULL); event_subscribe(EVENT_LIVING_ATTR_COLOR, living_event, NULL); while(1){ if (g_wifi_ok) { int ret = connect_iot_demo(); if (ret == 0){ printf("connerct iot success"); mqtt_conn = true; }else{ printf("connerct iot error"); } g_wifi_ok = false; } if (mqtt_conn && time_cnt >= 10) { update_attr(get_state(), led_brightness, led_color); time_cnt = 0; } time_cnt += 1; aos_msleep(500); }   } ``` 最后,主逻辑app\_main.c的头文件内容如下,供你参考。其中包含了硬件初始化接口函数,和LED模块、继电器功能接口函数的声明,以便源代码引用。 ```c++ /*  * Copyright (C) 2019-2020 Alibaba Group Holding Limited  */ #ifndef _APP_MAIN_H_ #define _APP_MAIN_H_ #include #include void board_cli_init(); #include extern netmgr_hdl_t app_netmgr_hdl; void app_exception_event(uint32_t event_id); void board_yoc_init(void); void led_pinmux_init(); void rgb_light(uint32_t red, uint32_t green, uint32_t blue, uint8_t brightness); void relay_pinmux_init(); void relay_toggle(bool on); unsigned char get_state(); #endif ``` ## 设备调试 在完成代码编写后,我们依然按照上一节中步骤,编译——烧录——运行,让智能灯开始工作,并接入物联网平台。 这时,我们就可以对智能灯进行在线调试了。打开阿里云生活物联网平台的设备调试页面后,我们点击测试设备条目中的“调试”操作,就会进入在线调试页面。 在调试页面中,我们可以选择调试功能“开关”,方法选择“设置”。下面的消息框中会自动根据物模型准备好JSON格式的消息体。 ![](https://static001.geekbang.org/resource/image/df/7d/df6yy6b7f1d09657cda991e58d1b507d.png?wh=1076x1300) 点击“发送指令”后,这个属性设置消息就会发送到智能灯,实现对智能灯的控制。当然,你也可以选择其他的属性进行设备测试。 另外,我们还可以在公版云智能App中测试、使用已接入平台的智能灯。这里要怎么实现公版云智能App的控制呢?你需要进入“批量投产”页面,然后点击“配网+App下载二维码”,根据提示下载云智能App到手机。接着,点击“产品发布”完成产品上线。 ![](https://static001.geekbang.org/resource/image/39/49/395747e61c1ebf089810764b9b2bdd49.png?wh=2036x1154) 完成这些准备工作后,你就可以在云智能App中添加我们的测试设备了。这里需要注意的是,要保证App和智能灯设备都连接到同一个Wi-Fi网络中,否则,云智能App是不能发现智能灯设备的。 App上的具体展示内容如下: ![](https://static001.geekbang.org/resource/image/06/0d/063f63945f3174f047815bde4c22490d.jpg?wh=1142x795) ## 小结 到这里,我们就完成了智能灯的联网控制开发任务。 在实验中,我们使用的物联网平台是阿里云生活物联网平台,整体的创建流程与第17讲的平台类似。重点是物模型的定义和人机交互界面的设计。 在智能灯的联网控制开发中,我们使用W800模组提供的AT指令来实现平台的交互。AT指令是通信领域常用的控制协议,在嵌入式领域也有广泛的应用,你可以基于本实验对它进行扩展学习。在连接Wi-Fi网络时,我们会使用到YoC嵌入式系统平台提供的网络管理器模块。关于YoC是什么,和模块的关系又是什么,我会在下一节详细讲解。 ## 思考题 最后,我给你留一个思考题。你可能注意到源代码中有些函数的前面有static关键字,有些函数前面没有这个关键字。比如函数parse\_living\_msg前面有static,这是为什么呢?欢迎你在评论区写一下自己的理解,也欢迎你将这一节分享给你的朋友,大家一起交流学习。