OpenHarmony南向设备开发中的JSON&MQTT基础
一、JSON基础
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。
JSON优点:数据格式比较简单,易于读写。
JSON主要有3种结构:
- JSON对象
- JSON数组
- JSON对象和数组嵌套
JSON对象简单而言便是键值对或名值对,而“值”可以是数值、字符串和布尔类型等。
JSON对象具体格式如下图1所示。 以 { } 包围。
JSON数组的表达方法和C语言数组的表达方法完全相同。以 [ ] 包围。
JSON嵌套就是JSON对象中可包括JSON数组,JSON数组中可包括JSON对象,值中可包括JSON对象。
华为云IoTDA平台下方的命令即是一个JSON对象,在根对象内包含三个string:value键值对,其中“paras"键对应的值是一个对象:
理解了JSON基础语法后,再看下面代码对上述JSON对象的抽丝剥茧,逻辑就很清晰了。
cJSON *root = cJSON_ParseWithLength(data->message->payload, data->message->payloadlen);
if(root != NULL){
cJSON *serv_id_obj = cJSON_GetObjectItem(root, "service_id"); //根对象的object item
if(serv_id_obj!= NULL){
char *serv_id_str = cJSON_GetStringValue(serv_id_obj);
if(strcmp(serv_id_str,"fan_service") == 0){
cJSON *cmd_name = cJSON_GetObjectItem(root, "command_name"); //根对象的object item
if(cmd_name != NULL){
char *cmd_name_str = cJSON_GetStringValue(cmd_name);
if(strcmp(cmd_name_str,"motor_cmd") == 0){
cJSON *para_obj = cJSON_GetObjectItem(root, "paras"); //根对象的object item
if(para_obj != NULL){
cJSON *level_obj = cJSON_GetObjectItem(para_obj,"motor_level"); //嵌套子对象的object item
if(level_obj != NULL){
int motor_level = (int)cJSON_GetNumberValue(level_obj);
//contrl motor run
motor_speed(motor_level);
}
}
}
}
}
}
}
二、MQTT基础
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输), 是IBM开发的一个即时通讯协议。
MQTT协议是为计算能力有限,且工作在低带宽、不可靠的网络的传感器和控制设备通讯而设计的协议,它有以下主要特性:
- 特别轻量级,使用一个8位的系统、30KB的空间,就可以运行MQTT的客户端
- 使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合
- 对负载内容屏蔽的消息传输
- 使用 TCP/IP 提供网络连接
- 有消息发布服务质量(QoS)机制,用户可根据应用场景需要选择:
- “至多一次”,消息发布完全依赖底层TCP/IP网络。会发生消息丢失或重复
- “至少一次”,确保消息到达,但消息重复可能会发生
- “只有一次”,确保消息到达一次
- 小型传输,开销很小(固定长度的头部是 2 字节),以降低网络流量
- 使用 “Last Will and Testament(最后的遗嘱)” 特性保证在客户端异常断开时实施各种策略
MQTT包括客户端、代理(broker)两部分.以智能家居系统为例,智能家电和手机为客户端,云端为代理。客户端首先向代理发起请求,代理收到后对客户端进行认证,认证通过后在客户端与代理之间建立一个TCP长连接通道,客户端通过该通道订阅若干关注的主题(Topic),同时在自身状态变化时,向相应的主题发布消息,代理将该消息发给正在订阅该主题的所有客户端。
三、OpenHarmony南向设备实践
1.在学习OpenHarmony南向设备开发学习时,可以借助华为云物联网平台IoTDA进行云端与智能设备的MQTT通讯功能实践。
2.使用开源c库cJSON
https://gitee.com/DaveGamble/cJSON
/* The cJSON structure: */
typedef struct cJSON
{
struct cJSON *next;
struct cJSON *prev;
struct cJSON *child;
/* The type of the item, as above. */
int type;
char *valuestring;
int valueint;
double valuedouble;
char *string;
} cJSON;
使用该库的重要注意事项:
cJSON的所有操作都是基于链表,在使用过程中大量使用malloc从堆中分配动态内存。在使用完之后,应当及时清空cJSON指针所指向的内存。
- 调用cJSON_Delete(cJSON *item)删除一个JSON对象数据时,如果有嵌套,会递归删除子对象数据
- 调用cJSON_Print(const cJSON *item) 或 cJSON_PrintUnformatted(const cJSON *item)函数将JSON对象转化为字符串时,同样会申请动态内存,也需要在使用完后,显式调用cJSON_free(void *object)函数释放内存。
3.使用开源c库paho.mqtt
https://github.com/eclipse/paho.mqtt.embedded-c
该库包含三个子目录(子工程):
- MQTTPacket - simple de/serialization of MQTT packets, plus helper functions
- MQTTClient - higher level C++ client
- MQTTClient-C - higher level C client
static void MQTTDemoTask(void)
{
int rc, count = 0;
WifiConnect(WIFI_SSID, WIFI_PWD);
NetworkInit(&network);
begin:
NetworkConnect(&network, HOST_ADDR, 1883);
MQTTClientInit(&client, &network, 2000, sendBuf, sizeof(sendBuf), readBuf, sizeof(readBuf));
MQTTString userName = MQTTString_initializer;
userName.cstring = "test_fan_01"; //deviceID
//clientId & password are generated by IoTDA website using DeviceId & DeviceSecret
MQTTString clientId = MQTTString_initializer;
clientId.cstring = "test_fan_01_0_0_2022073006"; //MQTT ClientId
MQTTString password = MQTTString_initializer;
password.cstring = "1d8581020a508d7854d1be17e8dab075fcb70e6364b99f6a916374cb212041ab";
MQTTPacket_connectData data = MQTTPacket_connectData_initializer;
data.clientID = clientId;
data.username = userName;
data.password = password;
data.willFlag = 0;
data.MQTTVersion = 4;
data.keepAliveInterval = 60;
data.cleansession = 1;
printf("MQTTConnect ...\n");
rc = MQTTConnect(&client, &data);
if (rc != 0) {
//连接错误处理
NetworkDisconnect(&network);
MQTTDisconnect(&client);
goto begin;
}
printf("MQTTSubscribe cloud cmd topic ...\n");
rc = MQTTSubscribe(&client, SUBCRIB_TOPIC, 0, messageArrived);
if (rc != 0) {
//订购错误处理
goto begin;
}
while (true) {
MQTTMessage message;
char *publishtopic="$oc/devices/test_fan_01/sys/properties/report";
char payload[300]={0};
//将上传数据打包为JSON对象
cJSON *root = cJSON_CreateObject();
if (root !=NULL) {
cJSON *serv_arr = cJSON_AddArrayToObject(root, "services");
cJSON *arr_item = cJSON_CreateObject();
cJSON_AddStringToObject(arr_item,"service_id","fan_service");
cJSON *data_obj = cJSON_CreateObject();
cJSON_AddItemToObject(arr_item, "properties", data_obj);
cJSON_AddNumberToObject(data_obj,"temperature",temp_val);
cJSON_AddNumberToObject(data_obj,"humidity",humi_val);
cJSON_AddItemToArray(serv_arr, arr_item);
char *payload_str =cJSON_PrintUnformatted(root);
strcpy(payload,payload_str);
//释放cJSON相关内存及对象
cJSON_free(payload_str);
cJSON_Delete(root);
}
//publish temperature & humidity data to cloud
message.qos = 0;
message.retained = 0;
message.payload = payload;
message.payloadlen = strlen(payload);
if ((rc = MQTTPublish(&client, publishtopic, &message)) != 0) {
//publish 错误处理
NetworkDisconnect(&network);
MQTTDisconnect(&client);
goto begin;
}
MQTTYield(&client, 5000);
}
}
太好了,思路很清晰,先理论,再实战,手动点赞