实战二 MQTT开发:如何实现联网控制?
你好,我是郭朝斌。
在上一节中,我们基于平头哥RVB2601开发板完成了智能电灯硬件的搭建和嵌入式应用的开发,但是打造一款物联网设备,我们还需要将硬件接入物联网平台。接下来,我就来讲解一下RVB2601开发板通过MQTT协议接入阿里云生活物联网平台的流程及方法。
在开始本节内容的阅读之前,你可以重新打开第17讲,了解一下Python语言的实现代码。对比着本节的C语言代码,你将会对程序开发有更深入的理解。
生活物联网平台的准备工作
阿里云生活物联网平台,又称为飞燕平台,是面向消费级产品的物联网开放平台。它具备完整的、面向家居物联网场景的功能定义,可以非常方便地完成智能设备的物联网接入工作。
接下来,我们就在这个平台上完成智能灯的联网控制实验。
创建项目和产品
首先,登录生活物联网平台,我们进行第一个项目的创建。项目的名称,我们可以填写“智能电灯”。对于项目类型,你可以根据产品需求来决定,因为我们不计划接入天猫精灵生态,所以这里选择“自有品牌项目”。
接着,我们为这个“智能电灯”项目创建一个新产品“Led_1”。
产品的参数可以这样设置:
- 所属品类,选择“电工照明”–>“灯”。
- 节点设备,选择“设备”。是否接入网关,选择“否”。
- 连网方式,选择“WiFi”。
- 数据格式,选择“ICA标准数据格式(Alink JSON)”。
产品功能定义
创建完产品,我们进入产品的研发流程。物联网平台把流程分为4个阶段,分别是:功能定义、人机交互、设备调试和批量投产。
首先,我们来完成功能定义的部分,也就是物联网设备的物模型定义。
基于创建产品时我们选择的产品类型和数据格式,平台已经为智能电灯自动生成了一个标准的物模型。针对你开发的智能灯的功能需求,你可以对各项功能进行编辑、删除,或者新增标准模版没有的功能。比如像我这里展示的一样,保留“开关”、“亮度”和“色温”,删除其他功能项,同时增加“RGB调色”功能。“RGB调色”功能项,对应了我们智能灯的三色LED模块。
人机交互设计
完成功能定义后,我们进入下一步,人机交互。在人机交互中,我们主要完成配网方式和手机App相关界面的设计。
首先,我们选择使用公版App控制产品。这样可以省掉我们开发独立App的工作。
在“产品展示”标签页中,设置一下产品名称。
在“设备面板”中,你可以点击进入“选择面板”页面,选择一个智能灯在App上的展示和操作界面。因为默认面板中没有适配“RGB调色”的面板,所以,你需要编辑一下“灯泡冷暖灯”模版来替代使用。否则,平台会显示错误信息,提示面板与物模型的属性定义不一致。
配网方式,我们保持默认设置即可。在“自动化与定时”标签页中,我们要勾选“开关”的“作为执行”选项。这样,在自动化场景的创建中,智能电灯的开关就可以作为执行动作起到控制的效果了。
设备调试设置
在设备调试页面中,我们需要先选择产品使用的芯片或者模组。对于我们的实验项目,这里直接选择列表最后一项——未知芯片即可。
然后,我们新建一个测试设备。因为我们需要获得一个设备证书,也就是智能灯连接物联网平台的五元组信息。
点击“新建测试设备”,你需要为测试设备输入一个名称,比如,可以是“RVB2601HoloLed1”。然后,点击“确定”,页面就会新增一个设备条目。
在新增设备条目中,点击“设备证书”,你就可以看到设备五元组信息。这里要记得复制、保存这些字符串,因为我们在后面的应用代码中需要用到。
你可以看到,这个新增测试设备的状态显示“未激活”。因为只有当设备通过MQTT协议第一次连接到物联网平台后,这个测试设备才会被激活,并且可以发送消息进行在线调试。
智能灯如何接入物联网
那么,智能灯如何接入物联网平台实现后续的调试、使用呢?下面,我们来开发一下智能灯的联网控制功能。
联网功能开发
RVB2601开发板中的W800模组提供了Wi-Fi和BLE通信能力,而且模组还集成了连接阿里云生活物联网平台(飞燕)的功能。主控芯片CH2601通过SPI接口与W800模组通信,它只需要发送/接收W800定义的AT指令,就可以实现相应的功能。
W800模组的AT指令集可以参考官方文档。我们就基于文档中飞燕平台的相关AT指令来实现与平台的通信。它的底层实现依然是MQTT协议,不过封装成了AT指令的接口形式。
这里,我们就需要修改W800的驱动代码,增加联网接口函数,其中具体包括:
- 设置设备五元组接口
- 建立MQTT连接接口
- 物模型属性设置回调注册接口
- 物模型属性上报接口
具体要怎么做呢?
我们需要在项目中引入W800的驱动模块drv_wifi_at_w800。在CDK中,点击右键打开“Packages for Led”,在模块窗口左侧找到drv_wifi_at_w800模块,点击箭头导入右侧列表中。
在W800的驱动模块drv_wifi_at_w800中,打开w800_api.h文件,增加函数接口定义。
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文件中,增加函数接口的实现代码。
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 。
#ifndef __APP_LIVING_H__
#define __APP_LIVING_H__
#include <uservice/eventid.h>
#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模块。
#include <stdlib.h>
#include <string.h>
#include <aos/debug.h>
#include <devices/w800.h>
#include <yoc/atparser.h>
#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控制代码进行改造,具体代码如下。
/*********************
* INCLUDES
*********************/
#define _DEFAULT_SOURCE /* needed for usleep() */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <aos/aos.h>
#include "app_config.h"
#include "app_main.h"
#include "csi_config.h"
#include "board_config.h"
#include "drv/gpio_pin.h"
#include <drv/pin.h>
#include <drv/pwm.h>
#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灯的开关状态,我们同样需要对继电器代码进行改造。具体代码如下。
/*********************
* INCLUDES
*********************/
#define _DEFAULT_SOURCE /* needed for usleep() */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <aos/aos.h>
#include "app_config.h"
#include "csi_config.h"
#include "app_main.h"
#include "board_config.h"
#include "drv/gpio_pin.h"
#include <drv/pin.h>
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字符串和密码字符串的长度。
#include <stdbool.h>
#include <aos/kv.h>
#include <yoc/partition.h>
#include <yoc/init.h>
#include <drv/pin.h>
#include <yoc/at_port.h>
#include <devices/w800.h>
#include <devices/drv_snd_alkaid.h>
#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循环中建立物联网平台连接,并定期上报智能灯状态。
我们看一下具体的代码:
#include <stdlib.h>
#include <string.h>
#include <aos/list.h>
#include <aos/debug.h>
#include <uservice/uservice.h>
#include <uservice/eventid.h>
#include <yoc/sysinfo.h>
#include <board.h>
#include "drv/gpio_pin.h"
#include <drv/pin.h>
#include <drv/pwm.h>
#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模块、继电器功能接口函数的声明,以便源代码引用。
/*
* Copyright (C) 2019-2020 Alibaba Group Holding Limited
*/
#ifndef _APP_MAIN_H_
#define _APP_MAIN_H_
#include <uservice/uservice.h>
#include <yoc/netmgr_service.h>
void board_cli_init();
#include <stdint.h>
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格式的消息体。
点击“发送指令”后,这个属性设置消息就会发送到智能灯,实现对智能灯的控制。当然,你也可以选择其他的属性进行设备测试。
另外,我们还可以在公版云智能App中测试、使用已接入平台的智能灯。这里要怎么实现公版云智能App的控制呢?你需要进入“批量投产”页面,然后点击“配网+App下载二维码”,根据提示下载云智能App到手机。接着,点击“产品发布”完成产品上线。
完成这些准备工作后,你就可以在云智能App中添加我们的测试设备了。这里需要注意的是,要保证App和智能灯设备都连接到同一个Wi-Fi网络中,否则,云智能App是不能发现智能灯设备的。
App上的具体展示内容如下:
小结
到这里,我们就完成了智能灯的联网控制开发任务。
在实验中,我们使用的物联网平台是阿里云生活物联网平台,整体的创建流程与第17讲的平台类似。重点是物模型的定义和人机交互界面的设计。
在智能灯的联网控制开发中,我们使用W800模组提供的AT指令来实现平台的交互。AT指令是通信领域常用的控制协议,在嵌入式领域也有广泛的应用,你可以基于本实验对它进行扩展学习。在连接Wi-Fi网络时,我们会使用到YoC嵌入式系统平台提供的网络管理器模块。关于YoC是什么,和模块的关系又是什么,我会在下一节详细讲解。
思考题
最后,我给你留一个思考题。你可能注意到源代码中有些函数的前面有static关键字,有些函数前面没有这个关键字。比如函数parse_living_msg前面有static,这是为什么呢?欢迎你在评论区写一下自己的理解,也欢迎你将这一节分享给你的朋友,大家一起交流学习。
- LDxy 👍(1) 💬(0)
static函数仅可以在本文件中使用,不是static的函数除了能够在本文件中使用,还能在其他文件中使用
2022-05-03 - 种花家 👍(0) 💬(1)
老师,reb2601 咋么实现4G/5G联网,以及不在同一wifi 下手机控制灯呢?
2022-04-27