IoT Pi SDDC 设备开发
本章将介绍如何在 IoT Pi 上使用 MS-RTOS 和 libsddc 开发一个能接入到 EdgerOS 的 Wi-Fi 物联网设备。
软件准备
IoT Pi 开发环境搭建
参考《IoT Pi 快速入门》,完成以下步骤:
完成 MS-RTOS 开发工具下载和安装
在 MS-RTOS 云开发平台上完成
msrtos_base_sdk
配置和下载, 配置 MS-RTOS 时,需要手动勾选 libmsdriver、esp_at_net、fatfs、libsddc、mbedtls、cjson 这几个组件,如下图所示:在 IoT studio 上完成
msrtos_base_sdk
工程导入和编译在 IoT studio 上完成
bspstm32f4xx
工程的下载和导入,如下图所示:
工程配置
bspstm32f4xx 配置
Wi-Fi AP 连接模式配置
bspstm32f4xx/src/board/IOT_PI/iot_pi_cfg.h
文件为 bspstm32f4xx
工程的 IoT Pi 开发板的配置头文件,与 IoT Pi 连接 Wi-Fi AP 相关的配置宏如下:
宏 | 含义 |
---|---|
BSP_ESP8266_AUTO_JOIN | 自动连接模式,尝试连接上一次连接的 Wi-Fi AP |
BSP_ESP8266_SMART_CFG | SmartConfig 模式 |
BSP_ESP8266_MANUAL_CFG | 手动连接模式,连接 ap_list[] 中指定的 Wi-Fi AP |
BSP_CFG_ESP8266_MODE | 连接模式, 以上三种连接模式的组合,默认模式为自动连接模式 |
Wi-Fi AP 列表配置
bspstm32f4xx/src/board/IOT_PI/iot_pi_init.c
为 IoT Pi 开发板的初始化源文件,在此源文件中的 ap_list[]
变量用于指定手动连接模式下尝试连接到的 Wi-Fi AP 列表:
/**
* WiFi AP list.
*/
static const ms_esp_at_net_ap_t ap_list[] = {
{ "EOS-00000F", "123456789" }, // Spirit 1 的 Wi-Fi AP SSID 与密码
};
libsddc 配置
msrtos_base_sdk/libsddc/src/sddc_config.h
文件为 libsddc
工程的配置头文件,内容如下:
#define SDDC_CFG_PORT 680U // EdgerOS 端口
#define SDDC_CFG_RECV_BUF_SIZE 1460U // 接收缓冲区大小
#define SDDC_CFG_SEND_BUF_SIZE 1460U // 发送缓冲区大小
#define SDDC_CFG_NET_IMPL "ms_esp_at_net" // MS-RTOS 网络实现名字
#define SDDC_CFG_MQUEUE_SIZE 6U // 消息队列大小
#define SDDC_CFG_RETRIES_INTERVAL 500U // 消息重传时间间隔,单位:毫秒
#define SDDC_CFG_EDGEROS_ALIVE 40U // EdgerOS 保活时间,单位:消息重传时间间隔
#define SDDC_CFG_CONNECTOR_TIMEOUT 5000U // 数据连接器接收超时时间,单位:毫秒
#define SDDC_CFG_DBG_EN 1U // 是否使能调试信息
#define SDDC_CFG_WARN_EN 1U // 是否使能警告信息
#define SDDC_CFG_ERR_EN 1U // 是否使能出错信息
#define SDDC_CFG_CRIT_EN 1U // 是否使能临界信息
#define SDDC_CFG_INFO_EN 1U // 是否使能打印信息
#define SDDC_CFG_SECURITY_EN 1U // 是否使能数据加密通信
#undef __FREERTOS__ // 使用 FreeRTOS 时定义
工程编译
bspstm32f4xx 编译
选中 bspstm32f4xx
工程,点击编译按钮,将编译 bspstm32f4xx
工程,编译完成后,会在 Debug
目录生成 bspiotpi.bin
文件:
libsddc 编译
选中 msrtos_base_sdk
工程,点击编译按钮,将编译 msrtos_base_sdk
工程的组件,编译完成后,会在 libsddc/Debug/sddc_examples/src/example/demo
目录生成 sddc_demo.bin
文件:
验证功能
烧写镜像
使用 MS-RTOS AutoTester 烧写镜像,请参考《IoT Pi 快速入门》完成 bspiotpi.bin
和 sddc_demo.bin
镜像烧写,注意不同的镜像需要烧写到不同的地址,如下表所示:
镜像 | 烧写地址 |
---|---|
bspiotpi.bin | 0x08000000 |
sddc_demo.bin | 0x08040000 |
IoT Pi 连接 EdgerOS
按下 IoT Pi 开发板的 RESET 按键,MS-RTOS 操作系统启动后,将自动运行 0x08040000 地址处的 SDDC demo 程序:
如果在前面配置 bspstm32f4xx
工程时,将 Wi-Fi AP 连接模式配置为手动连接模式,并配置了正确的 Spirit 1 的 Wi-Fi AP SSID 和密码,IoT Pi 开发板将能成功连接 Spirit 1 的 Wi-Fi 网络,接下来请参考《SDDC introduction》章节的“添加设备流程”去添加 IoT Pi 开发板。
IoT Pi SmartConfig
如果在前面配置 bspstm32f4xx
工程时,未配置正确的 Spirit 1 的 Wi-Fi AP SSID 和密码,或在后续使用过程中修改了 Spirit 1 的 Wi-Fi AP SSID 或密码,则可以通过 SmartConfig 方式令 IoT Pi 开发板重新加入 Spirit 1 的 Wi-Fi 网络,操作方式请参考《SDDC introduction》章节的“SmartConfig 流程”。
EdgerOS 安装 IoT Pi App
打开爱智世界:
搜索 IoT Pi App:
点击获取安装 IoT Pi App。
安装 IoT Pi App:
等待 IoT Pi App 下载和安装完成。
给 IoT Pi App 分配设备权限
打开设置:
点击隐私设置:
点击设备权限:
选择 IoT Pi 开发板:
给 IoT Pi App 分配设备权限:
退出设置。
使用 IoT Pi App 控制 IoT Pi 开发板
打开 IoT Pi App:
点击 IoT Pi 开发板,进入 IoT Pi 控制界面。
控制 IoT Pi 开发板:
libsddc demo 代码解析
libsddc demo 代码位于 msrtos_base_sdk/libsddc/src/example/demo/sddc_demo.c
:
#include <ms_rtos.h>
#include "sddc.h"
#include "cJSON.h"
static int led1_fd; // LED1 文件描述符
static int led2_fd; // LED2 文件描述符
static int led3_fd; // LED3 文件描述符
static int key1_fd; // KEY1 文件描述符
static int key2_fd; // KEY2 文件描述符
static int key3_fd; // KEY3 文件描述符
static int sockfd; // socket 文件描述符
static sddc_bool_t led_state_bak[3]; // LED 状态备份
// 报告 IoT Pi LED 状态
static void iot_pi_led_state_report(sddc_t *sddc, const uint8_t *uid, sddc_bool_t *led_state)
{
cJSON *root;
char *str;
// 构建 LED 状态消息
root = cJSON_CreateObject();
sddc_return_value_if_fail(root, -1);
if (led_state != MS_NULL) {
cJSON_AddBoolToObject(root, "led1", led_state[0]);
cJSON_AddBoolToObject(root, "led2", led_state[1]);
cJSON_AddBoolToObject(root, "led3", led_state[2]);
} else {
cJSON_AddBoolToObject(root, "led1", led_state_bak[0]);
cJSON_AddBoolToObject(root, "led2", led_state_bak[1]);
cJSON_AddBoolToObject(root, "led3", led_state_bak[2]);
}
str = cJSON_Print(root);
sddc_goto_error_if_fail(str);
// 发送 LED 状态消息
sddc_send_message(sddc, uid, str, strlen(str), 1, MS_FALSE, MS_NULL);
cJSON_free(str);
cJSON_Delete(root);
return 0;
error:
cJSON_Delete(root);
return -1;
}
// 处理 MESSAGE 报文
static sddc_bool_t iot_pi_on_message(sddc_t *sddc, const uint8_t *uid, const char *message, ms_size_t len)
{
// 分析消息
cJSON *root = cJSON_Parse(message);
cJSON *led;
sddc_bool_t led_state[3];
char *str;
sddc_return_value_if_fail(root, SDDC_TRUE);
memcpy(led_state, led_state_bak, sizeof(led_state_bak));
// 打印消息
str = cJSON_Print(root);
sddc_goto_error_if_fail(str);
sddc_printf("iot_pi_on_message: %s\n", str);
cJSON_free(str);
led = cJSON_GetObjectItem(root, "led1");
if (cJSON_IsBool(led)) {
// 控制 LED1
if (cJSON_IsTrue(led)) {
ms_uint8_t on = 0;
ms_io_write(led1_fd, &on, 1);
led_state[0] = 1;
} else {
ms_uint8_t off = 1;
ms_io_write(led1_fd, &off, 1);
led_state[0] = 0;
}
}
led = cJSON_GetObjectItem(root, "led2");
if (cJSON_IsBool(led)) {
// 控制 LED2
if (cJSON_IsTrue(led)) {
ms_uint8_t on = 0;
ms_io_write(led2_fd, &on, 1);
led_state[1] = 1;
} else {
ms_uint8_t off = 1;
ms_io_write(led2_fd, &off, 1);
led_state[1] = 0;
}
}
led = cJSON_GetObjectItem(root, "led3");
if (cJSON_IsBool(led)) {
// 控制 LED3
if (cJSON_IsTrue(led)) {
ms_uint8_t on = 0;
ms_io_write(led3_fd, &on, 1);
led_state[2] = 1;
} else {
ms_uint8_t off = 1;
ms_io_write(led3_fd, &off, 1);
led_state[2] = 0;
}
}
// 报告 IoT Pi LED 状态
iot_pi_led_state_report(sddc, uid, led_state);
memcpy(led_state_bak, led_state, sizeof(led_state_bak));
error:
cJSON_Delete(root);
return MS_TRUE;
}
// 处理 MESSAGE ACK
static void iot_pi_on_message_ack(sddc_t *sddc, const uint8_t *uid, uint16_t seqno)
{
}
// 处理 MESSAGE 丢失
static void iot_pi_on_message_lost(sddc_t *sddc, const uint8_t *uid, uint16_t seqno)
{
}
// 处理 EdgerOS 失联
static void iot_pi_on_edgeros_lost(sddc_t *sddc, const uint8_t *uid)
{
}
// 处理 UPDATE 报文
static sddc_bool_t iot_pi_on_update(sddc_t *sddc, const uint8_t *uid, const char *update_data, ms_size_t len)
{
cJSON *root = cJSON_Parse(udpate_data);
char *str;
sddc_return_value_if_fail(root, SDDC_FALSE);
// 分析 UPDATE 报文
str = cJSON_Print(root);
sddc_goto_error_if_fail(str);
// 打印 UPDATE 报文
sddc_printf("iot_pi_on_update: %s\n", str);
cJSON_free(str);
cJSON_Delete(root);
return SDDC_TRUE;
error:
cJSON_Delete(root);
return SDDC_FALSE;
}
// 处理 INVITE 报文
static sddc_bool_t iot_pi_on_invite(sddc_t *sddc, const uint8_t *uid, const char *invite_data, size_t len)
{
cJSON *root = cJSON_Parse(invite_data);
char *str;
sddc_return_value_if_fail(root, SDDC_FALSE);
// 分析 INVITE 报文
str = cJSON_Print(root);
sddc_goto_error_if_fail(str);
// 打印 INVITE 报文
sddc_printf("iot_pi_on_invite: %s\n", str);
cJSON_free(str);
cJSON_Delete(root);
return SDDC_TRUE;
error:
cJSON_Delete(root);
return SDDC_FALSE;
}
// 处理 INVITE 结束
static sddc_bool_t iot_pi_on_invite_end(sddc_t *sddc, const uint8_t *uid)
{
// 报告 IoT Pi LED 状态
iot_pi_led_state_report(sddc, uid, MS_NULL);
return MS_TRUE;
}
// 创建 REPORT 数据
static char *iot_pi_report_data_create(void)
{
cJSON *root;
cJSON *report;
char *str;
root = cJSON_CreateObject();
sddc_return_value_if_fail(root, NULL);
report = cJSON_CreateObject();
sddc_return_value_if_fail(report, NULL);
cJSON_AddItemToObject(root, "report", report);
cJSON_AddStringToObject(report, "name", "IoT Pi");
cJSON_AddStringToObject(report, "type", "device");
cJSON_AddBoolToObject(report, "excl", SDDC_FALSE);
cJSON_AddStringToObject(report, "desc", "翼辉 IoT Pi");
cJSON_AddStringToObject(report, "model", "1");
cJSON_AddStringToObject(report, "vendor", "ACOINFO");
/*
* Add extension here
*/
str = cJSON_Print(root);
sddc_return_value_if_fail(str, NULL);
sddc_printf("REPORT DATA: %s\n", str);
cJSON_Delete(root);
return str;
}
// 创建 INVITE 数据
static char *iot_pi_invite_data_create(void)
{
cJSON *root;
cJSON *report;
char *str;
root = cJSON_CreateObject();
sddc_return_value_if_fail(root, NULL);
report = cJSON_CreateObject();
sddc_return_value_if_fail(report, NULL);
cJSON_AddItemToObject(root, "report", report);
cJSON_AddStringToObject(report, "name", "IoT Pi");
cJSON_AddStringToObject(report, "type", "device");
cJSON_AddBoolToObject(report, "excl", SDDC_FALSE);
cJSON_AddStringToObject(report, "desc", "翼辉 IoT Pi");
cJSON_AddStringToObject(report, "model", "1");
cJSON_AddStringToObject(report, "vendor", "ACOINFO");
/*
* Add extension here
*/
str = cJSON_Print(root);
sddc_return_value_if_fail(str, NULL);
sddc_printf("INVITE DATA: %s\n", str);
cJSON_Delete(root);
return str;
}
// IoT Pi 按键扫描线程
static void iot_pi_key_thread(ms_ptr_t arg)
{
fd_set rfds;
sddc_t *sddc = arg;
ms_uint8_t key1_press = 0;
ms_uint64_t key1_press_begin = 0;
while (1) {
// 等按键按下
FD_ZERO(&rfds);
FD_SET(key1_fd, &rfds);
FD_SET(key2_fd, &rfds);
FD_SET(key3_fd, &rfds);
if (select(key3_fd + 1, &rfds, MS_NULL, MS_NULL, MS_NULL) > 0) {
cJSON *root;
char *str;
root = cJSON_CreateObject();
sddc_return_if_fail(root);
if (FD_ISSET(key1_fd, &rfds)) {
// KEY1 按下
key1_press++;
if (key1_press == 1) {
// 记录第一次按下的时间
key1_press_begin = ms_time_get_ms();
} else if (key1_press == 3) {
// 按下了三次
key1_press = 0;
// 三个按下的时间小于 800MS
if ((ms_time_get_ms() - key1_press_begin) < 800) {
static struct ifreq ifreq;
ifreq.ifr_flags = !ifreq.ifr_flags;
if (ifreq.ifr_flags) {
// 启动 SmartConfig
sddc_printf("Start smart configure...\n");
} else {
// 停止 SmartConfig
sddc_printf("Stop smart configure...\n");
}
// 操作网卡
ioctl(sockfd, SIOCSIFPFLAGS, &ifreq);
continue;
}
}
cJSON_AddBoolToObject(root, "key1", MS_TRUE);
// 反转 LED1
ms_io_write(led1_fd, &led_state_bak[0], 1);
led_state_bak[0] = !led_state_bak[0];
cJSON_AddBoolToObject(root, "led1", led_state_bak[0]);
}
if (FD_ISSET(key2_fd, &rfds)) {
// KEY2 按下
cJSON_AddBoolToObject(root, "key2", MS_TRUE);
// 反转 LED2
ms_io_write(led2_fd, &led_state_bak[1], 1);
led_state_bak[1] = !led_state_bak[1];
cJSON_AddBoolToObject(root, "led2", led_state_bak[1]);
key1_press = 0;
}
if (FD_ISSET(key3_fd, &rfds)) {
// KEY3 按下
cJSON_AddBoolToObject(root, "key3", MS_TRUE);
// 反转 LED3
ms_io_write(led3_fd, &led_state_bak[2], 1);
led_state_bak[2] = !led_state_bak[2];
cJSON_AddBoolToObject(root, "led3", led_state_bak[2]);
key1_press = 0;
}
str = cJSON_Print(root);
sddc_return_if_fail(str);
// 给连接的 EdgerOS 广播 MESSAGE
sddc_broadcast_message(sddc, str, strlen(str), 1, MS_FALSE, MS_NULL);
cJSON_free(str);
cJSON_Delete(root);
}
}
}
// 初始化 IoT Pi 的 LED
static int iot_pi_led_init(void)
{
ms_gpio_param_t param;
// 打开 LED1
led1_fd = ms_io_open("/dev/led1", O_WRONLY, 0666);
sddc_return_value_if_fail(led1_fd >= 0, -1);
// 打开 LED2
led2_fd = ms_io_open("/dev/led2", O_WRONLY, 0666);
sddc_return_value_if_fail(led2_fd >= 0, -1);
// 打开 LED3
led3_fd = ms_io_open("/dev/led3", O_WRONLY, 0666);
sddc_return_value_if_fail(led3_fd >= 0, -1);
// 设置 LED 的 GPIO 模式
param.mode = MS_GPIO_MODE_OUTPUT_PP;
param.pull = MS_GPIO_PULL_UP;
param.speed = MS_GPIO_SPEED_HIGH;
ms_io_ioctl(led1_fd, MS_GPIO_CMD_SET_PARAM, ¶m);
ms_io_ioctl(led2_fd, MS_GPIO_CMD_SET_PARAM, ¶m);
ms_io_ioctl(led3_fd, MS_GPIO_CMD_SET_PARAM, ¶m);
// 读 LED 状态到备份变量
ms_io_read(led1_fd, &led_state_bak[0], 1);
ms_io_read(led2_fd, &led_state_bak[1], 1);
ms_io_read(led3_fd, &led_state_bak[2], 1);
led_state_bak[0] = !led_state_bak[0];
led_state_bak[1] = !led_state_bak[1];
led_state_bak[2] = !led_state_bak[2];
return 0;
}
// 初始化 IoT Pi 的 按键
static int iot_pi_key_init(void)
{
ms_gpio_param_t param;
// 打开 KEY1
key1_fd = ms_io_open("/dev/key1", O_WRONLY, 0666);
sddc_return_value_if_fail(key1_fd >= 0, -1);
// 打开 KEY2
key2_fd = ms_io_open("/dev/key2", O_WRONLY, 0666);
sddc_return_value_if_fail(key2_fd >= 0, -1);
// 打开 KEY3
key3_fd = ms_io_open("/dev/key3", O_WRONLY, 0666);
sddc_return_value_if_fail(key3_fd >= 0, -1);
// 设置 KEY 的 GPIO 模式
param.mode = MS_GPIO_MODE_IRQ_FALLING;
param.pull = MS_GPIO_PULL_UP;
param.speed = MS_GPIO_SPEED_HIGH;
ms_io_ioctl(key1_fd, MS_GPIO_CMD_SET_PARAM, ¶m);
ms_io_ioctl(key2_fd, MS_GPIO_CMD_SET_PARAM, ¶m);
ms_io_ioctl(key3_fd, MS_GPIO_CMD_SET_PARAM, ¶m);
return 0;
}
// 应用入口函数
int main(int argc, char *argv[])
{
struct ifreq ifreq;
struct sockaddr_in *psockaddrin = (struct sockaddr_in *)&(ifreq.ifr_addr);
sddc_t *sddc;
char *data;
int ret;
// 初始化 IoT Pi 的三个 LED 灯
ret = iot_pi_led_init();
sddc_return_value_if_fail(ret == 0, -1);
// 初始化 IoT Pi 的三个 KEY
ret = iot_pi_key_init();
sddc_return_value_if_fail(ret == 0, -1);
// 设置网络实现
#ifdef SDDC_CFG_NET_IMPL
ret = ms_net_set_impl(SDDC_CFG_NET_IMPL);
sddc_return_value_if_fail(ret == MS_ERR_NONE, -1);
#endif
// 创建 SDDC 协议对象
sddc = sddc_create(SDDC_CFG_PORT);
sddc_return_value_if_fail(sddc, -1);
// 设置事件响应函数
sddc_set_on_message(sddc, iot_pi_on_message); // 设置接收消息请求时的回调函数
sddc_set_on_message_ack(sddc, iot_pi_on_message_ack); // 设置接收消息确认时的回调函数
sddc_set_on_message_lost(sddc, iot_pi_on_message_lost); // 设置丢失消息时的回调函数
sddc_set_on_invite(sddc, iot_pi_on_invite); // 设置接受邀请请求时的回调函数
sddc_set_on_invite_end(sddc, iot_pi_on_invite_end); // 设置发送邀请后的回调函数
sddc_set_on_update(sddc, iot_pi_on_update); // 设置接收更新请求时的回调函数
sddc_set_on_edgeros_lost(sddc, iot_pi_on_edgeros_lost); // 设置 EdgerOS 断连时的回调函数
// 设置设备密码
#if SDDC_CFG_SECURITY_EN > 0 // SDDC_CFG_SECURITY_EN 宏控制是否支持数据加密通信
ret = sddc_set_token(sddc, "1234567890");
sddc_return_value_if_fail(ret == 0, -1);
#endif
// 创建并设置 Report 报文数据
data = iot_pi_report_data_create();
sddc_return_value_if_fail(data, -1);
sddc_set_report_data(sddc, data, strlen(data));
// 创建并设置 Invite 报文数据
data = iot_pi_invite_data_create();
sddc_return_value_if_fail(data, -1);
sddc_set_invite_data(sddc, data, strlen(data));
// 获取并打印网卡 mac 地址
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
sddc_return_value_if_fail(sockfd >= 0, -1);
ioctl(sockfd, SIOCGIFHWADDR, &ifreq);
sddc_printf("MAC addr: %02x:%02x:%02x:%02x:%02x:%02x/n",
(ms_uint8_t)ifreq.ifr_hwaddr.sa_data[0],
(ms_uint8_t)ifreq.ifr_hwaddr.sa_data[1],
(ms_uint8_t)ifreq.ifr_hwaddr.sa_data[2],
(ms_uint8_t)ifreq.ifr_hwaddr.sa_data[3],
(ms_uint8_t)ifreq.ifr_hwaddr.sa_data[4],
(ms_uint8_t)ifreq.ifr_hwaddr.sa_data[5]);
// 使用网卡 mac 地址设置设备唯一标识 UID
sddc_set_uid(sddc, (const ms_uint8_t *)ifreq.ifr_hwaddr.sa_data);
// 获取并打印 IP 地址
if (ioctl(sockfd, SIOCGIFADDR, &ifreq) == 0) {
char ip[sizeof("255.255.255.255")];
inet_ntoa_r(psockaddrin->sin_addr, ip, sizeof(ip));
sddc_printf("IP addr: %s/n", ip);
} else {
sddc_printf("Failed to get IP address, Wi-Fi AP not online!/n");
}
// 创建按键扫描线程,快速连续按下 KEY 1 按键 三次,IoT Pi 将会进入 SmartConfig 模式
ret = ms_thread_create("t_key",
iot_pi_key_thread,
sddc,
2048U,
30U,
70U,
MS_THREAD_OPT_USER | MS_THREAD_OPT_REENT_EN,
MS_NULL);
sddc_return_value_if_fail(ret == MS_ERR_NONE, -1);
// 运行 SDDC 协议循环
while (1) {
sddc_printf("SDDC running.../n");
sddc_run(sddc);
sddc_printf("SDDC quit!/n");
}
// 销毁 SDDC 协议对象
sddc_destroy(sddc);
return 0;
}