IoT Pi CoAP 设备开发
本章将介绍如何在 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 这几个组件,如下图所示:在 IoT studio 上完成
msrtos_base_sdk
工程导入和编译在 IoT studio 上完成
bspstm32f4xx
工程的下载和导入,如下图所示:
获取 CoAP 组件
进入 msrtos_base_sdk
根目录,使用 git 工具从 MS-RTOS github 社区 下载 libcoap
组件,命令如下:
git clone https://github.com/ms-rtos/libcoap.git
下载完成后右键 msrtos_base_sdk
工程选择 Refresh
菜单项刷新工程,刷新完成后将出现 libcoap
组件,如下图所示:
在 msrtos_base_sdk
根目录下的 Makefile
中添加 libcoap
,如下图所示:
安装 Node CoAP CLI
参考《CoAP 协议介绍》完成 Node CoAP CLI
安装。
工程配置
bspstm32f4xx 配置
配置自启动进程名
bspstm32f4xx/src/board/IOT_PI/iot_pi_init.h
文件中设定了 IoT Pi 开发板开机时会创建进程启用 0x08040000
地址处的程序,修改自启动进程名字,具体代码如下:
ms_process_create("iotpi_coap", (ms_addr_t)0x08040000, 65536, 4096, 9, 0 , 0, MS_NULL, MS_NULL, MS_NULL);
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 与密码
};
工程编译
libcoap 编译
选中 msrtos_base_sdk
工程,点击 “编译” 按钮,将编译 msrtos_base_sdk
工程的组件,编译完成后,会在 libcoap/Debug/coap_examples/src
目录生成 coap_server_example.bin
文件:
bspstm32f4xx 编译
选中 bspstm32f4xx
工程,点击 "编译" 按钮,将编译 bspstm32f4xx
工程,编译完成后,会在 Debug
目录生成 bspiotpi.bin
文件:
验证功能
烧写镜像
使用 MS-RTOS AutoTester 烧写镜像,请参考《IoT Pi 快速入门》完成 bspiotpi.bin
和 coap_server_example.bin
镜像烧写,注意不同的镜像需要烧写到不同的地址,如下表所示:
镜像 | 烧写地址 |
---|---|
bspiotpi.bin | 0x08000000 |
coap_server_example.bin | 0x08040000 |
启动 CoAP Server
按下 IoT Pi 开发板的 RESET 按键或者点击 MS-RTOS Auto Tester GO
按钮,MS-RTOS 操作系统启动后,将自动运行 0x08040000
地址处的 CoAP Server 程序,在 MS-RTOS Auto Tester Input
窗口输入 ps
命令查看进程的信息:
使用 Node CoAP CLI 测试 CoAP Server
输入
coap get coap://192.168.128.103/test
获取 CoAP Server 的test
资源(这时没有信息,所以显示no data
),如下图所示:输入
coap put -p hello_world coap://192.168.128.103/test
修改 CoAP Server 的test
资源为字符串hello_world
,如下图所示:输入
coap get coap://192.168.128.103/test
再次获取 CoAP Server 的test
资源,这时能获取到上一步设置的字符串hello_world
,如下图所示:
CoAP Server 代码解析
CoAP Server 代码位于 msrtos_base_sdk/libcoap/src/coap_server_example.c
:
/* CoAP server Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include "coap.h"
/* Set this to 9 to get verbose logging from within libcoap */
/* If want to change log level num to open log, don't forget to enlarge coap_example_task size*/
#define COAP_LOGGING_LEVEL 0
// test 资源对应的变量
static char test_data[100];
static int test_data_len = 0;
// test 资源的 GET 请求处理函数
static void
hnd_test_get(coap_context_t *ctx, coap_resource_t *resource,
coap_session_t *session,
coap_pdu_t *request, coap_binary_t *token,
coap_string_t *query, coap_pdu_t *response)
{
// 将 test_data 变量的内容增加到响应中
coap_add_data_blocked_response(resource, session, request, response, token,
COAP_MEDIATYPE_TEXT_PLAIN, 0,
(size_t)test_data_len,
(const u_char *)test_data);
}
// test 资源的 PUT 请求处理函数
static void
hnd_test_put(coap_context_t *ctx,
coap_resource_t *resource,
coap_session_t *session,
coap_pdu_t *request,
coap_binary_t *token,
coap_string_t *query,
coap_pdu_t *response)
{
size_t size;
unsigned char *data;
// 通知观察者
coap_resource_notify_observers(resource, NULL);
// 设置响应代码
if (strcmp (test_data, "no data") == 0) {
response->code = COAP_RESPONSE_CODE(201);
} else {
response->code = COAP_RESPONSE_CODE(204);
}
// 从请求中获得数据
(void)coap_get_data(request, &size, &data);
// 用获得的数据设置 test_data 变量
if (size == 0) { /* re-init */
snprintf(test_data, sizeof(test_data), "no data");
test_data_len = strlen(test_data);
} else {
test_data_len = size > sizeof (test_data) ? sizeof (test_data) : size;
memcpy (test_data, data, test_data_len);
}
}
// test 资源的 DELETE 请求处理函数
static void
hnd_test_delete(coap_context_t *ctx,
coap_resource_t *resource,
coap_session_t *session,
coap_pdu_t *request,
coap_binary_t *token,
coap_string_t *query,
coap_pdu_t *response)
{
// 通知观察者
coap_resource_notify_observers(resource, NULL);
// 将 test_data 变量设置为 "no data"
snprintf(test_data, sizeof(test_data), "no data");
test_data_len = strlen(test_data);
// 设置响应代码
response->code = COAP_RESPONSE_CODE(202);
}
int main(int argc, char **argv)
{
coap_context_t *ctx = NULL; // CoAP 协议栈上下文
coap_address_t serv_addr; // 多种 sockaddr 的抽象
coap_resource_t *resource = NULL; // CoAP 资源指针
// 初始化 test_data 变量内容
snprintf(test_data, sizeof(test_data), "no data");
test_data_len = strlen(test_data);
// 设置日志级别
coap_set_log_level(COAP_LOGGING_LEVEL);
while (1) {
coap_endpoint_t *ep_udp = NULL;
coap_endpoint_t *ep_tcp = NULL;
unsigned wait_ms;
// 创建一个新的 CoAP 上下文
ctx = coap_new_context(NULL);
if (!ctx) {
continue;
}
// 准备 CoAP 服务器套接字
coap_address_init(&serv_addr);
serv_addr.addr.sin.sin_family = AF_INET;
serv_addr.addr.sin.sin_addr.s_addr = INADDR_ANY;
serv_addr.addr.sin.sin_port = htons(COAP_DEFAULT_PORT);
// 创建 UDP 通信端点
ep_udp = coap_new_endpoint(ctx, &serv_addr, COAP_PROTO_UDP); //Add IPv4 endpoint
if (!ep_udp) {
goto clean_up;
}
// 创建 TCP 通信端点
ep_tcp = coap_new_endpoint(ctx, &serv_addr, COAP_PROTO_TCP);
if (!ep_tcp) {
goto clean_up;
}
// 初始化 test 资源
resource = coap_resource_init(coap_make_str_const("test"), 0);
if (!resource) {
goto clean_up;
}
// 设置 test 资源的 GET 请求处理函数
coap_register_handler(resource, COAP_REQUEST_GET, hnd_test_get);
// 设置 test 资源的 PUT 请求处理函数
coap_register_handler(resource, COAP_REQUEST_PUT, hnd_test_put);
// 设置 test 资源的 DELETE 请求处理函数
coap_register_handler(resource, COAP_REQUEST_DELETE, hnd_test_delete);
// 设置 test 资源是否可观察
//(如果资源是可观察的,并且客户端在请求包中设置了 COAP_OPTION_OBSERVE,
// 当资源的状态发生变化时,调用 coap_resource_notify_observers(),
// 将发送一个 Observer 响应)
coap_resource_set_get_observable(resource, 1);
// CoAP 上下文加入 test 资源
coap_add_resource(ctx, resource);
// 设置等待时间
wait_ms = COAP_RESOURCE_CHECK_TIME * 1000;
while (1) {
// CoAP 协议栈 IO 处理,传入等待时间,返回前消耗的时间
int result = coap_io_process(ctx, wait_ms);
if (result < 0) {
// 出错
break;
} else if (result && (unsigned)result < wait_ms) {
// 如果消耗时间少于等待时间,则修改等待时间 wait_ms
wait_ms -= result;
}
if (result) {
// 如果消耗时间大于等于等待时间,则重置等待时间 wait_ms
wait_ms = COAP_RESOURCE_CHECK_TIME * 1000;
}
}
}
clean_up:
// 释放 CoAP 上下文
coap_free_context(ctx);
// 清理 libcoap
coap_cleanup();
return 0;
}