在冷冷的冬天 火锅配上豌豆尖 想想就很美 那就赶快行动起来 跟我们学习如何利用OneOS的端云融合能力种出更茂盛的豌豆尖吧~ 准备工具 : 万耦创世开发板*1 土壤湿度传感器*1 OneOS嵌入式操作系统 OneNET Studio 物联网开发平台 温湿度监测应用需要智能设备和云平台进行互联,在云平台上进行控制、管理和数据分析。OneOS在端侧以OneOS内核为基础,集成各类端云通信协议,屏蔽复杂通信过程,支持连接中国移动OneNET物联网数字感知能力中台和其他IoT平台,开发者可以通过OneOS提供的连接能力结合OneNET的开放能力及产品,快速打造端云融合的物联网应用。 本文通过使用标准MQTT协议接入OneNET 平台为例,展示OneOS快速上云,助力物联网产品快速开发。 OneNET Studio简介 OneNET Studio是一站式物联网开发平台,向下接入设备,向上承载应用。整合产业链上下游,向下整合终端设备接入与管理,向上延展物联网一站式应用开发,横向聚合增值能力,提供智能化数据分析,形成端到端完整链路物联网解决方案体系,打造物联网生态环境。 OneNET Studio快速入门 OneNET Studio快速入门主要包括以下几个步骤: •注册账号 •登录OneNET •进入控制台 •进入Studio平台 详细的说明可以访问 OneNET Studo快速入门 进行学习。 连接OneNET Studio MQTT协议 物联网平台支持标准MQTT协议和CoAP协议接入,是物联网的重要组成部分。MQTT是一种基于TCP构建的轻量级发布、订阅传输协议,适用于网络带宽有限的场景,同时其可以保持长连接,具有一定的实效性。 创建产品 OneNET Studio中产品是一组具有相同功能定义的设备集合,快速创建产品后可定义产品物模型、添加对应设备。进入Studio平台后,展开菜单栏中的「设备接入与管理」,点击「产品管理」,进入产品列表页面。点击「添加产品」,在弹出页面中,按照提示填写产品的基本信息,接入协议选择MQTT,完成产品创建。 详细的说明可以访问 创建产品 进行学习。 创建设备 物理设备要连接到平台,需要先在平台创建设备(支持单个或批量导入创建),并获取连接到平台的鉴权信息。进入Studio平台后,展开菜单栏中的「设备接入与管理」,点击「设备管理」,进入设备列表页面。点击「添加设备」,选中单个设备(默认方式)输入设备详情,点击「确定」创建设备。 生成接入Token 连接到平台需要鉴权参数,需要用到的参数如下: •ProductId:产品ID,平台生成唯一ID •DeviceName:设备名称 •Token:鉴权token Token的计算方法可以访问 设备安全认证 进行学习。 连接平台 使用MQTT客户端尝试连接平台,验证Token是否计算正确。 其中Client ID为在OneNET Studio中创建的设备名,用户名为产品ID,密码为计算后的Token。点击连接,当连接参数设置正确时将连接至平台,此时设备在页面上显示为在线状态。 自定义物模型 物模型 是对设备的数字化抽象描述,描述该型号设备是什么,能做什么,能对外提供哪些服务。物模型将物理空间中的实体设备数字化,在云端构建该实体的数据模型,即将物理空间的实体在云端进行格式化表示。本文以温/湿度传感器和光强/接近位置传感器的数据为例,进行物模型的定义。 点击「产品管理」页,产品列表中对应产品的「详情」操作,进入物模型设置页面,点击「设置物模型」,根据需求添加或选择物模型功能点,完成功能点编辑后,点击「保存」,使物模型模板生效。 本文自定义的物模型如上图所示,关于物模型详细说明可以访问 物模型 进行学习。 OneOS应用开发 OneOS及工具下载 目前OneOS代码已经开源,开发者可以前往 OneOS仓库 或者 OneOS官网 进行下载。 OneOS-Cube是OneOS操作系统基于命令行的开发工具,提供系统配置、项目编译构造、包贡献下载等功能。OneOS-Cube工具的下载及使用说明可以访问 OneOS-Cube环境搭建 进行学习。 硬件环境准备 本文使用OneOS官方的万耦创世L475开发板作为硬件平台。 万耦创世L475开发板使用了 STM32L475VGT6 芯片,STM32L4系列MCU可以根据微处理器运行时不同的应用需求来适时调整电压从而实现功耗的动态平衡。该功能适用于STOP模式下的低功耗外设(LP UART、LP定时器)、安全和保密特性、大量智能外设,以及诸如运算放大器、比较器、LCD、12位DAC和16位ADC(硬件过采样)等先进的低功耗模拟外设。 关于万耦开发板详细的文档说明可以参考 万耦开发板开发文档 软件环境准备 万耦开发板的示例工程需要准备以下的软件环境: •MDK 开发环境 安装 MDK-ARM 5.27 (正式版或评估版,5.14 版本及以上版本均可),推荐使用keil最新版本,避免不必要的问题,安装方法可以参考 Keil MDK安装。 •连接并安装Jlink驱动,便于进行下载调试; 为了深入开发流程,开发者可以使用本文提供的空白示例工程进行开发,一步一步的完成一个简易物联网应用的开发。 点亮万耦开发板 完成万耦开发板的硬件连接,如下图所示: 将本文提供的空白示例工程解压缩后放置在 OneOS/projects 目录下,如下图所示: 进入 stm32l475-cmcc-oneos-demo 目录下,打开MDK工程,点击左上角的 Build 按钮,等待编译完成。 当看到 0 Error(s), 0 Warning(s) 的输出时说明编译完成,此时点击左上角的 Download 按钮,将程序下载到开发板中。 当程序下载完成后,串口将输出 Hello OneOS! ,代表开发板中的程序运行成功! 读取传感器数据 OneOS在各类外设的基础上抽象出了设备驱动模型,有效提高了代码可复用性、可移植性,模块分层解耦,降低了各层的开发难度。 本文将使用OneOS中Sensor框架提供的API读取万耦开发板上板载的AHT10温/湿度传感器和AP3216C光强/接近位置传感器的数据。 Sensor框架配置 在stm32l475-cmcc-oneos-demo右键打开OneOS-Cube工具,使用menuconfig进行Sensor框架的配置,配置如下所示: (Top) → Drivers→ I2C Using I2C device drivers Use GPIO to simulate I2C (10) simulate I2C bus delay(us) Enable I2C1 BUS (software simulation) ---- Enable I2C2 BUS (software simulation) ---- Enable I2C4 BUS (software simulation) ---- (Top) → Drivers→ Sensors Using sensor device drivers Enable sht20 ---- (soft_i2c3) aht10 i2c bus name (0x38) aht10 i2c addr(7bit) Enable adxl345 ---- Enable bh1750 ---- Enable bmp180 ---- Enable ak8963 ---- Enable lsm6dsl ---- (soft_i2c3) ap3216c i2c bus name (0x1e) ap3216c i2c addr 完成配置后退出menuconfig并保存配置,使用命令 scons --ide=mdk5 更新MDK工程设置, 命令执行完成后重新打开MDK工程。 Sensor相关的代码和组将会被加到MDK工程中。在 main 函数中添加以下的代码,读取并打印传感器的数值。 /* 包含头文件 */ #include #include #include static os_device_t *sensor_temp = OS_NULL; static os_device_t *sensor_humi = OS_NULL; static os_device_t *sensor_light = OS_NULL; static os_device_t *sensor_ps = OS_NULL; /* 传感器初始化 */ static void sensor_init(void) { /* 根据传感器设备名称查找设备 */ sensor_temp = os_device_find("temp_aht10"); OS_ASSERT(sensor_temp != OS_NULL); sensor_humi = os_device_find("humi_aht10"); OS_ASSERT(sensor_humi != OS_NULL); sensor_light = os_device_find("li_ap3216c"); OS_ASSERT(sensor_light != OS_NULL); sensor_ps = os_device_find("pr_ap3216c"); OS_ASSERT(sensor_ps != OS_NULL); /* 打开传感器设备 */ os_device_open(sensor_temp, OS_DEVICE_FLAG_RDWR); os_device_open(sensor_humi, OS_DEVICE_FLAG_RDWR); os_device_open(sensor_light, OS_DEVICE_FLAG_RDWR); os_device_open(sensor_ps, OS_DEVICE_FLAG_RDWR); } int main(void) { printf("Hello, OneOS!\n"); struct os_sensor_data sensor_data; sensor_init(); /* 读取温度传感器数据 */ os_device_read(sensor_temp, 0, &sensor_data, sizeof(struct os_sensor_data)); printf("temp: %.2f\n", (double)sensor_data.data.temp / 1000); /* 读取湿度传感器数据 */ os_device_read(sensor_humi, 0, &sensor_data, sizeof(struct os_sensor_data)); printf("humi: %.2f\n", (double)sensor_data.data.humi / 1000); /* 读取光照传感器数据 */ os_device_read(sensor_light, 0, &sensor_data, sizeof(struct os_sensor_data)); printf("light: %.2f\n", (double)sensor_data.data.light / 1000); /* 读取接近传感器数据 */ os_device_read(sensor_ps, 0, &sensor_data, sizeof(struct os_sensor_data)); printf("ps: %d\n", sensor_data.data.raw); return 0; } 编译并下载到万耦开发板运行,可以通过串口看到打印的传感器数值。 关于OneOS驱动及Sensor设备的详细说明可以访问 Sensor设备用户开发 进行学习。 建立线程读取传感器 上面的示例代码只能读取和打印一次传感器数值,可以在OneOS中建立一个任务来循环读取和打印传感器数值。任务是 OneOS 操作系统中最小的调度单位。将上述的代码修改如下: ...... /* 传感器初始化 */ static void sensor_init(void) { ...... } static void sensor_read_entry(void *parameter) { struct os_sensor_data sensor_data; /* 循环读取传感器数据 */ while (1) { printf("---------Sensor Data--------\n"); /* 读取温度传感器数据 */ os_device_read(sensor_temp, 0, &sensor_data, sizeof(struct os_sensor_data)); printf("temp: %.2f\n", (double)sensor_data.data.temp / 1000); /* 读取湿度传感器数据 */ os_device_read(sensor_humi, 0, &sensor_data, sizeof(struct os_sensor_data)); printf("humi: %.2f\n", (double)sensor_data.data.humi / 1000); /* 读取光照传感器数据 */ os_device_read(sensor_light, 0, &sensor_data, sizeof(struct os_sensor_data)); printf("light: %.2f\n", (double)sensor_data.data.light / 1000); /* 读取接近传感器数据 */ os_device_read(sensor_ps, 0, &sensor_data, sizeof(struct os_sensor_data)); printf("ps: %d\n", sensor_data.data.raw); printf("----------------------------\n"); os_task_mdelay(3000); /* 延时3秒 */ } } int main(void) { printf("Hello, OneOS!\n"); sensor_init(); /* 创建传感器读取任务 */ os_task_t *sensor_read_task = os_task_create("sensor_read", sensor_read_entry, OS_NULL, 1024, 20, 5); if (sensor_task != OS_NULL) { os_task_startup(sensor_read_task); } return 0; } 编译并下载到万耦开发板运行,可以通过串口看到打印的循环读取的传感器数值。 关于OneOS任务管理及调度的详细内容可以访问 任务管理及调度 进行学习。 通过消息队列传递传感器数据 传感器读取任务读取的传感器数据可以保存在结构体中,然后发送至消息队列中进行保存。传感器处理任务可以从消息队列中读取传感器数据并进行处理。修改上述的示例代码中,新建一个消息队列和传感器处理线程,传感器处理线程从消息队列中读取数据并将数据按照指定的格式打印出来。 #include #include #include ...... static os_mq_t *sensor_mq = OS_NULL; typedef struct sensor_data { float temp; float humi; float light; int ps; } sensor_data_t; static void sensor_init(void) { ...... } static void sensor_read_entry(void *parameter) { struct os_sensor_data tmp_data; sensor_data_t data; /* 循环读取传感器数据 */ while (1) { /* 读取温度传感器数据 */ os_device_read(sensor_temp, 0, &tmp_data, sizeof(struct os_sensor_data)); data.temp = (float)tmp_data.data.temp / 1000; /* 读取湿度传感器数据 */ os_device_read(sensor_humi, 0, &tmp_data, sizeof(struct os_sensor_data)); data.humi = (float)tmp_data.data.humi / 1000; /* 读取光照传感器数据 */ os_device_read(sensor_light, 0, &tmp_data, sizeof(struct os_sensor_data)); data.light = (float)tmp_data.data.light / 1000; /* 读取接近传感器数据 */ os_device_read(sensor_ps, 0, &tmp_data, sizeof(struct os_sensor_data)); data.ps = tmp_data.data.raw; os_mq_send(sensor_mq, &data, sizeof(sensor_data_t), OS_IPC_WAITING_FOREVER); os_task_mdelay(3000); /* 延时3秒 */ } } static void sensor_process_entry(void *parameter) { sensor_data_t data; os_size_t data_size; while (1) { os_mq_recv(sensor_mq, &data, sizeof(sensor_data_t), OS_IPC_WAITING_FOREVER, &data_size); printf("---------Sensor Data--------\n"); printf("temp: %.2f\n", data.temp); printf("humi: %.2f\n", data.humi); printf("light: %.2f\n", data.light); printf("ps: %d\n", data.ps); printf("----------------------------\n"); } } int main(void) { printf("Hello, OneOS!\n"); sensor_init(); sensor_mq = os_mq_create("sensor_mq", sizeof(sensor_data_t), 10, OS_IPC_FLAG_PRIO); OS_ASSERT(sensor_mq != OS_NULL); /* 创建传感器读取任务 */ os_task_t *sensor_read_task = os_task_create("sensor_read", sensor_read_entry, OS_NULL, 1024, 20, 5); OS_ASSERT(sensor_read_task != OS_NULL); os_task_startup(sensor_read_task); /* 创建传感器处理任务 */ os_task_t *sensor_process_task = os_task_create("sensor_process", sensor_process_entry, OS_NULL, 2048, 15, 5); OS_ASSERT(sensor_process_task != OS_NULL); os_task_startup(sensor_process_task); return 0; } 关于OneOS消息队列及任务间数据通信的详细内容可以访问 消息队列 进行学习。 通过MQTT接入OneNET Studio MQTT组件 OneOS操作系统通过移植Paho-MQTT软件包来实现MQTT协议功能,开发者可以通过OneOS Cube中的menuconfig图形化界面轻松的将MQTT组件集成到自己的应用中,MQTT组件的配置如下: (Top) → Components→ Network→ Protocols→ MQTT Enable Paho MQTT Enable Paho MQTT TLS encrypt Enable Paho MQTT sample 关于MQTT组件的配置及API说明可以访问 MQTT用户编程手册 进行学习。 cJSON组件 OneNET Studio使用 OneJSON 作为物模型通信协议,OneJSON协议是针对物联网开发领域设计的一种数据交换规范,数据格式是JSON,用于设备端和物联网平台的双向通信,更便捷地实现和规范了设备端和物联网平台之间的业务数据交互。 OneOS操作系统通过cJSON组件提供JSON格式数据创建和解析的能力,cJSON组件的配置如下: (Top) → Thirdparty→ cJSON cJSON: Ultralightweight JSON parser in ANSI C 关于cJSON的详细说明可以访问 cJSON 进行学习。 Molink组件 OneOS操作系统通过 Molink模组互联套件 来提供设备与平台间互联互通的能力。Molink通过架构设计和模组适配实现了对不同的通信模组的统一控制,并向上层框架和应用提供统一的API接口,使开发者不必关心不同模组之间的差异即可完成网络相关应用的开发。 本文以使用 ML302 为例,ML302 是中国移动最新推出的 LTE Cat.1 模块。ML302 支持 TD- LTE/FDD- LTE 通信制式,采用 LCC+LGA 封装方式。丰富的 Internet 协议、行业标准接口和功能,支持 Windows、Linux 和 Android 驱动。ML302 可广泛应用到 M2M多个领域中,如共享、金融支付、POC、工业控制等。 Molink中ML302的配置如下所示: (Top) → Components → Network → Molink → Enable IoT modules support → Modules → 4G CAT1 Modules Support → ML302 → ML302 Enable ML302 Module Object Auto Create (uart4) ML302 Interface Device Name (115200) ML302 Interface Device Rate (512) The maximum length of AT command data accepted -*- Enable ML302 Module General Operates -*- Enable ML302 Module Network Service Operates Enable ML302 Module Ping Operates Enable ML302 Module Ifconfig Operates -*- Enable ML302 Module Network TCP/IP Operates Enable ML302 Module BSD Socket Operates Enable ML302 Module MQTT Client Operates 如果需要使用其他的通信模组,只需在Molink的菜单下进行重新配置即可,关于Molink的配置及API说明可以访问 Molink用户编程手册 进行学习。 完成上述MQTT、cJSON及Molink组件的配置后,在OneOS-Cube工具中使用命令scons --ide=mdk5更新MDK工程设置 MQTT、cJSON及Molink相关的代码将会被加入到MDK工程中, 编译代码并下载至万耦开发板中,当看到如下的日志输出时,说明ML302模组初始化成功。 示例代码 编写如下所示的代码连接到OneNET Studio平台,并将传感器数据发布至平台。 #include #include #include #include #include #include #include #include static os_device_t *sensor_temp = OS_NULL; static os_device_t *sensor_humi = OS_NULL; static os_device_t *sensor_light = OS_NULL; static os_device_t *sensor_ps = OS_NULL; static os_mq_t *sensor_mq = OS_NULL; typedef struct sensor_data { double temp; double humi; double light; int ps; } sensor_data_t; static void sensor_init(void) { ...... } static void sensor_read_entry(void *parameter) { ...... } #define MQTT_SERVER_IP "218.201.45.7" #define MQTT_SERVER_PORT (1883) #define MQTT_CLINET_ID "oneos_test" #define MQTT_POST_TOPIC "$sys/9HFp6Cf7i4/oneos_test/thing/property/post" #define MQTT_COMMAND_TIMEOUT (30000) #define MQTT_CLIENT_USERNAME "用户名" #define MQTT_CLINET_PASSWORD "用户密码" static unsigned char send_buff = {0}; static unsigned char read_buff = {0}; static void sensor_process_entry(void *parameter) { sensor_data_t data; os_size_t data_size; Network network; MQTTClient client; MQTTNetworkInit(&network, MQTT_SERVER_IP, MQTT_SERVER_PORT, OS_NULL); MQTTClientInit(&client, &network, MQTT_COMMAND_TIMEOUT, send_buff, sizeof(send_buff), read_buff, sizeof(read_buff)); int rc = network.connect(&network); if (0 != rc) { printf("Establish network failed, check MQTT server ip and port"); return; } MQTTPacket_connectData connectData = MQTTPacket_connectData_initializer; connectData.clientID.cstring = MQTT_CLINET_ID; connectData.username.cstring = MQTT_CLIENT_USERNAME; connectData.password.cstring = MQTT_CLINET_PASSWORD; if ((rc = MQTTConnect(&client, &connectData)) != 0) { printf("Return code from MQTT connect is %d", rc); return; } else { printf("MQTT Client Connected"); } while (1) { os_mq_recv(sensor_mq, &data, sizeof(sensor_data_t), OS_IPC_WAITING_FOREVER, &data_size); printf("---------Sensor Data--------\n"); /* 四舍五入保留两位小数 */ data.temp = ((int)(data.temp * 100 + 0.5)) / 100.0; data.humi = ((int)(data.humi * 100 + 0.5)) / 100.0; data.light = ((int)(data.light * 100 + 0.5)) / 100.0; printf("temp: %.2f\n", data.temp); printf("humi: %.2f\n", data.humi); printf("light: %.2f\n", data.light); printf("ps: %d\n", data.ps); printf("----------------------------\n"); if (MQTTIsConnected(&client)) { /* 将数据封装为OneJSON格式 */ cJSON *onejson_head = cJSON_CreateObject(); cJSON_AddStringToObject(onejson_head, "id", "123"); cJSON_AddStringToObject(onejson_head, "version", "1.0"); cJSON *onejson_params = cJSON_CreateObject(); cJSON *onejson_temp = cJSON_CreateObject(); cJSON_AddNumberToObject(onejson_temp, "value", data.temp); cJSON *onejson_humi = cJSON_CreateObject(); cJSON_AddNumberToObject(onejson_humi, "value", data.humi); cJSON *onejson_light = cJSON_CreateObject(); cJSON_AddNumberToObject(onejson_light, "value", data.light); cJSON *onejson_ps = cJSON_CreateObject(); cJSON_AddNumberToObject(onejson_ps, "value", data.ps); cJSON_AddItemToObject(onejson_params, "temp", onejson_temp); cJSON_AddItemToObject(onejson_params, "humi", onejson_humi); cJSON_AddItemToObject(onejson_params, "light", onejson_light); cJSON_AddItemToObject(onejson_params, "ps", onejson_ps); cJSON_AddItemToObject(onejson_head, "params", onejson_params); MQTTMessage message; message.qos = QOS1; message.retained = 0; message.payload = cJSON_Print(onejson_head); message.payloadlen = strlen(message.payload); /* 上报数据到OneOS Studio平台 */ rc = MQTTPublish(&client, MQTT_POST_TOPIC, &message); if (rc != 0) { printf("MQTT Client publish error %d\n", rc); } printf("MQTT Client publish success!\n"); free(message.payload); cJSON_Delete(onejson_head); } } } int main(void) { ...... } 其中,MQTT设备属性上报的主题为$sys/{pid}/{device-name}/thing/property/post,关于MQTT通信主题的详细内容可以访问 MQTT通信主题 进行学习。 编译代码并下载至万耦开发板中,当看到串口中输出MQTT Client publish success!时表示向OneNET Studio平台的物模型发布新的属性值成功。 打开OneNET Studio的设备详情页面,可以看到OneOS上报的AHT10温/湿度传感器和AP3216C光强/接近位置传感器的数据。 常见问题 1. 我没有ML302模组,如何使用其他模组来完成上述的示例? 本示例并不依赖与特定型号的模组,开发者可以在Molink的菜单下进行重新配置选择模组。 2. 我没有万耦开发板,如何使用其他开发板来完成上述的示例? OneOS已经适配支持了许多的开发板,可以查看projects目录下支持的开发板型号,选择一款适合自己的开发板。 3. 使用OneNET-Token计算工具计算出来的Token无法连接至平台怎么办? 请尝试调整Token中参数的顺序,如下图所示: References OneNET Studo快速入门: https://open.iot.10086.cn/doc/iot_platform/book/get-start/login.html#1 创建产品: https://open.iot.10086.cn/doc/iot_platform/book/device-connect&manager/product-create.html 设备安全认证: https://open.iot.10086.cn/doc/iot_platform/book/device-connect&manager/device-auth.html#%E8%AE%BE%E5%A4%87%E5%AE%89%E5%85%A8%E8%AE%A4%E8%AF%81 物模型: https://open.iot.10086.cn/doc/iot_platform/book/device-connect&manager/thing-model/introduce.html OneOS仓库: https://gitee.com/cmcc-oneos/OneOS OneOS官网: https://os.iot.10086.cn/download/ OneOS-Cube环境搭建: https://os.iot.10086.cn/doc/quick_start/setup_env/oneos_cube.html 万耦开发板开发文档: https://os.iot.10086.cn/doc/hardware_support/oneos_dev_board.html Sensor设备用户开发: https://os.iot.10086.cn/doc/user_guide/driver/sensor.html 任务管理及调度: https://os.iot.10086.cn/doc/api_refer/kernel/task.html 消息队列: https://os.iot.10086.cn/doc/api_refer/kernel/mq.html MQTT用户编程手册: https://os.iot.10086.cn/doc/user_guide/components/ug_pahomqtt.html cJSON: https://github.com/DaveGamble/cJSON Molink用户编程手册: https://os.iot.10086.cn/doc/user_guide/components/ug_molink_v1.1.0.html MQTT通信主题: https://open.iot.10086.cn/doc/iot_platform/book/device-connect&manager/MQTT/topic.html#2 作者: 柏灵 中移OneOS