更新硬件SDK

This commit is contained in:
kerwincui
2023-03-04 03:44:56 +08:00
parent dcdf6e1b7c
commit e39d3d2f03
1900 changed files with 663153 additions and 0 deletions

View File

@@ -0,0 +1,330 @@
/*
* 这个例子主要用于演示如何从设备端获取存储在云端的关于这个设备的远程配置
*
* 需要用户关注或修改的部分, 已用 `TODO` 在注释中标明
*
*/
/* TODO: 本例子用到了sleep函数, 所以包含了unistd.h. 如果用户自己的库中有可以替代的函数, 则可以将unistd.h替换掉
*
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "aiot_state_api.h"
#include "aiot_sysdep_api.h"
#include "aiot_mqtt_api.h"
#include "aiot_ota_api.h"
/* TODO: 替换为自己设备的三元组 */
char *product_key = "${YourProductKey}";
char *device_name = "${YourDeviceName}";
char *device_secret = "${YourDeviceSecret}";
/*
TODO: 替换为自己实例的接入点
对于企业实例, 或者2021年07月30日之后含当日开通的物联网平台服务下公共实例
mqtt_host的格式为"${YourInstanceId}.mqtt.iothub.aliyuncs.com"
其中${YourInstanceId}: 请替换为您企业/公共实例的Id
对于2021年07月30日之前不含当日开通的物联网平台服务下公共实例
需要将mqtt_host修改为: mqtt_host = "${YourProductKey}.iot-as-mqtt.${YourRegionId}.aliyuncs.com"
其中, ${YourProductKey}请替换为设备所属产品的ProductKey。可登录物联网平台控制台在对应实例的设备详情页获取。
${YourRegionId}:请替换为您的物联网平台设备所在地域代码, 比如cn-shanghai等
该情况下完整mqtt_host举例: a1TTmBPIChA.iot-as-mqtt.cn-shanghai.aliyuncs.com
详情请见: https://help.aliyun.com/document_detail/147356.html
*/
char *mqtt_host = "${YourInstanceId}.mqtt.iothub.aliyuncs.com";
/* 位于external/ali_ca_cert.c中的服务器证书 */
extern const char *ali_ca_cert;
void *g_dl_handle = NULL;
/* 位于portfiles/aiot_port文件夹下的系统适配函数集合 */
extern aiot_sysdep_portfile_t g_aiot_sysdep_portfile;
/* TODO: 如果要关闭日志, 就把这个函数实现为空, 如果要减少日志, 可根据code选择不打印
*
* 例如: [1578463098.611][LK-0309] pub: /ota/device/upgrade/a13FN5TplKq/ota_demo
*
* 上面这条日志的code就是0309(十六进制), code值的定义见core/aiot_state_api.h
*
*/
/* 日志回调函数, SDK的日志会从这里输出 */
int32_t demo_state_logcb(int32_t code, char *message)
{
/* 下载远程配置的时候会有大量的HTTP收包日志, 通过code筛选出来关闭 */
if (STATE_HTTP_LOG_RECV_CONTENT != code) {
printf("%s", message);
}
return 0;
}
/* MQTT事件回调函数, 当网络连接/重连/断开时被触发, 事件定义见core/aiot_mqtt_api.h */
void demo_mqtt_event_handler(void *handle, const aiot_mqtt_event_t *const event, void *userdata)
{
switch (event->type) {
/* SDK因为用户调用了aiot_mqtt_connect()接口, 与mqtt服务器建立连接已成功 */
case AIOT_MQTTEVT_CONNECT: {
printf("AIOT_MQTTEVT_CONNECT\r\n");
/* TODO: 处理SDK建连成功, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
/* SDK因为网络状况被动断连后, 自动发起重连已成功 */
case AIOT_MQTTEVT_RECONNECT: {
printf("AIOT_MQTTEVT_RECONNECT\r\n");
/* TODO: 处理SDK重连成功, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
/* SDK因为网络的状况而被动断开了连接, network是底层读写失败, heartbeat是没有按预期得到服务端心跳应答 */
case AIOT_MQTTEVT_DISCONNECT: {
char *cause = (event->data.disconnect == AIOT_MQTTDISCONNEVT_NETWORK_DISCONNECT) ? ("network disconnect") :
("heartbeat disconnect");
printf("AIOT_MQTTEVT_DISCONNECT: %s\r\n", cause);
/* TODO: 处理SDK被动断连, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
default: {
}
}
}
/* MQTT默认消息处理回调, 当SDK从服务器收到MQTT消息时, 且无对应用户回调处理时被调用 */
void demo_mqtt_default_recv_handler(void *handle, const aiot_mqtt_recv_t *const packet, void *userdata)
{
switch (packet->type) {
case AIOT_MQTTRECV_HEARTBEAT_RESPONSE: {
printf("heartbeat response\r\n");
/* TODO: 处理服务器对心跳的回应, 一般不处理 */
}
break;
case AIOT_MQTTRECV_SUB_ACK: {
printf("suback, res: -0x%04X, packet id: %d, max qos: %d\r\n",
-packet->data.sub_ack.res, packet->data.sub_ack.packet_id, packet->data.sub_ack.max_qos);
/* TODO: 处理服务器对订阅请求的回应, 一般不处理 */
}
break;
case AIOT_MQTTRECV_PUB: {
printf("pub, qos: %d, topic: %.*s\r\n", packet->data.pub.qos, packet->data.pub.topic_len, packet->data.pub.topic);
printf("pub, payload: %.*s\r\n", packet->data.pub.payload_len, packet->data.pub.payload);
/* TODO: 处理服务器下发的业务报文 */
}
break;
case AIOT_MQTTRECV_PUB_ACK: {
printf("puback, packet id: %d\r\n", packet->data.pub_ack.packet_id);
/* TODO: 处理服务器对QoS1上报消息的回应, 一般不处理 */
}
break;
default: {
}
}
}
/* 下载收包回调, 用户调用 aiot_download_recv() 后, SDK收到数据会进入这个函数, 把下载到的数据交给用户 */
/* TODO: 在用户收到远程配置的数据后, 需要自己决定如何处理 */
void demo_download_recv_handler(void *handle, const aiot_download_recv_t *packet, void *userdata)
{
/* 目前只支持 packet->type 为 AIOT_DLRECV_HTTPBODY 的情况 */
if (!packet || AIOT_DLRECV_HTTPBODY != packet->type) {
return;
}
int32_t percent = packet->data.percent;
uint8_t *src_buffer = packet->data.buffer;
uint32_t src_buffer_len = packet->data.len;
/* 如果 percent 为负数, 说明发生了收包异常或者digest校验错误 */
if (percent < 0) {
/* 收包异常或者digest校验错误 */
printf("exception happend, percent is %d\r\n", percent);
return;
}
aiot_download_report_progress(handle, percent);
printf("config len is %d, config content is %.*s\r\n", src_buffer_len, src_buffer_len, (char *)src_buffer);
}
/* 用户通过 aiot_ota_setopt() 注册的OTA消息处理回调, 如果SDK收到了OTA相关的MQTT消息, 会自动识别, 调用这个回调函数 */
void demo_ota_recv_handler(void *ota_handle, aiot_ota_recv_t *ota_msg, void *userdata)
{
switch (ota_msg->type) {
case AIOT_OTARECV_COTA: {
uint16_t port = 443;
uint32_t max_buffer_len = 2048;
aiot_sysdep_network_cred_t cred;
void *dl_handle = aiot_download_init();
if (NULL == dl_handle || NULL == ota_msg->task_desc || ota_msg->task_desc->protocol_type != AIOT_OTA_PROTOCOL_HTTPS) {
return;
}
printf("configId: %s, configSize: %u Bytes\r\n", ota_msg->task_desc->version,
ota_msg->task_desc->size_total);
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA;
cred.max_tls_fragment = 16384;
cred.x509_server_cert = ali_ca_cert;
cred.x509_server_cert_len = strlen(ali_ca_cert);
/* 设置下载时为TLS下载 */
if ((STATE_SUCCESS != aiot_download_setopt(dl_handle, AIOT_DLOPT_NETWORK_CRED, (void *)(&cred))) ||
/* 设置下载时访问的服务器端口号 */
(STATE_SUCCESS != aiot_download_setopt(dl_handle, AIOT_DLOPT_NETWORK_PORT, (void *)(&port))) ||
/* 设置下载的任务信息, 通过输入参数 ota_msg 中的 task_desc 成员得到, 内含下载地址, 远程配置的大小和版本号*/
(STATE_SUCCESS != aiot_download_setopt(dl_handle, AIOT_DLOPT_TASK_DESC, (void *)(ota_msg->task_desc))) ||
/* 设置下载内容到达时, SDK将调用的回调函数 */
(STATE_SUCCESS != aiot_download_setopt(dl_handle, AIOT_DLOPT_RECV_HANDLER, (void *)(demo_download_recv_handler))) ||
/* 设置单次下载最大的buffer长度, 每当这个长度的内存读满了后会通知用户 */
(STATE_SUCCESS != aiot_download_setopt(dl_handle, AIOT_DLOPT_BODY_BUFFER_MAX_LEN, (void *)(&max_buffer_len))) ||
/* 发送http的GET请求给http服务器 */
(STATE_SUCCESS != aiot_download_send_request(dl_handle))) {
aiot_download_deinit(&dl_handle);
break;
}
g_dl_handle = dl_handle;
break;
}
default:
break;
}
}
int main(int argc, char *argv[])
{
int32_t res = STATE_SUCCESS;
void *mqtt_handle = NULL;
uint16_t port = 443; /* 无论设备是否使用TLS连接阿里云平台, 目的端口都是443 */
aiot_sysdep_network_cred_t cred; /* 安全凭据结构体, 如果要用TLS, 这个结构体中配置CA证书等参数 */
void *ota_handle = NULL;
uint32_t timeout_ms = 0;
/* 配置SDK的底层依赖 */
aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
/* 配置SDK的日志输出 */
aiot_state_set_logcb(demo_state_logcb);
/* 创建SDK的安全凭据, 用于建立TLS连接 */
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA; /* 使用RSA证书校验MQTT服务端 */
cred.max_tls_fragment = 16384; /* 最大的分片长度为16K, 其它可选值还有4K, 2K, 1K, 0.5K */
cred.sni_enabled = 1; /* TLS建连时, 支持Server Name Indicator */
cred.x509_server_cert = ali_ca_cert; /* 用来验证MQTT服务端的RSA根证书 */
cred.x509_server_cert_len = strlen(ali_ca_cert); /* 用来验证MQTT服务端的RSA根证书长度 */
/* 创建1个MQTT客户端实例并内部初始化默认参数 */
mqtt_handle = aiot_mqtt_init();
if (mqtt_handle == NULL) {
return -1;
}
/* 配置MQTT服务器地址 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_HOST, (void *)mqtt_host);
/* 配置MQTT服务器端口 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PORT, (void *)&port);
/* 配置设备productKey */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PRODUCT_KEY, (void *)product_key);
/* 配置设备deviceName */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_NAME, (void *)device_name);
/* 配置设备deviceSecret */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_SECRET, (void *)device_secret);
/* 配置网络连接的安全凭据, 上面已经创建好了 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_NETWORK_CRED, (void *)&cred);
/* 配置MQTT默认消息接收回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_RECV_HANDLER, (void *)demo_mqtt_default_recv_handler);
/* 配置MQTT事件回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_EVENT_HANDLER, (void *)demo_mqtt_event_handler);
/* 与MQTT例程不同的是, 这里需要增加创建OTA会话实例的语句 */
ota_handle = aiot_ota_init();
if (NULL == ota_handle) {
goto exit;
}
/* 用以下语句, 把OTA会话和MQTT会话关联起来 */
aiot_ota_setopt(ota_handle, AIOT_OTAOPT_MQTT_HANDLE, mqtt_handle);
/* 用以下语句, 设置OTA会话的数据接收回调, SDK收到OTA相关推送时, 会进入这个回调函数 */
aiot_ota_setopt(ota_handle, AIOT_OTAOPT_RECV_HANDLER, demo_ota_recv_handler);
/* 与服务器建立MQTT连接 */
res = aiot_mqtt_connect(mqtt_handle);
if (res < STATE_SUCCESS) {
printf("aiot_mqtt_connect failed: -0x%04X\r\n\r\n", -res);
printf("please check variables like mqtt_host, produt_key, device_name, device_secret in demo\r\n");
/* 尝试建立连接失败, 销毁MQTT实例, 回收资源 */
goto exit;
}
/* 发送请求, 获取云端的远程配置. TODO:替换自己设备的productKey, deviceName */
/*
{
char *topic_string = "/sys/{YourProductKey}/{YourDeviceName}/thing/config/get";
char *payload_string = "{\"id\":\"123\",\"params\":{\"configScope\":\"product\",\"getType\":\"file\"}}";
res = aiot_mqtt_pub(mqtt_handle, topic_string, (uint8_t *)payload_string, strlen(payload_string), 0);
if (res < STATE_SUCCESS) {
printf("aiot_mqtt_pub failed: -0x%04X\r\n", -res);
goto exit;
}
}
*/
while (1) {
aiot_mqtt_process(mqtt_handle);
res = aiot_mqtt_recv(mqtt_handle);
if (res == STATE_SYS_DEPEND_NWK_CLOSED) {
sleep(1);
}
if (NULL != g_dl_handle) {
/* 完成远程配置的接收前, 将mqtt的收包超时调整到100ms, 以减少两次远程配置的下载间隔*/
int32_t ret = aiot_download_recv(g_dl_handle);
timeout_ms = 100;
if (STATE_DOWNLOAD_FINISHED == ret) {
aiot_download_deinit(&g_dl_handle);
/* 完成远程配置的接收后, 将mqtt的收包超时调整回到默认值5000ms */
timeout_ms = 5000;
}
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_RECV_TIMEOUT_MS, (void *)&timeout_ms);
}
}
/* 断开MQTT连接, 一般不会运行到这里 */
res = aiot_mqtt_disconnect(mqtt_handle);
if (res < STATE_SUCCESS) {
printf("aiot_mqtt_disconnect failed: -0x%04X\r\n", -res);
goto exit;
}
exit:
while (1) {
/* 销毁MQTT实例, 一般不会运行到这里 */
res = aiot_mqtt_deinit(&mqtt_handle);
if (res < STATE_SUCCESS) {
printf("aiot_mqtt_deinit failed: -0x%04X\r\n", -res);
return -1;
} else {
break;
}
}
/* 销毁OTA实例, 一般不会运行到这里 */
aiot_ota_deinit(&ota_handle);
return 0;
}

View File

@@ -0,0 +1,533 @@
/*
* 这个例程适用于`Linux`这类支持pthread的POSIX设备, 它演示了用SDK配置MQTT参数并建立连接, 之后创建2个线程
*
* + 一个线程用于保活长连接
* + 一个线程用于接收消息, 并在有消息到达时进入默认的数据回调, 在连接状态变化时进入事件回调
*
* 接着演示了在MQTT连接上进行属性上报, 事件上报, 以及处理收到的属性设置, 服务调用, 取消这些代码段落的注释即可观察运行效果
*
* 需要用户关注或修改的部分, 已经用 TODO 在注释中标明
*
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include "aiot_state_api.h"
#include "aiot_sysdep_api.h"
#include "aiot_mqtt_api.h"
#include "aiot_dm_api.h"
/* TODO: 替换为自己设备的三元组 */
char *product_key = "${YourProductKey}";
char *device_name = "${YourDeviceName}";
char *device_secret = "${YourDeviceSecret}";
/*
TODO: 替换为自己实例的接入点
对于企业实例, 或者2021年07月30日之后含当日开通的物联网平台服务下公共实例
mqtt_host的格式为"${YourInstanceId}.mqtt.iothub.aliyuncs.com"
其中${YourInstanceId}: 请替换为您企业/公共实例的Id
对于2021年07月30日之前不含当日开通的物联网平台服务下公共实例
需要将mqtt_host修改为: mqtt_host = "${YourProductKey}.iot-as-mqtt.${YourRegionId}.aliyuncs.com"
其中, ${YourProductKey}请替换为设备所属产品的ProductKey。可登录物联网平台控制台在对应实例的设备详情页获取。
${YourRegionId}:请替换为您的物联网平台设备所在地域代码, 比如cn-shanghai等
该情况下完整mqtt_host举例: a1TTmBPIChA.iot-as-mqtt.cn-shanghai.aliyuncs.com
详情请见: https://help.aliyun.com/document_detail/147356.html
*/
char *mqtt_host = "${YourInstanceId}.mqtt.iothub.aliyuncs.com";
/* 位于portfiles/aiot_port文件夹下的系统适配函数集合 */
extern aiot_sysdep_portfile_t g_aiot_sysdep_portfile;
/* 位于external/ali_ca_cert.c中的服务器证书 */
extern const char *ali_ca_cert;
static pthread_t g_mqtt_process_thread;
static pthread_t g_mqtt_recv_thread;
static uint8_t g_mqtt_process_thread_running = 0;
static uint8_t g_mqtt_recv_thread_running = 0;
/* TODO: 如果要关闭日志, 就把这个函数实现为空, 如果要减少日志, 可根据code选择不打印
*
* 上面这条日志的code就是0317(十六进制), code值的定义见core/aiot_state_api.h
*
*/
/* 日志回调函数, SDK的日志会从这里输出 */
int32_t demo_state_logcb(int32_t code, char *message)
{
printf("%s", message);
return 0;
}
/* MQTT事件回调函数, 当网络连接/重连/断开时被触发, 事件定义见core/aiot_mqtt_api.h */
void demo_mqtt_event_handler(void *handle, const aiot_mqtt_event_t *event, void *userdata)
{
switch (event->type) {
/* SDK因为用户调用了aiot_mqtt_connect()接口, 与mqtt服务器建立连接已成功 */
case AIOT_MQTTEVT_CONNECT: {
printf("AIOT_MQTTEVT_CONNECT\n");
}
break;
/* SDK因为网络状况被动断连后, 自动发起重连已成功 */
case AIOT_MQTTEVT_RECONNECT: {
printf("AIOT_MQTTEVT_RECONNECT\n");
}
break;
/* SDK因为网络的状况而被动断开了连接, network是底层读写失败, heartbeat是没有按预期得到服务端心跳应答 */
case AIOT_MQTTEVT_DISCONNECT: {
char *cause = (event->data.disconnect == AIOT_MQTTDISCONNEVT_NETWORK_DISCONNECT) ? ("network disconnect") :
("heartbeat disconnect");
printf("AIOT_MQTTEVT_DISCONNECT: %s\n", cause);
}
break;
default: {
}
}
}
/* 执行aiot_mqtt_process的线程, 包含心跳发送和QoS1消息重发 */
void *demo_mqtt_process_thread(void *args)
{
int32_t res = STATE_SUCCESS;
while (g_mqtt_process_thread_running) {
res = aiot_mqtt_process(args);
if (res == STATE_USER_INPUT_EXEC_DISABLED) {
break;
}
sleep(1);
}
return NULL;
}
/* 执行aiot_mqtt_recv的线程, 包含网络自动重连和从服务器收取MQTT消息 */
void *demo_mqtt_recv_thread(void *args)
{
int32_t res = STATE_SUCCESS;
while (g_mqtt_recv_thread_running) {
res = aiot_mqtt_recv(args);
if (res < STATE_SUCCESS) {
if (res == STATE_USER_INPUT_EXEC_DISABLED) {
break;
}
sleep(1);
}
}
return NULL;
}
static void demo_dm_recv_generic_reply(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata)
{
printf("demo_dm_recv_generic_reply msg_id = %d, code = %d, data = %.*s, message = %.*s\r\n",
recv->data.generic_reply.msg_id,
recv->data.generic_reply.code,
recv->data.generic_reply.data_len,
recv->data.generic_reply.data,
recv->data.generic_reply.message_len,
recv->data.generic_reply.message);
}
static void demo_dm_recv_property_set(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata)
{
printf("demo_dm_recv_property_set msg_id = %ld, params = %.*s\r\n",
(unsigned long)recv->data.property_set.msg_id,
recv->data.property_set.params_len,
recv->data.property_set.params);
/* TODO: 以下代码演示如何对来自云平台的属性设置指令进行应答, 用户可取消注释查看演示效果 */
/*
{
aiot_dm_msg_t msg;
memset(&msg, 0, sizeof(aiot_dm_msg_t));
msg.type = AIOT_DMMSG_PROPERTY_SET_REPLY;
msg.data.property_set_reply.msg_id = recv->data.property_set.msg_id;
msg.data.property_set_reply.code = 200;
msg.data.property_set_reply.data = "{}";
int32_t res = aiot_dm_send(dm_handle, &msg);
if (res < 0) {
printf("aiot_dm_send failed\r\n");
}
}
*/
}
static void demo_dm_recv_async_service_invoke(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata)
{
printf("demo_dm_recv_async_service_invoke msg_id = %ld, service_id = %s, params = %.*s\r\n",
(unsigned long)recv->data.async_service_invoke.msg_id,
recv->data.async_service_invoke.service_id,
recv->data.async_service_invoke.params_len,
recv->data.async_service_invoke.params);
/* TODO: 以下代码演示如何对来自云平台的异步服务调用进行应答, 用户可取消注释查看演示效果
*
* 注意: 如果用户在回调函数外进行应答, 需要自行保存msg_id, 因为回调函数入参在退出回调函数后将被SDK销毁, 不可以再访问到
*/
/*
{
aiot_dm_msg_t msg;
memset(&msg, 0, sizeof(aiot_dm_msg_t));
msg.type = AIOT_DMMSG_ASYNC_SERVICE_REPLY;
msg.data.async_service_reply.msg_id = recv->data.async_service_invoke.msg_id;
msg.data.async_service_reply.code = 200;
msg.data.async_service_reply.service_id = "ToggleLightSwitch";
msg.data.async_service_reply.data = "{\"dataA\": 20}";
int32_t res = aiot_dm_send(dm_handle, &msg);
if (res < 0) {
printf("aiot_dm_send failed\r\n");
}
}
*/
}
static void demo_dm_recv_sync_service_invoke(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata)
{
printf("demo_dm_recv_sync_service_invoke msg_id = %ld, rrpc_id = %s, service_id = %s, params = %.*s\r\n",
(unsigned long)recv->data.sync_service_invoke.msg_id,
recv->data.sync_service_invoke.rrpc_id,
recv->data.sync_service_invoke.service_id,
recv->data.sync_service_invoke.params_len,
recv->data.sync_service_invoke.params);
/* TODO: 以下代码演示如何对来自云平台的同步服务调用进行应答, 用户可取消注释查看演示效果
*
* 注意: 如果用户在回调函数外进行应答, 需要自行保存msg_id和rrpc_id字符串, 因为回调函数入参在退出回调函数后将被SDK销毁, 不可以再访问到
*/
/*
{
aiot_dm_msg_t msg;
memset(&msg, 0, sizeof(aiot_dm_msg_t));
msg.type = AIOT_DMMSG_SYNC_SERVICE_REPLY;
msg.data.sync_service_reply.rrpc_id = recv->data.sync_service_invoke.rrpc_id;
msg.data.sync_service_reply.msg_id = recv->data.sync_service_invoke.msg_id;
msg.data.sync_service_reply.code = 200;
msg.data.sync_service_reply.service_id = "SetLightSwitchTimer";
msg.data.sync_service_reply.data = "{}";
int32_t res = aiot_dm_send(dm_handle, &msg);
if (res < 0) {
printf("aiot_dm_send failed\r\n");
}
}
*/
}
static void demo_dm_recv_raw_data(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata)
{
printf("demo_dm_recv_raw_data raw data len = %d\r\n", recv->data.raw_data.data_len);
/* TODO: 以下代码演示如何发送二进制格式数据, 若使用需要有相应的数据透传脚本部署在云端 */
/*
{
aiot_dm_msg_t msg;
uint8_t raw_data[] = {0x01, 0x02};
memset(&msg, 0, sizeof(aiot_dm_msg_t));
msg.type = AIOT_DMMSG_RAW_DATA;
msg.data.raw_data.data = raw_data;
msg.data.raw_data.data_len = sizeof(raw_data);
aiot_dm_send(dm_handle, &msg);
}
*/
}
static void demo_dm_recv_raw_sync_service_invoke(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata)
{
printf("demo_dm_recv_raw_sync_service_invoke raw sync service rrpc_id = %s, data_len = %d\r\n",
recv->data.raw_service_invoke.rrpc_id,
recv->data.raw_service_invoke.data_len);
}
static void demo_dm_recv_raw_data_reply(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata)
{
printf("demo_dm_recv_raw_data_reply receive reply for up_raw msg, data len = %d\r\n", recv->data.raw_data.data_len);
/* TODO: 用户处理下行的二进制数据, 位于recv->data.raw_data.data中 */
}
/* 用户数据接收处理回调函数 */
static void demo_dm_recv_handler(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata)
{
printf("demo_dm_recv_handler, type = %d\r\n", recv->type);
switch (recv->type) {
/* 属性上报, 事件上报, 获取期望属性值或者删除期望属性值的应答 */
case AIOT_DMRECV_GENERIC_REPLY: {
demo_dm_recv_generic_reply(dm_handle, recv, userdata);
}
break;
/* 属性设置 */
case AIOT_DMRECV_PROPERTY_SET: {
demo_dm_recv_property_set(dm_handle, recv, userdata);
}
break;
/* 异步服务调用 */
case AIOT_DMRECV_ASYNC_SERVICE_INVOKE: {
demo_dm_recv_async_service_invoke(dm_handle, recv, userdata);
}
break;
/* 同步服务调用 */
case AIOT_DMRECV_SYNC_SERVICE_INVOKE: {
demo_dm_recv_sync_service_invoke(dm_handle, recv, userdata);
}
break;
/* 下行二进制数据 */
case AIOT_DMRECV_RAW_DATA: {
demo_dm_recv_raw_data(dm_handle, recv, userdata);
}
break;
/* 二进制格式的同步服务调用, 比单纯的二进制数据消息多了个rrpc_id */
case AIOT_DMRECV_RAW_SYNC_SERVICE_INVOKE: {
demo_dm_recv_raw_sync_service_invoke(dm_handle, recv, userdata);
}
break;
/* 上行二进制数据后, 云端的回复报文 */
case AIOT_DMRECV_RAW_DATA_REPLY: {
demo_dm_recv_raw_data_reply(dm_handle, recv, userdata);
}
break;
default:
break;
}
}
/* 属性上报函数演示 */
int32_t demo_send_property_post(void *dm_handle, char *params)
{
aiot_dm_msg_t msg;
memset(&msg, 0, sizeof(aiot_dm_msg_t));
msg.type = AIOT_DMMSG_PROPERTY_POST;
msg.data.property_post.params = params;
return aiot_dm_send(dm_handle, &msg);
}
int32_t demo_send_property_batch_post(void *dm_handle, char *params)
{
aiot_dm_msg_t msg;
memset(&msg, 0, sizeof(aiot_dm_msg_t));
msg.type = AIOT_DMMSG_PROPERTY_BATCH_POST;
msg.data.property_post.params = params;
return aiot_dm_send(dm_handle, &msg);
}
/* 事件上报函数演示 */
int32_t demo_send_event_post(void *dm_handle, char *event_id, char *params)
{
aiot_dm_msg_t msg;
memset(&msg, 0, sizeof(aiot_dm_msg_t));
msg.type = AIOT_DMMSG_EVENT_POST;
msg.data.event_post.event_id = event_id;
msg.data.event_post.params = params;
return aiot_dm_send(dm_handle, &msg);
}
/* 演示了获取属性LightSwitch的期望值, 用户可将此函数加入到main函数中运行演示 */
int32_t demo_send_get_desred_requset(void *dm_handle)
{
aiot_dm_msg_t msg;
memset(&msg, 0, sizeof(aiot_dm_msg_t));
msg.type = AIOT_DMMSG_GET_DESIRED;
msg.data.get_desired.params = "[\"LightSwitch\"]";
return aiot_dm_send(dm_handle, &msg);
}
/* 演示了删除属性LightSwitch的期望值, 用户可将此函数加入到main函数中运行演示 */
int32_t demo_send_delete_desred_requset(void *dm_handle)
{
aiot_dm_msg_t msg;
memset(&msg, 0, sizeof(aiot_dm_msg_t));
msg.type = AIOT_DMMSG_DELETE_DESIRED;
msg.data.get_desired.params = "{\"LightSwitch\":{}}";
return aiot_dm_send(dm_handle, &msg);
}
int main(int argc, char *argv[])
{
int32_t res = STATE_SUCCESS;
void *dm_handle = NULL;
void *mqtt_handle = NULL;
uint16_t port = 443; /* 无论设备是否使用TLS连接阿里云平台, 目的端口都是443 */
aiot_sysdep_network_cred_t cred; /* 安全凭据结构体, 如果要用TLS, 这个结构体中配置CA证书等参数 */
uint8_t post_reply = 1;
/* 配置SDK的底层依赖 */
aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
/* 配置SDK的日志输出 */
aiot_state_set_logcb(demo_state_logcb);
/* 创建SDK的安全凭据, 用于建立TLS连接 */
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA; /* 使用RSA证书校验MQTT服务端 */
cred.max_tls_fragment = 16384; /* 最大的分片长度为16K, 其它可选值还有4K, 2K, 1K, 0.5K */
cred.sni_enabled = 1; /* TLS建连时, 支持Server Name Indicator */
cred.x509_server_cert = ali_ca_cert; /* 用来验证MQTT服务端的RSA根证书 */
cred.x509_server_cert_len = strlen(ali_ca_cert); /* 用来验证MQTT服务端的RSA根证书长度 */
/* 创建1个MQTT客户端实例并内部初始化默认参数 */
mqtt_handle = aiot_mqtt_init();
if (mqtt_handle == NULL) {
printf("aiot_mqtt_init failed\n");
return -1;
}
/* 配置MQTT服务器地址 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_HOST, (void *)mqtt_host);
/* 配置MQTT服务器端口 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PORT, (void *)&port);
/* 配置设备productKey */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PRODUCT_KEY, (void *)product_key);
/* 配置设备deviceName */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_NAME, (void *)device_name);
/* 配置设备deviceSecret */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_SECRET, (void *)device_secret);
/* 配置网络连接的安全凭据, 上面已经创建好了 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_NETWORK_CRED, (void *)&cred);
/* 配置MQTT事件回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_EVENT_HANDLER, (void *)demo_mqtt_event_handler);
/* 创建DATA-MODEL实例 */
dm_handle = aiot_dm_init();
if (dm_handle == NULL) {
printf("aiot_dm_init failed");
return -1;
}
/* 配置MQTT实例句柄 */
aiot_dm_setopt(dm_handle, AIOT_DMOPT_MQTT_HANDLE, mqtt_handle);
/* 配置消息接收处理回调函数 */
aiot_dm_setopt(dm_handle, AIOT_DMOPT_RECV_HANDLER, (void *)demo_dm_recv_handler);
/* 配置是云端否需要回复post_reply给设备. 如果为1, 表示需要云端回复, 否则表示不回复 */
aiot_dm_setopt(dm_handle, AIOT_DMOPT_POST_REPLY, (void *)&post_reply);
/* 与服务器建立MQTT连接 */
res = aiot_mqtt_connect(mqtt_handle);
if (res < STATE_SUCCESS) {
/* 尝试建立连接失败, 销毁MQTT实例, 回收资源 */
aiot_dm_deinit(&dm_handle);
aiot_mqtt_deinit(&mqtt_handle);
printf("aiot_mqtt_connect failed: -0x%04X\n\r\n", -res);
printf("please check variables like mqtt_host, produt_key, device_name, device_secret in demo\r\n");
return -1;
}
/* 向服务器订阅property/batch/post_reply这个topic */
aiot_mqtt_sub(mqtt_handle, "/sys/${YourProductKey}/${YourDeviceName}/thing/event/property/batch/post_reply", NULL, 1,
NULL);
/* 创建一个单独的线程, 专用于执行aiot_mqtt_process, 它会自动发送心跳保活, 以及重发QoS1的未应答报文 */
g_mqtt_process_thread_running = 1;
res = pthread_create(&g_mqtt_process_thread, NULL, demo_mqtt_process_thread, mqtt_handle);
if (res < 0) {
printf("pthread_create demo_mqtt_process_thread failed: %d\n", res);
aiot_dm_deinit(&dm_handle);
aiot_mqtt_disconnect(mqtt_handle);
aiot_mqtt_deinit(&mqtt_handle);
return -1;
}
/* 创建一个单独的线程用于执行aiot_mqtt_recv, 它会循环收取服务器下发的MQTT消息, 并在断线时自动重连 */
g_mqtt_recv_thread_running = 1;
res = pthread_create(&g_mqtt_recv_thread, NULL, demo_mqtt_recv_thread, mqtt_handle);
if (res < 0) {
printf("pthread_create demo_mqtt_recv_thread failed: %d\n", res);
aiot_dm_deinit(&dm_handle);
aiot_mqtt_disconnect(mqtt_handle);
aiot_mqtt_deinit(&mqtt_handle);
return -1;
}
/* 主循环进入休眠 */
while (1) {
/* TODO: 以下代码演示了简单的属性上报和事件上报, 用户可取消注释观察演示效果 */
demo_send_property_post(dm_handle, "{\"LightSwitch\": 0}");
/*
demo_send_event_post(dm_handle, "Error", "{\"ErrorCode\": 0}");
*/
/* TODO: 以下代码演示了基于模块的物模型的上报, 用户可取消注释观察演示效果
* 本例需要用户在产品的功能定义的页面中, 点击"编辑草稿", 增加一个名为demo_extra_block的模块,
* 再到该模块中, 通过添加标准功能, 选择一个名为NightLightSwitch的物模型属性, 再点击"发布上线".
* 有关模块化的物模型的概念, 请见 https://help.aliyun.com/document_detail/73727.html
*/
/*
demo_send_property_post(dm_handle, "{\"demo_extra_block:NightLightSwitch\": 1}");
*/
/* TODO: 以下代码显示批量上报用户数据, 用户可取消注释观察演示效果
* 具体数据格式请见https://help.aliyun.com/document_detail/89301.html 的"设备批量上报属性、事件"一节
*/
/*
demo_send_property_batch_post(dm_handle,
"{\"properties\":{\"Power\": [ {\"value\":\"on\",\"time\":1612684518}],\"WF\": [{\"value\": 3,\"time\":1612684518}]}}");
*/
sleep(5);
}
/* 停止收发动作 */
g_mqtt_process_thread_running = 0;
g_mqtt_recv_thread_running = 0;
/* 断开MQTT连接, 一般不会运行到这里 */
res = aiot_mqtt_disconnect(mqtt_handle);
if (res < STATE_SUCCESS) {
aiot_dm_deinit(&dm_handle);
aiot_mqtt_deinit(&mqtt_handle);
printf("aiot_mqtt_disconnect failed: -0x%04X\n", -res);
return -1;
}
/* 销毁DATA-MODEL实例, 一般不会运行到这里 */
res = aiot_dm_deinit(&dm_handle);
if (res < STATE_SUCCESS) {
printf("aiot_dm_deinit failed: -0x%04X\n", -res);
return -1;
}
/* 销毁MQTT实例, 一般不会运行到这里 */
res = aiot_mqtt_deinit(&mqtt_handle);
if (res < STATE_SUCCESS) {
printf("aiot_mqtt_deinit failed: -0x%04X\n", -res);
return -1;
}
pthread_join(g_mqtt_process_thread, NULL);
pthread_join(g_mqtt_recv_thread, NULL);
return 0;
}

View File

@@ -0,0 +1,432 @@
/*
* 这个例程适用于`Linux`这类支持pthread的POSIX设备, 它演示了用SDK配置MQTT参数并建立连接, 之后创建2个线程
*
* + 一个线程用于保活长连接
* + 一个线程用于接收消息, 并在有消息到达时进入默认的数据回调, 在连接状态变化时进入事件回调
*
* 接着在MQTT连接上发送设备标签更新或删除请求, 如果云平台的回应报文到达, 从接收线程会调用devinfo消息处理的回调函数, 把上报结果打印出来
*
* 需要用户关注或修改的部分, 已经用 TODO 在注释中标明
*
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include "aiot_state_api.h"
#include "aiot_sysdep_api.h"
#include "aiot_mqtt_api.h"
#include "aiot_devinfo_api.h"
/* TODO: 替换为自己设备的三元组 */
char *product_key = "${YourProductKey}";
char *device_name = "${YourDeviceName}";
char *device_secret = "${YourDeviceSecret}";
/*
TODO: 替换为自己实例的接入点
对于企业实例, 或者2021年07月30日之后含当日开通的物联网平台服务下公共实例
mqtt_host的格式为"${YourInstanceId}.mqtt.iothub.aliyuncs.com"
其中${YourInstanceId}: 请替换为您企业/公共实例的Id
对于2021年07月30日之前不含当日开通的物联网平台服务下公共实例
需要将mqtt_host修改为: mqtt_host = "${YourProductKey}.iot-as-mqtt.${YourRegionId}.aliyuncs.com"
其中, ${YourProductKey}请替换为设备所属产品的ProductKey。可登录物联网平台控制台在对应实例的设备详情页获取。
${YourRegionId}:请替换为您的物联网平台设备所在地域代码, 比如cn-shanghai等
该情况下完整mqtt_host举例: a1TTmBPIChA.iot-as-mqtt.cn-shanghai.aliyuncs.com
详情请见: https://help.aliyun.com/document_detail/147356.html
*/
char *mqtt_host = "${YourInstanceId}.mqtt.iothub.aliyuncs.com";
/* 位于portfiles/aiot_port文件夹下的系统适配函数集合 */
extern aiot_sysdep_portfile_t g_aiot_sysdep_portfile;
/* 位于external/ali_ca_cert.c中的服务器证书 */
extern const char *ali_ca_cert;
static pthread_t g_mqtt_process_thread;
static pthread_t g_mqtt_recv_thread;
static uint8_t g_mqtt_process_thread_running = 0;
static uint8_t g_mqtt_recv_thread_running = 0;
/* TODO: 如果要关闭日志, 就把这个函数实现为空, 如果要减少日志, 可根据code选择不打印
*
* 例如: [1581501698.455][LK-0309] pub: /sys/gb80sFmX7yX/devinfo_basic_demo/thing/deviceinfo/update
*
* 上面这条日志的code就是0309(十六进制), code值的定义见core/aiot_state_api.h
*
*/
/* 日志回调函数, SDK的日志会从这里输出 */
static int32_t demo_state_logcb(int32_t code, char *message)
{
printf("%s", message);
return 0;
}
/* MQTT事件回调函数, 当网络连接/重连/断开时被触发, 事件定义见core/aiot_mqtt_api.h */
void demo_mqtt_event_handler(void *handle, const aiot_mqtt_event_t *event, void *userdata)
{
switch (event->type) {
/* SDK因为用户调用了aiot_mqtt_connect()接口, 与mqtt服务器建立连接已成功 */
case AIOT_MQTTEVT_CONNECT: {
printf("AIOT_MQTTEVT_CONNECT\n");
}
break;
/* SDK因为网络状况被动断连后, 自动发起重连已成功 */
case AIOT_MQTTEVT_RECONNECT: {
printf("AIOT_MQTTEVT_RECONNECT\n");
}
break;
/* SDK因为网络的状况而被动断开了连接, network是底层读写失败, heartbeat是没有按预期得到服务端心跳应答 */
case AIOT_MQTTEVT_DISCONNECT: {
char *cause = (event->data.disconnect == AIOT_MQTTDISCONNEVT_NETWORK_DISCONNECT) ? ("network disconnect") :
("heartbeat disconnect");
printf("AIOT_MQTTEVT_DISCONNECT: %s\n", cause);
}
break;
default: {
}
}
}
/* MQTT默认消息处理回调, 当SDK从服务器收到MQTT消息时, 且无对应用户回调处理时被调用 */
void demo_mqtt_default_recv_handler(void *handle, const aiot_mqtt_recv_t *packet, void *userdata)
{
switch (packet->type) {
case AIOT_MQTTRECV_HEARTBEAT_RESPONSE: {
printf("heartbeat response\n");
}
break;
case AIOT_MQTTRECV_SUB_ACK: {
printf("suback, res: -0x%04X, packet id: %d, max qos: %d\n",
-packet->data.sub_ack.res, packet->data.sub_ack.packet_id, packet->data.sub_ack.max_qos);
}
break;
case AIOT_MQTTRECV_PUB: {
printf("pub, qos: %d, topic: %.*s\n", packet->data.pub.qos, packet->data.pub.topic_len, packet->data.pub.topic);
printf("pub, payload: %.*s\n", packet->data.pub.payload_len, packet->data.pub.payload);
}
break;
case AIOT_MQTTRECV_PUB_ACK: {
printf("puback, packet id: %d\n", packet->data.pub_ack.packet_id);
}
break;
default: {
}
}
}
/* 执行aiot_mqtt_process的线程, 包含心跳发送和QoS1消息重发 */
void *demo_mqtt_process_thread(void *args)
{
int32_t res = STATE_SUCCESS;
while (g_mqtt_process_thread_running) {
res = aiot_mqtt_process(args);
if (res == STATE_USER_INPUT_EXEC_DISABLED) {
break;
}
sleep(1);
}
return NULL;
}
/* 执行aiot_mqtt_recv的线程, 包含网络自动重连和从服务器收取MQTT消息 */
void *demo_mqtt_recv_thread(void *args)
{
int32_t res = STATE_SUCCESS;
while (g_mqtt_recv_thread_running) {
res = aiot_mqtt_recv(args);
if (res < STATE_SUCCESS) {
if (res == STATE_USER_INPUT_EXEC_DISABLED) {
break;
}
sleep(1);
}
}
return NULL;
}
int32_t demo_mqtt_start(void **handle, char *product_key, char *device_name, char *device_secret)
{
int32_t res = STATE_SUCCESS;
void *mqtt_handle = NULL;
uint16_t port = 443; /* 无论设备是否使用TLS连接阿里云平台, 目的端口都是443 */
aiot_sysdep_network_cred_t cred; /* 安全凭据结构体, 如果要用TLS, 这个结构体中配置CA证书等参数 */
/* 配置SDK的底层依赖 */
aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
/* 配置SDK的日志输出 */
aiot_state_set_logcb(demo_state_logcb);
/* 创建SDK的安全凭据, 用于建立TLS连接 */
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA; /* 使用RSA证书校验MQTT服务端 */
cred.max_tls_fragment = 16384; /* 最大的分片长度为16K, 其它可选值还有4K, 2K, 1K, 0.5K */
cred.sni_enabled = 1; /* TLS建连时, 支持Server Name Indicator */
cred.x509_server_cert = ali_ca_cert; /* 用来验证MQTT服务端的RSA根证书 */
cred.x509_server_cert_len = strlen(ali_ca_cert); /* 用来验证MQTT服务端的RSA根证书长度 */
/* 创建1个MQTT客户端实例并内部初始化默认参数 */
mqtt_handle = aiot_mqtt_init();
if (mqtt_handle == NULL) {
printf("aiot_mqtt_init failed\n");
return -1;
}
/* 如果以下代码不被注释, 则例程会用TCP而不是TLS连接云平台 */
/*
{
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_NONE;
}
*/
/* 配置MQTT服务器地址 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_HOST, (void *)mqtt_host);
/* 配置MQTT服务器端口 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PORT, (void *)&port);
/* 配置设备productKey */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PRODUCT_KEY, (void *)product_key);
/* 配置设备deviceName */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_NAME, (void *)device_name);
/* 配置设备deviceSecret */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_SECRET, (void *)device_secret);
/* 配置网络连接的安全凭据, 上面已经创建好了 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_NETWORK_CRED, (void *)&cred);
/* 配置MQTT默认消息接收回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_RECV_HANDLER, (void *)demo_mqtt_default_recv_handler);
/* 配置MQTT事件回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_EVENT_HANDLER, (void *)demo_mqtt_event_handler);
/* 与服务器建立MQTT连接 */
res = aiot_mqtt_connect(mqtt_handle);
if (res < STATE_SUCCESS) {
/* 尝试建立连接失败, 销毁MQTT实例, 回收资源 */
aiot_mqtt_deinit(&mqtt_handle);
printf("aiot_mqtt_connect failed: -0x%04X\n\r\n", -res);
printf("please check variables like mqtt_host, produt_key, device_name, device_secret in demo\r\n");
return -1;
}
/* 创建一个单独的线程, 专用于执行aiot_mqtt_process, 它会自动发送心跳保活, 以及重发QoS1的未应答报文 */
g_mqtt_process_thread_running = 1;
res = pthread_create(&g_mqtt_process_thread, NULL, demo_mqtt_process_thread, mqtt_handle);
if (res < 0) {
printf("pthread_create demo_mqtt_process_thread failed: %d\n", res);
aiot_mqtt_deinit(&mqtt_handle);
return -1;
}
/* 创建一个单独的线程用于执行aiot_mqtt_recv, 它会循环收取服务器下发的MQTT消息, 并在断线时自动重连 */
g_mqtt_recv_thread_running = 1;
res = pthread_create(&g_mqtt_recv_thread, NULL, demo_mqtt_recv_thread, mqtt_handle);
if (res < 0) {
printf("pthread_create demo_mqtt_recv_thread failed: %d\n", res);
g_mqtt_process_thread_running = 0;
pthread_join(g_mqtt_process_thread, NULL);
aiot_mqtt_deinit(&mqtt_handle);
return -1;
}
*handle = mqtt_handle;
return 0;
}
int32_t demo_mqtt_stop(void **handle)
{
int32_t res = STATE_SUCCESS;
void *mqtt_handle = NULL;
mqtt_handle = *handle;
g_mqtt_process_thread_running = 0;
g_mqtt_recv_thread_running = 0;
pthread_join(g_mqtt_process_thread, NULL);
pthread_join(g_mqtt_recv_thread, NULL);
/* 断开MQTT连接 */
res = aiot_mqtt_disconnect(mqtt_handle);
if (res < STATE_SUCCESS) {
aiot_mqtt_deinit(&mqtt_handle);
printf("aiot_mqtt_disconnect failed: -0x%04X\n", -res);
return -1;
}
/* 销毁MQTT实例 */
res = aiot_mqtt_deinit(&mqtt_handle);
if (res < STATE_SUCCESS) {
printf("aiot_mqtt_deinit failed: -0x%04X\n", -res);
return -1;
}
return 0;
}
/* 事件处理回调, */
void demo_devinfo_event_handler(void *handle, const aiot_devinfo_event_t *event, void *userdata)
{
switch (event->type) {
case AIOT_DEVINFOEVT_INVALID_DEVINFO: {
printf("AIOT_DEVINFOEVT_INVALID_DEVINFO\n");
}
break;
case AIOT_DEVINFOEVT_INVALID_RESPONSE: {
printf("AIOT_DEVINFOEVT_INVALID_RESPONSE\n");
}
break;
case AIOT_DEVINFOEVT_INVALID_RESPONSE_FORMAT: {
printf("AIOT_DEVINFOEVT_INVALID_RESPONSE_FORMAT\n");
}
break;
default: {
}
}
}
/* TODO: 数据处理回调, 当SDK从网络上收到devinfo消息时被调用 */
void demo_devinfo_recv_handler(void *handle, const aiot_devinfo_recv_t *packet, void *userdata)
{
switch (packet->type) {
/* 这是云端对devinfo消息的应答报文 */
case AIOT_DEVINFORECV_GENERIC_REPLY: {
printf("pk: %s, dn: %s, code: %d, msg id: %d, data: %.*s, message: %.*s\n", packet->product_key, packet->device_name,
packet->data.generic_reply.code, packet->data.generic_reply.msg_id, packet->data.generic_reply.data_len,
packet->data.generic_reply.data, packet->data.generic_reply.message_len, packet->data.generic_reply.message);
}
break;
default: {
}
}
}
int main(int argc, char *argv[])
{
int32_t res = STATE_SUCCESS;
void *mqtt_handle = NULL, *devinfo_handle = NULL;
/* 建立MQTT连接, 并开启保活线程和接收线程 */
res = demo_mqtt_start(&mqtt_handle, product_key, device_name, device_secret);
if (res < 0) {
printf("demo_mqtt_start failed\n");
return -1;
}
/* 创建1个devinfo客户端实例并内部初始化默认参数 */
devinfo_handle = aiot_devinfo_init();
if (devinfo_handle == NULL) {
demo_mqtt_stop(&mqtt_handle);
printf("aiot_devinfo_init failed\n");
return -1;
}
/* 配置devinfo会话, 把它和MQTT会话的句柄关联起来 */
res = aiot_devinfo_setopt(devinfo_handle, AIOT_DEVINFOOPT_MQTT_HANDLE, mqtt_handle);
if (res < STATE_SUCCESS) {
printf("aiot_devinfo_setopt AIOT_DEVINFOOPT_MQTT_HANDLE failed, res: -0x%04X\n", -res);
aiot_devinfo_deinit(&devinfo_handle);
demo_mqtt_stop(&mqtt_handle);
return -1;
}
/* TODO: DEVINFO消息回应从云端到达设备时, 会进入此处设置的回调函数 */
res = aiot_devinfo_setopt(devinfo_handle, AIOT_DEVINFOOPT_RECV_HANDLER, (void *)demo_devinfo_recv_handler);
if (res < STATE_SUCCESS) {
printf("aiot_devinfo_setopt AIOT_DEVINFOOPT_RECV_HANDLER failed, res: -0x%04X\n", -res);
aiot_devinfo_deinit(&devinfo_handle);
demo_mqtt_stop(&mqtt_handle);
return -1;
}
res = aiot_devinfo_setopt(devinfo_handle, AIOT_DEVINFOOPT_EVENT_HANDLER, (void *)demo_devinfo_event_handler);
if (res < STATE_SUCCESS) {
printf("aiot_devinfo_setopt AIOT_DEVINFOOPT_EVENT_HANDLER failed, res: -0x%04X\n", -res);
aiot_devinfo_deinit(&devinfo_handle);
demo_mqtt_stop(&mqtt_handle);
return -1;
}
/* 示例: 发送delete删除请求给云平台 */
/* TODO: 替换示例JSON中的 testKey 部分, 效果就会变成删除其他Key标识的设备标签 */
/* {
aiot_devinfo_msg_t devinfo_delete;
char *delete = "[{\"attrKey\":\"testKey\"}]";
memset(&devinfo_delete, 0, sizeof(aiot_devinfo_msg_t));
devinfo_delete.product_key = product_key;
devinfo_delete.device_name = device_name;
devinfo_delete.type = AIOT_DEVINFO_MSG_DELETE;
devinfo_delete.data.delete.params = delete;
res = aiot_devinfo_send(devinfo_handle, &devinfo_delete);
if (res < STATE_SUCCESS) {
aiot_devinfo_deinit(&devinfo_handle);
demo_mqtt_stop(&mqtt_handle);
return -1;
}
printf("aiot_devinfo_send delete msg id: %d\n", res);
} */
/* 示例: 发送update请求给云平台 */
/* TODO: 替换示例JSON中的 testKey 部分, 效果就会变成更新其他Key标识的设备标签 */
/* TODO: 替换示例JSON中的 testValue 部分, 效果就会变成更新其他Value到设备标签 */
/* {
aiot_devinfo_msg_t devinfo_update;
char *update = "[{\"attrKey\":\"testKey\",\"attrValue\":\"testValue\"}]";
memset(&devinfo_update, 0, sizeof(aiot_devinfo_msg_t));
devinfo_update.product_key = product_key;
devinfo_update.device_name = device_name;
devinfo_update.type = AIOT_DEVINFO_MSG_UPDATE;
devinfo_update.data.update.params = update;
res = aiot_devinfo_send(devinfo_handle, &devinfo_update);
if (res < STATE_SUCCESS) {
aiot_devinfo_deinit(&devinfo_handle);
demo_mqtt_stop(&mqtt_handle);
return -1;
}
printf("aiot_devinfo_send update msg id: %d\n", res);
} */
/* 主线程进入休眠, 等云平台的DEVINFO回应到达时, 接收线程会调用 demo_devinfo_recv_handler() */
while (1) {
sleep(1);
}
/* 销毁DEVINFO实例, 一般不会运行到这里 */
res = aiot_devinfo_deinit(&devinfo_handle);
if (res < STATE_SUCCESS) {
demo_mqtt_stop(&mqtt_handle);
printf("aiot_devinfo_deinit failed: -0x%04X\n", -res);
return -1;
}
/* 销毁MQTT实例, 退出线程, 一般不会运行到这里 */
res = demo_mqtt_stop(&mqtt_handle);
if (res < 0) {
printf("demo_start_stop failed\n");
return -1;
}
return 0;
}

View File

@@ -0,0 +1,554 @@
/*
* 这个例程适用于`Linux`这类支持pthread的POSIX设备, 它演示了......
*
* 需要用户关注或修改的部分, 已经用 TODO 在注释中标明
*
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include "aiot_state_api.h"
#include "aiot_sysdep_api.h"
#include "aiot_mqtt_api.h"
#include "aiot_dm_api.h"
#include "aiot_diag_api.h"
/* TODO: 替换为自己设备的三元组 */
char *product_key = "${YourProductKey}";
char *device_name = "${YourDeviceName}";
char *device_secret = "${YourDeviceSecret}";
/*
TODO: 替换为自己实例的接入点
对于企业实例, 或者2021年07月30日之后含当日开通的物联网平台服务下公共实例
mqtt_host的格式为"${YourInstanceId}.mqtt.iothub.aliyuncs.com"
其中${YourInstanceId}: 请替换为您企业/公共实例的Id
对于2021年07月30日之前不含当日开通的物联网平台服务下公共实例
需要将mqtt_host修改为: mqtt_host = "${YourProductKey}.iot-as-mqtt.${YourRegionId}.aliyuncs.com"
其中, ${YourProductKey}请替换为设备所属产品的ProductKey。可登录物联网平台控制台在对应实例的设备详情页获取。
${YourRegionId}:请替换为您的物联网平台设备所在地域代码, 比如cn-shanghai等
该情况下完整mqtt_host举例: a1TTmBPIChA.iot-as-mqtt.cn-shanghai.aliyuncs.com
详情请见: https://help.aliyun.com/document_detail/147356.html
*/
char *mqtt_host = "${YourInstanceId}.mqtt.iothub.aliyuncs.com";
/* 位于portfiles/aiot_port文件夹下的系统适配函数集合 */
extern aiot_sysdep_portfile_t g_aiot_sysdep_portfile;
/* 位于external/ali_ca_cert.c中的服务器证书 */
extern const char *ali_ca_cert;
static pthread_t g_mqtt_process_thread;
static pthread_t g_mqtt_recv_thread;
static uint8_t g_mqtt_process_thread_running = 0;
static uint8_t g_mqtt_recv_thread_running = 0;
/* TODO: 如果要关闭日志, 就把这个函数实现为空, 如果要减少日志, 可根据code选择不打印
*
* 例如: [1577589489.033][LK-0317] diag_basic_demo&gb80sFmX7yX
*
* 上面这条日志的code就是0317(十六进制), code值的定义见core/aiot_state_api.h
*
*/
/* 日志回调函数, SDK的日志会从这里输出 */
static int32_t demo_state_logcb(int32_t code, char *message)
{
printf("%s", message);
return 0;
}
/* MQTT事件回调函数, 当网络连接/重连/断开时被触发, 事件定义见core/aiot_mqtt_api.h */
void demo_mqtt_event_handler(void *handle, const aiot_mqtt_event_t *event, void *userdata)
{
switch (event->type) {
/* SDK因为用户调用了aiot_mqtt_connect()接口, 与mqtt服务器建立连接已成功 */
case AIOT_MQTTEVT_CONNECT: {
printf("AIOT_MQTTEVT_CONNECT\n");
}
break;
/* SDK因为网络状况被动断连后, 自动发起重连已成功 */
case AIOT_MQTTEVT_RECONNECT: {
printf("AIOT_MQTTEVT_RECONNECT\n");
}
break;
/* SDK因为网络的状况而被动断开了连接, network是底层读写失败, heartbeat是没有按预期得到服务端心跳应答 */
case AIOT_MQTTEVT_DISCONNECT: {
char *cause = (event->data.disconnect == AIOT_MQTTDISCONNEVT_NETWORK_DISCONNECT) ? ("network disconnect") :
("heartbeat disconnect");
printf("AIOT_MQTTEVT_DISCONNECT: %s\n", cause);
}
break;
default: {
}
}
}
/* MQTT默认消息处理回调, 当SDK从服务器收到MQTT消息时, 且无对应用户回调处理时被调用 */
void demo_mqtt_default_recv_handler(void *handle, const aiot_mqtt_recv_t *packet, void *userdata)
{
switch (packet->type) {
case AIOT_MQTTRECV_HEARTBEAT_RESPONSE: {
printf("heartbeat response\n");
}
break;
case AIOT_MQTTRECV_SUB_ACK: {
printf("suback, res: -0x%04X, packet id: %d, max qos: %d\n",
-packet->data.sub_ack.res, packet->data.sub_ack.packet_id, packet->data.sub_ack.max_qos);
}
break;
case AIOT_MQTTRECV_PUB: {
printf("pub, qos: %d, topic: %.*s\n", packet->data.pub.qos, packet->data.pub.topic_len, packet->data.pub.topic);
printf("pub, payload: %.*s\n", packet->data.pub.payload_len, packet->data.pub.payload);
}
break;
case AIOT_MQTTRECV_PUB_ACK: {
printf("puback, packet id: %d\n", packet->data.pub_ack.packet_id);
}
break;
default: {
}
}
}
/* 执行aiot_mqtt_process的线程, 包含心跳发送和QoS1消息重发 */
void *demo_mqtt_process_thread(void *args)
{
int32_t res = STATE_SUCCESS;
while (g_mqtt_process_thread_running) {
res = aiot_mqtt_process(args);
if (res == STATE_USER_INPUT_EXEC_DISABLED) {
break;
}
sleep(1);
}
return NULL;
}
/* 执行aiot_mqtt_recv的线程, 包含网络自动重连和从服务器收取MQTT消息 */
void *demo_mqtt_recv_thread(void *args)
{
int32_t res = STATE_SUCCESS;
while (g_mqtt_recv_thread_running) {
res = aiot_mqtt_recv(args);
if (res < STATE_SUCCESS) {
if (res == STATE_USER_INPUT_EXEC_DISABLED) {
break;
}
sleep(1);
}
}
return NULL;
}
int32_t demo_mqtt_start(void *handle, char *product_key, char *device_name, char *device_secret)
{
int32_t res = STATE_SUCCESS;
void *mqtt_handle = handle;
uint16_t port = 443; /* 无论设备是否使用TLS连接阿里云平台, 目的端口都是443 */
aiot_sysdep_network_cred_t cred; /* 安全凭据结构体, 如果要用TLS, 这个结构体中配置CA证书等参数 */
/* 创建SDK的安全凭据, 用于建立TLS连接 */
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA; /* 使用RSA证书校验MQTT服务端 */
cred.max_tls_fragment = 16384; /* 最大的分片长度为16K, 其它可选值还有4K, 2K, 1K, 0.5K */
cred.sni_enabled = 1; /* TLS建连时, 支持Server Name Indicator */
cred.x509_server_cert = ali_ca_cert; /* 用来验证MQTT服务端的RSA根证书 */
cred.x509_server_cert_len = strlen(ali_ca_cert); /* 用来验证MQTT服务端的RSA根证书长度 */
/* TODO: 如果以下代码不被注释, 则例程会用TCP而不是TLS连接云平台 */
/*
{
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_NONE;
}
*/
/* 配置MQTT服务器地址 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_HOST, (void *)mqtt_host);
/* 配置MQTT服务器端口 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PORT, (void *)&port);
/* 配置设备productKey */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PRODUCT_KEY, (void *)product_key);
/* 配置设备deviceName */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_NAME, (void *)device_name);
/* 配置设备deviceSecret */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_SECRET, (void *)device_secret);
/* 配置网络连接的安全凭据, 上面已经创建好了 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_NETWORK_CRED, (void *)&cred);
/* 配置MQTT默认消息接收回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_RECV_HANDLER, (void *)demo_mqtt_default_recv_handler);
/* 配置MQTT事件回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_EVENT_HANDLER, (void *)demo_mqtt_event_handler);
/* 与服务器建立MQTT连接 */
res = aiot_mqtt_connect(mqtt_handle);
if (res < STATE_SUCCESS) {
/* 尝试建立连接失败, 销毁MQTT实例, 回收资源 */
aiot_mqtt_deinit(&mqtt_handle);
printf("aiot_mqtt_connect failed: -0x%04X\n\r\n", -res);
printf("please check variables like mqtt_host, produt_key, device_name, device_secret in demo\r\n");
return -1;
}
/* 创建一个单独的线程, 专用于执行aiot_mqtt_process, 它会自动发送心跳保活, 以及重发QoS1的未应答报文 */
g_mqtt_process_thread_running = 1;
res = pthread_create(&g_mqtt_process_thread, NULL, demo_mqtt_process_thread, mqtt_handle);
if (res < 0) {
printf("pthread_create demo_mqtt_process_thread failed: %d\n", res);
aiot_mqtt_deinit(&mqtt_handle);
return -1;
}
/* 创建一个单独的线程用于执行aiot_mqtt_recv, 它会循环收取服务器下发的MQTT消息, 并在断线时自动重连 */
g_mqtt_recv_thread_running = 1;
res = pthread_create(&g_mqtt_recv_thread, NULL, demo_mqtt_recv_thread, mqtt_handle);
if (res < 0) {
printf("pthread_create demo_mqtt_recv_thread failed: %d\n", res);
g_mqtt_process_thread_running = 0;
pthread_join(g_mqtt_process_thread, NULL);
aiot_mqtt_deinit(&mqtt_handle);
return -1;
}
return 0;
}
int32_t demo_mqtt_stop(void **handle)
{
int32_t res = STATE_SUCCESS;
void *mqtt_handle = NULL;
mqtt_handle = *handle;
g_mqtt_process_thread_running = 0;
g_mqtt_recv_thread_running = 0;
pthread_join(g_mqtt_process_thread, NULL);
pthread_join(g_mqtt_recv_thread, NULL);
/* 断开MQTT连接 */
res = aiot_mqtt_disconnect(mqtt_handle);
if (res < STATE_SUCCESS) {
aiot_mqtt_deinit(&mqtt_handle);
printf("aiot_mqtt_disconnect failed: -0x%04X\n", -res);
return -1;
}
/* 销毁MQTT实例 */
res = aiot_mqtt_deinit(&mqtt_handle);
if (res < STATE_SUCCESS) {
printf("aiot_mqtt_deinit failed: -0x%04X\n", -res);
return -1;
}
return 0;
}
/* 用户数据接收处理回调函数 */
static void demo_dm_recv_handler(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata)
{
printf("demo_dm_recv_handler, type = %d\r\n", recv->type);
switch (recv->type) {
/* 属性上报, 事件上报, 获取期望属性值或者删除期望属性值的应答 */
case AIOT_DMRECV_GENERIC_REPLY: {
printf("msg_id = %d, code = %d, data = %.*s, message = %.*s\r\n",
recv->data.generic_reply.msg_id,
recv->data.generic_reply.code,
recv->data.generic_reply.data_len,
recv->data.generic_reply.data,
recv->data.generic_reply.message_len,
recv->data.generic_reply.message);
}
break;
/* 属性设置 */
case AIOT_DMRECV_PROPERTY_SET: {
printf("msg_id = %ld, params = %.*s\r\n",
(unsigned long)recv->data.property_set.msg_id,
recv->data.property_set.params_len,
recv->data.property_set.params);
/* TODO: 以下代码演示如何对来自云平台的属性设置指令进行应答, 用户可取消注释查看演示效果 */
/*
{
aiot_dm_msg_t msg;
memset(&msg, 0, sizeof(aiot_dm_msg_t));
msg.type = AIOT_DMMSG_PROPERTY_SET_REPLY;
msg.data.property_set_reply.msg_id = recv->data.property_set.msg_id;
msg.data.property_set_reply.code = 200;
msg.data.property_set_reply.data = "{}";
int32_t res = aiot_dm_send(dm_handle, &msg);
if (res < 0) {
printf("aiot_dm_send failed\r\n");
}
}
*/
}
break;
/* 异步服务调用 */
case AIOT_DMRECV_ASYNC_SERVICE_INVOKE: {
printf("msg_id = %ld, service_id = %s, params = %.*s\r\n",
(unsigned long)recv->data.async_service_invoke.msg_id,
recv->data.async_service_invoke.service_id,
recv->data.async_service_invoke.params_len,
recv->data.async_service_invoke.params);
/* TODO: 以下代码演示如何对来自云平台的异步服务调用进行应答, 用户可取消注释查看演示效果
*
* 注意: 如果用户在回调函数外进行应答, 需要自行保存msg_id, 因为回调函数入参在退出回调函数后将被SDK销毁, 不可以再访问到
*/
/*
{
aiot_dm_msg_t msg;
memset(&msg, 0, sizeof(aiot_dm_msg_t));
msg.type = AIOT_DMMSG_ASYNC_SERVICE_REPLY;
msg.data.async_service_reply.msg_id = recv->data.async_service_invoke.msg_id;
msg.data.async_service_reply.code = 200;
msg.data.async_service_reply.service_id = "ToggleLightSwitch";
msg.data.async_service_reply.data = "{\"dataA\": 20}";
int32_t res = aiot_dm_send(dm_handle, &msg);
if (res < 0) {
printf("aiot_dm_send failed\r\n");
}
}
*/
}
break;
/* 同步服务调用 */
case AIOT_DMRECV_SYNC_SERVICE_INVOKE: {
printf("msg_id = %ld, rrpc_id = %s, service_id = %s, params = %.*s\r\n",
(unsigned long)recv->data.sync_service_invoke.msg_id,
recv->data.sync_service_invoke.rrpc_id,
recv->data.sync_service_invoke.service_id,
recv->data.sync_service_invoke.params_len,
recv->data.sync_service_invoke.params);
/* TODO: 以下代码演示如何对来自云平台的同步服务调用进行应答, 用户可取消注释查看演示效果
*
* 注意: 如果用户在回调函数外进行应答, 需要自行保存msg_id和rrpc_id字符串, 因为回调函数入参在退出回调函数后将被SDK销毁, 不可以再访问到
*/
/*
{
aiot_dm_msg_t msg;
memset(&msg, 0, sizeof(aiot_dm_msg_t));
msg.type = AIOT_DMMSG_SYNC_SERVICE_REPLY;
msg.data.sync_service_reply.rrpc_id = recv->data.sync_service_invoke.rrpc_id;
msg.data.sync_service_reply.msg_id = recv->data.sync_service_invoke.msg_id;
msg.data.sync_service_reply.code = 200;
msg.data.sync_service_reply.service_id = "SetLightSwitchTimer";
msg.data.sync_service_reply.data = "{}";
int32_t res = aiot_dm_send(dm_handle, &msg);
if (res < 0) {
printf("aiot_dm_send failed\r\n");
}
}
*/
}
break;
/* 下行二进制数据 */
case AIOT_DMRECV_RAW_DATA: {
printf("raw data len = %d\r\n", recv->data.raw_data.data_len);
/* TODO: 以下代码演示如何发送二进制格式数据, 若使用需要有相应的数据透传脚本部署在云端 */
/*
{
aiot_dm_msg_t msg;
uint8_t raw_data[] = {0x01, 0x02};
memset(&msg, 0, sizeof(aiot_dm_msg_t));
msg.type = AIOT_DMMSG_RAW_DATA;
msg.data.raw_data.data = raw_data;
msg.data.raw_data.data_len = sizeof(raw_data);
aiot_dm_send(dm_handle, &msg);
}
*/
}
break;
/* 二进制格式的同步服务调用, 比单纯的二进制数据消息多了个rrpc_id */
case AIOT_DMRECV_RAW_SYNC_SERVICE_INVOKE: {
printf("raw sync service rrpc_id = %s, data_len = %d\r\n",
recv->data.raw_service_invoke.rrpc_id,
recv->data.raw_service_invoke.data_len);
}
break;
default:
break;
}
}
int32_t demo_dm_start(void **handle, void *mqtt_handle, char *product_key, char *device_name)
{
void *dm_handle = NULL;
dm_handle = aiot_dm_init();
if (dm_handle == NULL) {
return -1;
}
/* 配置MQTT实例句柄 */
aiot_dm_setopt(dm_handle, AIOT_DMOPT_MQTT_HANDLE, mqtt_handle);
/* 配置消息接收处理回调函数 */
aiot_dm_setopt(dm_handle, AIOT_DMOPT_RECV_HANDLER, (void *)demo_dm_recv_handler);
*handle = dm_handle;
return STATE_SUCCESS;
}
int32_t demo_dm_stop(void **handle)
{
int32_t res = STATE_SUCCESS;
/* 销毁DATA-MODEL实例, 一般不会运行到这里 */
res = aiot_dm_deinit(handle);
if (res < STATE_SUCCESS) {
printf("aiot_dm_deinit failed: -0x%04X\n", -res);
return -1;
}
return STATE_SUCCESS;
}
/* 事件回调函数, 当网络连接/重连/断开时被触发, 事件定义见core/aiot_diag_api.h */
void demo_diag_event_handler(void *handle, const aiot_diag_event_t *event, void *userdata)
{
switch (event->type) {
case AIOT_DIAGEVT_ALERT: {
printf("AIOT_DIAGEVT_ALERT, module name: %s, level: %s, desc: %s\n",
event->data.alert.module_name, event->data.alert.level, event->data.alert.desc);
}
break;
default: {
}
}
}
/* 数据处理回调, 当SDK从网络上收到diag消息时被调用 */
void demo_diag_recv_handler(void *handle, const aiot_diag_recv_t *packet, void *userdata)
{
switch (packet->type) {
case AIOT_DIAGRECV_DIAG_CONTROL: {
}
break;
default: {
}
}
}
int main(int argc, char *argv[])
{
int32_t res = STATE_SUCCESS;
void *mqtt_handle = NULL, *diag_handle = NULL, *dm_handle = NULL;
/* 配置SDK的底层依赖 */
aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
/* 配置SDK的日志输出 */
aiot_state_set_logcb(demo_state_logcb);
/* 创建1个MQTT客户端实例并内部初始化默认参数 */
mqtt_handle = aiot_mqtt_init();
if (mqtt_handle == NULL) {
printf("aiot_mqtt_init failed\n");
return -1;
}
diag_handle = aiot_diag_init();
if (diag_handle == NULL) {
printf("aiot_diag_init failed\n");
return -1;
}
res = aiot_diag_setopt(diag_handle, AIOT_DIAGOPT_MQTT_HANDLE, mqtt_handle);
if (res < STATE_SUCCESS) {
aiot_diag_deinit(&diag_handle);
printf("aiot_diag_setopt failed\n");
return -1;
}
res = aiot_diag_setopt(diag_handle, AIOT_DIAGOPT_EVENT_HANDLER, demo_diag_event_handler);
if (res < STATE_SUCCESS) {
aiot_diag_deinit(&diag_handle);
printf("aiot_diag_setopt failed\n");
return -1;
}
res = aiot_diag_start(diag_handle);
if (res < STATE_SUCCESS) {
aiot_diag_deinit(&diag_handle);
printf("aiot_diag_start failed\n");
return -1;
}
/* 建立MQTT连接, 并开启保活线程和接收线程 */
res = demo_mqtt_start(mqtt_handle, product_key, device_name, device_secret);
if (res < STATE_SUCCESS) {
aiot_diag_deinit(&diag_handle);
printf("demo_mqtt_start failed\n");
return -1;
}
res = demo_dm_start(&dm_handle, mqtt_handle, product_key, device_name);
if (res < STATE_SUCCESS) {
aiot_diag_deinit(&diag_handle);
demo_mqtt_stop(&mqtt_handle);
printf("demo_dm_start failed\n");
return -1;
}
/* {
aiot_dm_msg_t msg;
memset(&msg, 0, sizeof(aiot_dm_msg_t));
msg.type = AIOT_DMMSG_PROPERTY_POST;
msg.data.property_post.params = "{\"LightSwitch\":1}";
res = aiot_dm_send(dm_handle, &msg);
} */
while (1) {
sleep(1);
}
/* 销毁dm实例, 一般不会运行到这里 */
demo_dm_stop(&dm_handle);
/* 销毁diag实例, 一般不会运行到这里 */
aiot_diag_deinit(&diag_handle);
/* 销毁MQTT实例, 退出线程, 一般不会运行到这里 */
demo_mqtt_stop(&mqtt_handle);
return 0;
}

View File

@@ -0,0 +1,203 @@
/* 这个例程演示了用SDK配置动态注册会话实例的参数, 并发起请求和接收应答, 之后
*
* + 如果接收应答失败了, 销毁实例, 回收资源, 结束程序退出
* + 如果接收应答成功, 在`demo_dynreg_recv_handler()`的应答处理回调函数中, 演示解析获取服务端应答的内容
*
* 需要用户关注或修改的部分, 已用 `TODO` 在注释中标明
*
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include "aiot_state_api.h"
#include "aiot_sysdep_api.h"
#include "aiot_dynreg_api.h"
typedef struct {
uint32_t code;
char *device_secret;
} demo_info_t;
/* 位于portfiles/aiot_port文件夹下的系统适配函数集合 */
extern aiot_sysdep_portfile_t g_aiot_sysdep_portfile;
/* 位于external/ali_ca_cert.c中的服务器证书 */
extern const char *ali_ca_cert;
/* TODO: 如果要关闭日志, 就把这个函数实现为空, 如果要减少日志, 可根据code选择不打印
*
* 例如: [1580995015.811][LK-040B] > POST /auth/register/device HTTP/1.1
*
* 上面这条日志的code就是040B(十六进制), code值的定义见components/dynreg/aiot_dynreg_api.h
*
*/
/* 日志回调函数, SDK的日志会从这里输出 */
static int32_t demo_state_logcb(int32_t code, char *message)
{
printf("%s", message);
return 0;
}
/* 数据处理回调, 当SDK从网络上收到dynreg消息时被调用 */
void demo_dynreg_recv_handler(void *handle, const aiot_dynreg_recv_t *packet, void *userdata)
{
demo_info_t *demo_info = (demo_info_t *)userdata;
switch (packet->type) {
case AIOT_DYNREGRECV_STATUS_CODE: {
demo_info->code = packet->data.status_code.code;
}
break;
/* TODO: 回调中需要将packet指向的空间内容复制保存好, 因为回调返回后, 这些空间就会被SDK释放 */
case AIOT_DYNREGRECV_DEVICE_INFO: {
demo_info->device_secret = malloc(strlen(packet->data.device_info.device_secret) + 1);
if (demo_info->device_secret != NULL) {
memset(demo_info->device_secret, 0, strlen(packet->data.device_info.device_secret) + 1);
memcpy(demo_info->device_secret, packet->data.device_info.device_secret,
strlen(packet->data.device_info.device_secret));
}
}
break;
default: {
}
break;
}
}
int main(int argc, char *argv[])
{
int32_t res = STATE_SUCCESS;
void *dynreg_handle = NULL;
char *host = "iot-auth.cn-shanghai.aliyuncs.com"; /* 阿里云平台动态注册国内站点 */
uint16_t port = 443; /* 无论设备是否使用TLS连接阿里云平台, 目的端口都是443 */
aiot_sysdep_network_cred_t cred; /* 安全凭据结构体, 如果要用TLS, 这个结构体中配置CA证书等参数 */
demo_info_t demo_info;
/* TODO: 替换为自己设备的productKey, productSecret和deviceName */
char *product_key = "a13FN5TplKq";
char *product_secret = "y7GSILD480lBSsP8";
char *device_name = "dynreg_basic_demo";
memset(&demo_info, 0, sizeof(demo_info_t));
/* 配置SDK的底层依赖 */
aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
/* 配置SDK的日志输出 */
aiot_state_set_logcb(demo_state_logcb);
/* 创建SDK的安全凭据, 用于建立TLS连接 */
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA; /* 使用RSA证书校验DYNREG服务端 */
cred.max_tls_fragment = 16384; /* 最大的分片长度为16K, 其它可选值还有4K, 2K, 1K, 0.5K */
cred.sni_enabled = 1; /* TLS建连时, 支持Server Name Indicator */
cred.x509_server_cert = ali_ca_cert; /* 用来验证服务端的RSA根证书 */
cred.x509_server_cert_len = strlen(ali_ca_cert); /* 用来验证服务端的RSA根证书长度 */
/* 创建1个dynreg客户端实例并内部初始化默认参数 */
dynreg_handle = aiot_dynreg_init();
if (dynreg_handle == NULL) {
printf("aiot_dynreg_init failed\n");
return -1;
}
/* 配置连接的服务器地址 */
res = aiot_dynreg_setopt(dynreg_handle, AIOT_DYNREGOPT_HOST, (void *)host);
if (res < STATE_SUCCESS) {
printf("aiot_dynreg_setopt AIOT_DYNREGOPT_HOST failed, res: -0x%04X\n", -res);
aiot_dynreg_deinit(&dynreg_handle);
return -1;
}
/* 配置连接的服务器端口 */
res = aiot_dynreg_setopt(dynreg_handle, AIOT_DYNREGOPT_PORT, (void *)&port);
if (res < STATE_SUCCESS) {
printf("aiot_dynreg_setopt AIOT_DYNREGOPT_PORT failed, res: -0x%04X\n", -res);
aiot_dynreg_deinit(&dynreg_handle);
return -1;
}
/* 配置设备productKey */
res = aiot_dynreg_setopt(dynreg_handle, AIOT_DYNREGOPT_PRODUCT_KEY, (void *)product_key);
if (res < STATE_SUCCESS) {
printf("aiot_dynreg_setopt AIOT_DYNREGOPT_PRODUCT_KEY failed, res: -0x%04X\n", -res);
aiot_dynreg_deinit(&dynreg_handle);
return -1;
}
/* 配置设备productSecret */
res = aiot_dynreg_setopt(dynreg_handle, AIOT_DYNREGOPT_PRODUCT_SECRET, (void *)product_secret);
if (res < STATE_SUCCESS) {
printf("aiot_dynreg_setopt AIOT_DYNREGOPT_PRODUCT_SECRET failed, res: -0x%04X\n", -res);
aiot_dynreg_deinit(&dynreg_handle);
return -1;
}
/* 配置设备deviceName */
res = aiot_dynreg_setopt(dynreg_handle, AIOT_DYNREGOPT_DEVICE_NAME, (void *)device_name);
if (res < STATE_SUCCESS) {
printf("aiot_dynreg_setopt AIOT_DYNREGOPT_DEVICE_NAME failed, res: -0x%04X\n", -res);
aiot_dynreg_deinit(&dynreg_handle);
return -1;
}
/* 配置网络连接的安全凭据, 上面已经创建好了 */
res = aiot_dynreg_setopt(dynreg_handle, AIOT_DYNREGOPT_NETWORK_CRED, (void *)&cred);
if (res < STATE_SUCCESS) {
printf("aiot_dynreg_setopt AIOT_DYNREGOPT_NETWORK_CRED failed, res: -0x%04X\n", -res);
aiot_dynreg_deinit(&dynreg_handle);
return -1;
}
/* 配置DYNREG默认消息接收回调函数 */
res = aiot_dynreg_setopt(dynreg_handle, AIOT_DYNREGOPT_RECV_HANDLER, (void *)demo_dynreg_recv_handler);
if (res < STATE_SUCCESS) {
printf("aiot_dynreg_setopt AIOT_DYNREGOPT_RECV_HANDLER failed, res: -0x%04X\n", -res);
aiot_dynreg_deinit(&dynreg_handle);
return -1;
}
/* 设置用户上下文,该上下文会在 demo_dynreg_recv_handler 被调用时传回 */
res = aiot_dynreg_setopt(dynreg_handle, AIOT_DYNREGOPT_USERDATA, (void *)&demo_info);
if (res < STATE_SUCCESS) {
printf("aiot_dynreg_setopt AIOT_DYNREGOPT_USERDATA failed, res: -0x%04X\n", -res);
aiot_dynreg_deinit(&dynreg_handle);
return -1;
}
/* 发送动态注册请求 */
res = aiot_dynreg_send_request(dynreg_handle);
if (res < STATE_SUCCESS) {
printf("aiot_dynreg_send_request failed: -0x%04X\n", -res);
return -1;
}
/* 接收动态注册请求 */
res = aiot_dynreg_recv(dynreg_handle);
if (res < STATE_SUCCESS) {
printf("aiot_dynreg_recv failed: -0x%04X\n", -res);
return -1;
}
printf("status code: %d\n", demo_info.code);
/* 把服务应答中的 deviceSecret 打印出来 */
if (demo_info.device_secret != NULL) {
printf("device secret: %s\n", demo_info.device_secret);
free(demo_info.device_secret);
}
/* 销毁动态注册会话实例 */
res = aiot_dynreg_deinit(&dynreg_handle);
if (res < STATE_SUCCESS) {
printf("aiot_dynreg_deinit failed: -0x%04X\n", -res);
return -1;
}
return 0;
}

View File

@@ -0,0 +1,261 @@
/* 这个例程演示了用SDK配置动态注册会话实例的参数, 并发起请求和接收应答, 之后
*
* + 如果接收应答失败了, 销毁实例, 回收资源, 结束程序退出
* + 如果接收应答成功, 在`demo_dynregmq_recv_handler()`的应答处理回调函数中, 演示解析获取服务端应答的内容
*
* 需要用户关注或修改的部分, 已用 `TODO` 在注释中标明
*
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include "aiot_state_api.h"
#include "aiot_sysdep_api.h"
#include "aiot_dynregmq_api.h"
/* TODO: 替换为自己设备的三元组 */
char *product_key = "${YourProductKey}";
char *device_name = "${YourDeviceName}";
char *product_secret = "${YourProductSecret}";
/*
TODO: 替换为自己实例的接入点
对于企业实例, 或者2021年07月30日之后含当日开通的物联网平台服务下公共实例
mqtt_host的格式为"${YourInstanceId}.mqtt.iothub.aliyuncs.com"
其中${YourInstanceId}: 请替换为您企业/公共实例的Id
对于2021年07月30日之前不含当日开通的物联网平台服务下公共实例
需要将mqtt_host修改为: mqtt_host = "${YourProductKey}.iot-as-mqtt.${YourRegionId}.aliyuncs.com"
其中, ${YourProductKey}请替换为设备所属产品的ProductKey。可登录物联网平台控制台在对应实例的设备详情页获取。
${YourRegionId}:请替换为您的物联网平台设备所在地域代码, 比如cn-shanghai等
该情况下完整mqtt_host举例: a1TTmBPIChA.iot-as-mqtt.cn-shanghai.aliyuncs.com
详情请见: https://help.aliyun.com/document_detail/147356.html
*/
char *mqtt_host = "${YourInstanceId}.mqtt.iothub.aliyuncs.com";
uint8_t skip_pre_regist =
1; /* TODO: 如果要免预注册, 需要将该值设置为1;如果需要在控制台预先注册设备, 置为0 */
/* 白名单模式下用于保存deviceSecret的结构体定义 */
typedef struct {
char device_secret[64];
} demo_devinfo_wl_t;
/* 免白名单模式下用于保存mqtt建连信息clientid, username和password的结构体定义 */
typedef struct {
char conn_clientid[128];
char conn_username[128];
char conn_password[64];
} demo_devinfo_nwl_t;
/* 位于portfiles/aiot_port文件夹下的系统适配函数集合 */
extern aiot_sysdep_portfile_t g_aiot_sysdep_portfile;
/* 位于external/ali_ca_cert.c中的服务器证书 */
extern const char *ali_ca_cert;
/* 用户保存白名单模式动态注册, 服务器返回的deviceSecret */
static demo_devinfo_wl_t demo_devinfo_wl;
/* 用户保存免白名单模式动态注册, 服务器返回的mqtt建连信息 */
static demo_devinfo_nwl_t demo_devinfo_nwl;
/* TODO: 如果要关闭日志, 就把这个函数实现为空, 如果要减少日志, 可根据code选择不打印
*
* 例如: [1580995015.811][LK-040B] > POST /auth/register/device HTTP/1.1
*
* 上面这条日志的code就是040B(十六进制), code值的定义见components/dynregmq/aiot_dynregmq_api.h
*
*/
/* 日志回调函数, SDK的日志会从这里输出 */
static int32_t demo_state_logcb(int32_t code, char *message)
{
printf("%s", message);
return 0;
}
/* 数据处理回调, 当SDK从网络上收到dynregmq消息时被调用 */
void demo_dynregmq_recv_handler(void *handle, const aiot_dynregmq_recv_t *packet, void *userdata)
{
switch (packet->type) {
/* TODO: 回调中需要将packet指向的空间内容复制保存好, 因为回调返回后, 这些空间就会被SDK释放 */
case AIOT_DYNREGMQRECV_DEVICEINFO_WL: {
if (strlen(packet->data.deviceinfo_wl.device_secret) >= sizeof(demo_devinfo_wl.device_secret)) {
break;
}
/* 白名单模式, 用户务必要对device_secret进行持久化保存 */
memset(&demo_devinfo_wl, 0, sizeof(demo_devinfo_wl_t));
memcpy(demo_devinfo_wl.device_secret, packet->data.deviceinfo_wl.device_secret,
strlen(packet->data.deviceinfo_wl.device_secret));
}
break;
/* TODO: 回调中需要将packet指向的空间内容复制保存好, 因为回调返回后, 这些空间就会被SDK释放 */
case AIOT_DYNREGMQRECV_DEVICEINFO_NWL: {
if (strlen(packet->data.deviceinfo_nwl.clientid) >= sizeof(demo_devinfo_nwl.conn_clientid) ||
strlen(packet->data.deviceinfo_nwl.username) >= sizeof(demo_devinfo_nwl.conn_username) ||
strlen(packet->data.deviceinfo_nwl.password) >= sizeof(demo_devinfo_nwl.conn_password)) {
break;
}
/* 免白名单模式, 用户务必要对MQTT的建连信息clientid, username和password进行持久化保存 */
memset(&demo_devinfo_nwl, 0, sizeof(demo_devinfo_nwl_t));
memcpy(demo_devinfo_nwl.conn_clientid, packet->data.deviceinfo_nwl.clientid,
strlen(packet->data.deviceinfo_nwl.clientid));
memcpy(demo_devinfo_nwl.conn_username, packet->data.deviceinfo_nwl.username,
strlen(packet->data.deviceinfo_nwl.username));
memcpy(demo_devinfo_nwl.conn_password, packet->data.deviceinfo_nwl.password,
strlen(packet->data.deviceinfo_nwl.password));
}
break;
default: {
}
break;
}
}
int main(int argc, char *argv[])
{
int32_t res = STATE_SUCCESS;
void *dynregmq_handle = NULL;
uint16_t port = 443; /* 无论设备是否使用TLS连接阿里云平台, 目的端口都是443 */
aiot_sysdep_network_cred_t
cred; /* 安全凭据结构体, 如果要用TLS, 这个结构体中配置CA证书等参数 */
/* 配置SDK的底层依赖 */
aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
/* 配置SDK的日志输出 */
aiot_state_set_logcb(demo_state_logcb);
/* 创建SDK的安全凭据, 用于建立TLS连接 */
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA; /* 使用RSA证书校验DYNREGMQ服务端 */
cred.max_tls_fragment = 16384; /* 最大的分片长度为16K, 其它可选值还有4K, 2K, 1K, 0.5K */
cred.sni_enabled = 1; /* TLS建连时, 支持Server Name Indicator */
cred.x509_server_cert = ali_ca_cert; /* 用来验证服务端的RSA根证书 */
cred.x509_server_cert_len = strlen(ali_ca_cert); /* 用来验证服务端的RSA根证书长度 */
/* 创建1个dynregmq客户端实例并内部初始化默认参数 */
dynregmq_handle = aiot_dynregmq_init();
if (dynregmq_handle == NULL) {
printf("aiot_dynregmq_init failed\n");
return -1;
}
/* 配置连接的服务器地址 */
res = aiot_dynregmq_setopt(dynregmq_handle, AIOT_DYNREGMQOPT_HOST, (void *)mqtt_host);
if (res < STATE_SUCCESS) {
printf("aiot_dynregmq_setopt AIOT_DYNREGMQOPT_HOST failed, res: -0x%04X\n", -res);
aiot_dynregmq_deinit(&dynregmq_handle);
return -1;
}
/* 配置连接的服务器端口 */
res = aiot_dynregmq_setopt(dynregmq_handle, AIOT_DYNREGMQOPT_PORT, (void *)&port);
if (res < STATE_SUCCESS) {
printf("aiot_dynregmq_setopt AIOT_DYNREGMQOPT_PORT failed, res: -0x%04X\n", -res);
aiot_dynregmq_deinit(&dynregmq_handle);
return -1;
}
/* 配置设备productKey */
res = aiot_dynregmq_setopt(dynregmq_handle, AIOT_DYNREGMQOPT_PRODUCT_KEY, (void *)product_key);
if (res < STATE_SUCCESS) {
printf("aiot_dynregmq_setopt AIOT_DYNREGMQOPT_PRODUCT_KEY failed, res: -0x%04X\n", -res);
aiot_dynregmq_deinit(&dynregmq_handle);
return -1;
}
/* 配置设备productSecret */
res = aiot_dynregmq_setopt(dynregmq_handle, AIOT_DYNREGMQOPT_PRODUCT_SECRET, (void *)product_secret);
if (res < STATE_SUCCESS) {
printf("aiot_dynregmq_setopt AIOT_DYNREGMQOPT_PRODUCT_SECRET failed, res: -0x%04X\n", -res);
aiot_dynregmq_deinit(&dynregmq_handle);
return -1;
}
/* 配置设备deviceName */
res = aiot_dynregmq_setopt(dynregmq_handle, AIOT_DYNREGMQOPT_DEVICE_NAME, (void *)device_name);
if (res < STATE_SUCCESS) {
printf("aiot_dynregmq_setopt AIOT_DYNREGMQOPT_DEVICE_NAME failed, res: -0x%04X\n", -res);
aiot_dynregmq_deinit(&dynregmq_handle);
return -1;
}
/* 配置网络连接的安全凭据, 上面已经创建好了 */
res = aiot_dynregmq_setopt(dynregmq_handle, AIOT_DYNREGMQOPT_NETWORK_CRED, (void *)&cred);
if (res < STATE_SUCCESS) {
printf("aiot_dynregmq_setopt AIOT_DYNREGMQOPT_NETWORK_CRED failed, res: -0x%04X\n", -res);
aiot_dynregmq_deinit(&dynregmq_handle);
return -1;
}
/* 配置DYNREGMQ默认消息接收回调函数 */
res = aiot_dynregmq_setopt(dynregmq_handle, AIOT_DYNREGMQOPT_RECV_HANDLER, (void *)demo_dynregmq_recv_handler);
if (res < STATE_SUCCESS) {
printf("aiot_dynregmq_setopt AIOT_DYNREGMQOPT_RECV_HANDLER failed, res: -0x%04X\n", -res);
aiot_dynregmq_deinit(&dynregmq_handle);
return -1;
}
/* 配置DYNREGMQ动态注册模式,
1. 配置为0则为白名单模式, 用户必须提前在控制台录入deviceName, 动态注册完成后服务会返回deviceSecret, 用户可通过
AIOT_DYNREGMQRECV_DEVICEINFO_WL类型数据回调获取到deviceSecret.
2. 配置为1则为免白名单模式, 用户无需提前在控制台录入deviceName, 动态注册完成后服务会返回MQTT建连信息, 用户可通过
AIOT_DYNREGMQRECV_DEVICEINFO_NWL类型数据回调获取到clientid, username, password. 用户需要将这三个参数通过
aiot_mqtt_setopt接口以AIOT_MQTTOPT_CLIENTID, AIOT_MQTTOPT_USERNAME, AIOT_MQTTOPT_PASSWORD配置选项
配置到MQTT句柄中。
*/
res = aiot_dynregmq_setopt(dynregmq_handle, AIOT_DYNREGMQOPT_NO_WHITELIST, (void *)&skip_pre_regist);
if (res < STATE_SUCCESS) {
printf("aiot_dynregmq_setopt AIOT_DYNREGMQOPT_NO_WHITELIST failed, res: -0x%04X\n", -res);
aiot_dynregmq_deinit(&dynregmq_handle);
return -1;
}
/* 发送动态注册请求 */
res = aiot_dynregmq_send_request(dynregmq_handle);
if (res < STATE_SUCCESS) {
printf("aiot_dynregmq_send_request failed: -0x%04X\n\r\n", -res);
printf("please check variables like mqtt_host, produt_key, device_name, product_secret in demo\r\n");
aiot_dynregmq_deinit(&dynregmq_handle);
return -1;
}
/* 接收动态注册请求 */
res = aiot_dynregmq_recv(dynregmq_handle);
if (res < STATE_SUCCESS) {
printf("aiot_dynregmq_recv failed: -0x%04X\n", -res);
aiot_dynregmq_deinit(&dynregmq_handle);
return -1;
}
/* 把服务应答中的信息打印出来 */
if (skip_pre_regist == 0) {
printf("device secret: %s\n", demo_devinfo_wl.device_secret);
} else {
printf("clientid: %s\n", demo_devinfo_nwl.conn_clientid);
printf("username: %s\n", demo_devinfo_nwl.conn_username);
printf("password: %s\n", demo_devinfo_nwl.conn_password);
}
/* 销毁动态注册会话实例 */
res = aiot_dynregmq_deinit(&dynregmq_handle);
if (res < STATE_SUCCESS) {
printf("aiot_dynregmq_deinit failed: -0x%04X\n", -res);
return -1;
}
return 0;
}

View File

@@ -0,0 +1,464 @@
/*
* 这个例程适用于不支持通过pthread函数在主线程外开独立的固件下载线程的设备
* 它演示了用SDK配置MQTT参数并建立连接, 并在接收到OTA的mqtt消息后开始下载升级固件的过程
* 同时, 它演示了如何将固件的大小分为两半, 每次下载一半的做法. 用户可以进一步将固件分成更多更小的分段
*
* 需要用户关注或修改的部分, 已用 `TODO` 在注释中标明
*
*/
/* TODO: 本例子用到了sleep函数, 所以包含了unistd.h. 如果用户自己的库中有可以替代的函数, 则可以将unistd.h替换掉
*
* 本例子用到了malloc/free函数, 所以用到了stdlib.h, 用户如果自己库中有可以替代的函数, 则需要将stdlib.h替换掉
*
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include "aiot_state_api.h"
#include "aiot_sysdep_api.h"
#include "aiot_mqtt_api.h"
#include "aiot_ota_api.h"
/* TODO: 替换为自己设备的三元组 */
char *product_key = "${YourProductKey}";
char *device_name = "${YourDeviceName}";
char *device_secret = "${YourDeviceSecret}";
/*
TODO: 替换为自己实例的接入点
对于企业实例, 或者2021年07月30日之后含当日开通的物联网平台服务下公共实例
mqtt_host的格式为"${YourInstanceId}.mqtt.iothub.aliyuncs.com"
其中${YourInstanceId}: 请替换为您企业/公共实例的Id
对于2021年07月30日之前不含当日开通的物联网平台服务下公共实例
需要将mqtt_host修改为: mqtt_host = "${YourProductKey}.iot-as-mqtt.${YourRegionId}.aliyuncs.com"
其中, ${YourProductKey}请替换为设备所属产品的ProductKey。可登录物联网平台控制台在对应实例的设备详情页获取。
${YourRegionId}:请替换为您的物联网平台设备所在地域代码, 比如cn-shanghai等
该情况下完整mqtt_host举例: a1TTmBPIChA.iot-as-mqtt.cn-shanghai.aliyuncs.com
详情请见: https://help.aliyun.com/document_detail/147356.html
*/
char *mqtt_host = "${YourInstanceId}.mqtt.iothub.aliyuncs.com";
/* 位于external/ali_ca_cert.c中的服务器证书 */
extern const char *ali_ca_cert;
void *g_ota_handle = NULL;
void *g_dl_handle = NULL;
uint32_t g_firmware_size = 0;
/* 位于portfiles/aiot_port文件夹下的系统适配函数集合 */
extern aiot_sysdep_portfile_t g_aiot_sysdep_portfile;
/* TODO: 如果要关闭日志, 就把这个函数实现为空, 如果要减少日志, 可根据code选择不打印
*
* 例如: [1578463098.611][LK-0309] pub: /ota/device/upgrade/a13FN5TplKq/ota_demo
*
* 上面这条日志的code就是0309(十六进制), code值的定义见core/aiot_state_api.h
*
*/
/* 日志回调函数, SDK的日志会从这里输出 */
int32_t demo_state_logcb(int32_t code, char *message)
{
/* 下载固件的时候会有大量的HTTP收包日志, 通过code筛选出来关闭 */
if (STATE_HTTP_LOG_RECV_CONTENT != code) {
printf("%s", message);
}
return 0;
}
/* MQTT事件回调函数, 当网络连接/重连/断开时被触发, 事件定义见core/aiot_mqtt_api.h */
void demo_mqtt_event_handler(void *handle, const aiot_mqtt_event_t *const event, void *userdata)
{
switch (event->type) {
/* SDK因为用户调用了aiot_mqtt_connect()接口, 与mqtt服务器建立连接已成功 */
case AIOT_MQTTEVT_CONNECT: {
printf("AIOT_MQTTEVT_CONNECT\r\n");
/* TODO: 处理SDK建连成功, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
/* SDK因为网络状况被动断连后, 自动发起重连已成功 */
case AIOT_MQTTEVT_RECONNECT: {
printf("AIOT_MQTTEVT_RECONNECT\r\n");
/* TODO: 处理SDK重连成功, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
/* SDK因为网络的状况而被动断开了连接, network是底层读写失败, heartbeat是没有按预期得到服务端心跳应答 */
case AIOT_MQTTEVT_DISCONNECT: {
char *cause = (event->data.disconnect == AIOT_MQTTDISCONNEVT_NETWORK_DISCONNECT) ? ("network disconnect") :
("heartbeat disconnect");
printf("AIOT_MQTTEVT_DISCONNECT: %s\r\n", cause);
/* TODO: 处理SDK被动断连, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
default: {
}
}
}
/* MQTT默认消息处理回调, 当SDK从服务器收到MQTT消息时, 且无对应用户回调处理时被调用 */
void demo_mqtt_default_recv_handler(void *handle, const aiot_mqtt_recv_t *const packet, void *userdata)
{
switch (packet->type) {
case AIOT_MQTTRECV_HEARTBEAT_RESPONSE: {
printf("heartbeat response\r\n");
/* TODO: 处理服务器对心跳的回应, 一般不处理 */
}
break;
case AIOT_MQTTRECV_SUB_ACK: {
printf("suback, res: -0x%04X, packet id: %d, max qos: %d\r\n",
-packet->data.sub_ack.res, packet->data.sub_ack.packet_id, packet->data.sub_ack.max_qos);
/* TODO: 处理服务器对订阅请求的回应, 一般不处理 */
}
break;
case AIOT_MQTTRECV_PUB: {
printf("pub, qos: %d, topic: %.*s\r\n", packet->data.pub.qos, packet->data.pub.topic_len, packet->data.pub.topic);
printf("pub, payload: %.*s\r\n", packet->data.pub.payload_len, packet->data.pub.payload);
/* TODO: 处理服务器下发的业务报文 */
}
break;
case AIOT_MQTTRECV_PUB_ACK: {
printf("puback, packet id: %d\r\n", packet->data.pub_ack.packet_id);
/* TODO: 处理服务器对QoS1上报消息的回应, 一般不处理 */
}
break;
default: {
}
}
}
/* 下载收包回调, 用户调用 aiot_download_recv() 后, SDK收到数据会进入这个函数, 把下载到的数据交给用户 */
/* TODO: 一般来说, 设备升级时, 会在这个回调中, 把下载到的数据写到Flash上 */
void user_download_recv_handler(void *handle, const aiot_download_recv_t *packet, void *userdata)
{
int32_t percent = 0;
int32_t last_percent = 0;
uint32_t data_buffer_len = 0;
/* 目前只支持 packet->type 为 AIOT_DLRECV_HTTPBODY 的情况 */
if (!packet || AIOT_DLRECV_HTTPBODY != packet->type) {
return;
}
percent = packet->data.percent;
/* 如果 percent 为负数, 说明发生了收包异常或者digest校验错误 */
if (percent < 0) {
/* digest校验错误 */
printf("exception happend, percent is %d\r\n", percent);
if (userdata) {
free(userdata);
}
return;
}
/* userdata可以存放 demo_download_recv_handler() 的不同次进入之间, 需要共享的数据 */
/* 这里用来存放上一次进入本回调函数时, 下载的固件进度百分比 */
if (userdata) {
last_percent = *((uint32_t *)(userdata));
}
data_buffer_len = packet->data.len;
/*
* TODO: 下载一段固件成功, 这个时候, 用户应该将
* 起始地址为 packet->data.buffer, 长度为 packet->data.len 的内存, 保存到flash上
*
* 如果烧写flash失败, 还应该调用 aiot_download_report_progress(handle, -4) 将失败上报给云平台
* 备注:协议中, 与云平台商定的错误码在 aiot_ota_protocol_errcode_t 类型中, 例如
* -1: 表示升级失败
* -2: 表示下载失败
* -3: 表示校验失败
* -4: 表示烧写失败
*
* 详情可见 https://help.aliyun.com/document_detail/85700.html
*/
/* percent 入参的值为 100 时, 说明SDK已经下载固件内容全部完成 */
if (percent == 100) {
/* 上报版本号 */
/*
* TODO: 这个时候, 一般用户就应该完成所有的固件烧录, 保存当前工作, 重启设备, 切换到新的固件上启动了
* 并且, 新的固件必须要以
*
* aiot_ota_report_version(g_ota_handle, new_version);
*
* 这样的操作, 将升级后的新版本号(比如1.0.0升到1.1.0, 则new_version的值是"1.1.0")上报给云平台
* 云平台收到了新的版本号上报后, 才会判定升级成功, 否则会认为本次升级失败了
*
*/
}
/* 简化输出, 只有距离上次的下载进度增加5%以上时, 才会打印进度, 并向服务器上报进度 */
if (percent - last_percent >= 5 || percent == 100) {
printf("download %03d%% done, +%d bytes\r\n", percent, data_buffer_len);
aiot_download_report_progress(handle, percent);
if (userdata) {
*((uint32_t *)(userdata)) = percent;
}
if (percent == 100 && userdata) {
free(userdata);
}
}
}
/* 用户通过 aiot_ota_setopt() 注册的OTA消息处理回调, 如果SDK收到了OTA相关的MQTT消息, 会自动识别, 调用这个回调函数 */
void user_ota_recv_handler(void *ota_handle, aiot_ota_recv_t *ota_msg, void *userdata)
{
switch (ota_msg->type) {
case AIOT_OTARECV_FOTA: {
uint16_t port = 443;
uint32_t max_buffer_len = 2048;
aiot_sysdep_network_cred_t cred;
void *dl_handle = NULL;
void *last_percent = NULL;
if (NULL == ota_msg->task_desc || ota_msg->task_desc->protocol_type != AIOT_OTA_PROTOCOL_HTTPS) {
break;
}
dl_handle = aiot_download_init();
if (NULL == dl_handle) {
break;
}
last_percent = malloc(sizeof(uint32_t));
if (NULL == last_percent) {
aiot_download_deinit(&dl_handle);
break;
}
memset(last_percent, 0, sizeof(uint32_t));
printf("OTA target firmware version: %s, size: %u Bytes\r\n", ota_msg->task_desc->version,
ota_msg->task_desc->size_total);
if (NULL != ota_msg->task_desc->extra_data) {
printf("extra data: %s\r\n", ota_msg->task_desc->extra_data);
}
g_firmware_size = ota_msg->task_desc->size_total;
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA;
cred.max_tls_fragment = 16384;
cred.x509_server_cert = ali_ca_cert;
cred.x509_server_cert_len = strlen(ali_ca_cert);
uint32_t end = g_firmware_size / 2;
/* 设置下载时为TLS下载 */
if ((STATE_SUCCESS != aiot_download_setopt(dl_handle, AIOT_DLOPT_NETWORK_CRED, (void *)(&cred)))
/* 设置下载时访问的服务器端口号 */
|| (STATE_SUCCESS != aiot_download_setopt(dl_handle, AIOT_DLOPT_NETWORK_PORT, (void *)(&port)))
/* 设置下载的任务信息, 通过输入参数 ota_msg 中的 task_desc 成员得到, 内含下载地址, 固件大小, 固件签名等 */
|| (STATE_SUCCESS != aiot_download_setopt(dl_handle, AIOT_DLOPT_TASK_DESC, (void *)(ota_msg->task_desc)))
/* 设置下载内容到达时, SDK将调用的回调函数 */
|| (STATE_SUCCESS != aiot_download_setopt(dl_handle, AIOT_DLOPT_RECV_HANDLER, (void *)(user_download_recv_handler)))
/* 设置单次下载最大的buffer长度, 每当这个长度的内存读满了后会通知用户 */
|| (STATE_SUCCESS != aiot_download_setopt(dl_handle, AIOT_DLOPT_BODY_BUFFER_MAX_LEN, (void *)(&max_buffer_len)))
/* 设置 AIOT_DLOPT_RECV_HANDLER 的不同次调用之间共享的数据, 比如例程把进度存在这里 */
|| (STATE_SUCCESS != aiot_download_setopt(dl_handle, AIOT_DLOPT_USERDATA, (void *)last_percent))
/* 指明下载方式是按照range下载, 并且当前只下载一半 */
|| (STATE_SUCCESS != aiot_download_setopt(dl_handle, AIOT_DLOPT_RANGE_END, (void *)&end))
/* 发送http的GET请求给http服务器 */
|| (STATE_SUCCESS != aiot_download_send_request(dl_handle))) {
aiot_download_deinit(&dl_handle);
free(last_percent);
break;
}
g_dl_handle = dl_handle;
break;
}
default:
break;
}
}
int main(int argc, char *argv[])
{
int32_t res = STATE_SUCCESS;
void *mqtt_handle = NULL;
uint16_t port = 443; /* 无论设备是否使用TLS连接阿里云平台, 目的端口都是443 */
aiot_sysdep_network_cred_t cred; /* 安全凭据结构体, 如果要用TLS, 这个结构体中配置CA证书等参数 */
char *cur_version = NULL;
void *ota_handle = NULL;
uint32_t timeout_ms = 0;
/* 配置SDK的底层依赖 */
aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
/* 配置SDK的日志输出 */
aiot_state_set_logcb(demo_state_logcb);
/* 创建SDK的安全凭据, 用于建立TLS连接 */
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA; /* 使用RSA证书校验MQTT服务端 */
cred.max_tls_fragment = 16384; /* 最大的分片长度为16K, 其它可选值还有4K, 2K, 1K, 0.5K */
cred.sni_enabled = 1; /* TLS建连时, 支持Server Name Indicator */
cred.x509_server_cert = ali_ca_cert; /* 用来验证MQTT服务端的RSA根证书 */
cred.x509_server_cert_len = strlen(ali_ca_cert); /* 用来验证MQTT服务端的RSA根证书长度 */
/* 创建1个MQTT客户端实例并内部初始化默认参数 */
mqtt_handle = aiot_mqtt_init();
if (mqtt_handle == NULL) {
return -1;
}
/* 配置MQTT服务器地址 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_HOST, (void *)mqtt_host);
/* 配置MQTT服务器端口 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PORT, (void *)&port);
/* 配置设备productKey */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PRODUCT_KEY, (void *)product_key);
/* 配置设备deviceName */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_NAME, (void *)device_name);
/* 配置设备deviceSecret */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_SECRET, (void *)device_secret);
/* 配置网络连接的安全凭据, 上面已经创建好了 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_NETWORK_CRED, (void *)&cred);
/* 配置MQTT默认消息接收回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_RECV_HANDLER, (void *)demo_mqtt_default_recv_handler);
/* 配置MQTT事件回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_EVENT_HANDLER, (void *)demo_mqtt_event_handler);
/* 与MQTT例程不同的是, 这里需要增加创建OTA会话实例的语句 */
ota_handle = aiot_ota_init();
if (NULL == ota_handle) {
goto exit;
}
/* 用以下语句, 把OTA会话和MQTT会话关联起来 */
aiot_ota_setopt(ota_handle, AIOT_OTAOPT_MQTT_HANDLE, mqtt_handle);
/* 用以下语句, 设置OTA会话的数据接收回调, SDK收到OTA相关推送时, 会进入这个回调函数 */
aiot_ota_setopt(ota_handle, AIOT_OTAOPT_RECV_HANDLER, user_ota_recv_handler);
g_ota_handle = ota_handle;
/* 与服务器建立MQTT连接 */
res = aiot_mqtt_connect(mqtt_handle);
if (res < STATE_SUCCESS) {
printf("aiot_mqtt_connect failed: -0x%04X\r\n\r\n", -res);
printf("please check variables like mqtt_host, produt_key, device_name, device_secret in demo\r\n");
/* 尝试建立连接失败, 销毁MQTT实例, 回收资源 */
goto exit;
}
/* TODO: 非常重要!!!
*
* cur_version 要根据用户实际情况, 改成从设备的配置区获取, 要反映真实的版本号, 而不能像示例这样写为固定值
*
* 1. 如果设备从未上报过版本号, 在控制台网页将无法部署升级任务
* 2. 如果设备升级完成后, 上报的不是新的版本号, 在控制台网页将会显示升级失败
*
*/
cur_version = "1.0.0";
/* 演示MQTT连接建立起来之后, 就可以上报当前设备的版本号了 */
res = aiot_ota_report_version(ota_handle, cur_version);
if (res < STATE_SUCCESS) {
printf("report version failed, code is -0x%04X\r\n", -res);
}
while (1) {
aiot_mqtt_process(mqtt_handle);
res = aiot_mqtt_recv(mqtt_handle);
if (res == STATE_SYS_DEPEND_NWK_CLOSED) {
sleep(1);
}
if (NULL != g_dl_handle) {
/* 完成固件的接收前, 将mqtt的收包超时调整到100ms, 以减少两次固件下载动作之间的时间间隔 */
int32_t ret = aiot_download_recv(g_dl_handle);
timeout_ms = 100;
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_RECV_TIMEOUT_MS, (void *)&timeout_ms);
/* 本例子中, 将整个固件分为2个等份来下载, 每次下载一半, 第一半下载完成时会进入这个分支 */
if (STATE_DOWNLOAD_RANGE_FINISHED == ret) {
printf("the first half download finished\n");
/* 当第一次下载完成后, 要重新设置range的start/end值, 下载另外一半 */
/*
* 非常重要:
*
* 上一次下载的范围是[0, g_firmware_size/2],
* 因此这一次下载需要从 [g_firmware_size/2 + 1, 固件结束地址],
* 其中固件结束地址填0的话就表示直到下载结束
*
*/
uint32_t start = g_firmware_size / 2 + 1;
uint32_t end = 0;
aiot_download_setopt(g_dl_handle, AIOT_DLOPT_RANGE_START, (void *)&start);
/* range_end如果指定为0, 服务器就理解为下载到文件末尾后结束 */
aiot_download_setopt(g_dl_handle, AIOT_DLOPT_RANGE_END, (void *)&end);
/* 向服务器发起下一次下载的请求 */
aiot_download_send_request(g_dl_handle);
continue;
}
/* 整个固件下载完成 */
if (ret == STATE_DOWNLOAD_FINISHED) {
printf("down finished all\n");
aiot_download_deinit(&g_dl_handle);
/* 完成固件的接收后, 将mqtt的收包超时调整回到默认值5000ms */
timeout_ms = 5000;
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_RECV_TIMEOUT_MS, (void *)&timeout_ms);
continue;
}
/* 下载的内容超出了固件的总大小, 可能是用户把range划分错了, range之间有重叠 */
if (ret == STATE_DOWNLOAD_FETCH_TOO_MANY) {
printf("downloaded more than expeced bytes, please check range start/end settings\n");
aiot_download_deinit(&g_dl_handle);
/* 完成固件的接收后, 将mqtt的收包超时调整回到默认值5000ms */
timeout_ms = 5000;
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_RECV_TIMEOUT_MS, (void *)&timeout_ms);
continue;
}
if (STATE_DOWNLOAD_RENEWAL_REQUEST_SENT == ret) {
printf("download renewal request has been sent successfully\r\n");
continue;
}
if (ret <= STATE_SUCCESS) {
printf("download failed, error code is %d, try to send renewal request\r\n", ret);
continue;
}
}
}
/* 断开MQTT连接, 一般不会运行到这里 */
res = aiot_mqtt_disconnect(mqtt_handle);
if (res < STATE_SUCCESS) {
printf("aiot_mqtt_disconnect failed: -0x%04X\r\n", -res);
goto exit;
}
exit:
while (1) {
/* 销毁MQTT实例, 一般不会运行到这里 */
res = aiot_mqtt_deinit(&mqtt_handle);
if (res < STATE_SUCCESS) {
printf("aiot_mqtt_deinit failed: -0x%04X\r\n", -res);
return -1;
} else {
break;
}
}
/* 销毁OTA实例, 一般不会运行到这里 */
aiot_ota_deinit(&ota_handle);
return 0;
}

View File

@@ -0,0 +1,510 @@
/*
* 这个例程适用于`Linux`这类支持pthread的POSIX设备, 它演示了用SDK配置MQTT参数并建立连接, 之后创建3个线程
*
* + 一个线程用于保活长连接
* + 一个线程用于接收消息, 并在有消息到达时进入默认的数据回调, 在连接状态变化时进入事件回调
* + 一个线程用于从网络上HTTP下载待升级的固件, 这个线程由接收消息线程得到OTA升级的MQTT消息后启动
*
* 本例讲述了单个升级包中多个url情况下设备如何升级的用例
* 需要用户关注或修改的部分, 已用 `TODO` 在注释中标明
*
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include "aiot_state_api.h"
#include "aiot_sysdep_api.h"
#include "aiot_ota_api.h"
#include "aiot_mqtt_api.h"
/* TODO: 替换为自己设备的三元组 */
char *product_key = "${YourProductKey}";
char *device_name = "${YourDeviceName}";
char *device_secret = "${YourDeviceSecret}";
/*
TODO: 替换为自己实例的接入点
对于企业实例, 或者2021年07月30日之后含当日开通的物联网平台服务下公共实例
mqtt_host的格式为"${YourInstanceId}.mqtt.iothub.aliyuncs.com"
其中${YourInstanceId}: 请替换为您企业/公共实例的Id
对于2021年07月30日之前不含当日开通的物联网平台服务下公共实例
需要将mqtt_host修改为: mqtt_host = "${YourProductKey}.iot-as-mqtt.${YourRegionId}.aliyuncs.com"
其中, ${YourProductKey}请替换为设备所属产品的ProductKey。可登录物联网平台控制台在对应实例的设备详情页获取。
${YourRegionId}:请替换为您的物联网平台设备所在地域代码, 比如cn-shanghai等
该情况下完整mqtt_host举例: a1TTmBPIChA.iot-as-mqtt.cn-shanghai.aliyuncs.com
详情请见: https://help.aliyun.com/document_detail/147356.html
*/
char *mqtt_host = "${YourInstanceId}.mqtt.iothub.aliyuncs.com";
/* 收发包动作是要结束 */
int should_stop = 0;
/* 位于portfiles/aiot_port文件夹下的系统适配函数集合 */
extern aiot_sysdep_portfile_t g_aiot_sysdep_portfile;
/* 位于external/ali_ca_cert.c中的服务器证书 */
extern const char *ali_ca_cert;
static pthread_t g_mqtt_process_thread; /* 用于MQTT的长连接保活线程 */
static pthread_t g_mqtt_recv_thread; /* 用于MQTT的循环收消息线程 */
static pthread_t g_download_thread; /* 用于HTTP的固件下载线程 */
/* TODO: 如果要关闭日志, 就把这个函数实现为空, 如果要减少日志, 可根据code选择不打印
*
* 例如: [1578463098.611][LK-0309] pub: /ota/device/upgrade/a13FN5TplKq/ota_demo
*
* 上面这条日志的code就是0309(十六进制), code值的定义见core/aiot_state_api.h
*
*/
/* 单升级包多文件情况下的整体任务进度描述 */
typedef struct {
int32_t last_percent;
uint32_t file_id;
uint32_t file_num;
} multi_download_status_t;
int32_t g_finished_task_num = 0;
/* 日志回调函数, SDK的日志会从这里输出 */
int32_t demo_state_logcb(int32_t code, char *message)
{
/* 下载固件的时候会有大量的HTTP收包日志, 通过code筛选出来关闭 */
if (STATE_HTTP_LOG_RECV_CONTENT != code) {
printf("%s", message);
}
return 0;
}
/* 下载收包回调, 用户调用 aiot_download_recv() 后, SDK收到数据会进入这个函数, 把下载到的数据交给用户 */
/* TODO: 一般来说, 设备升级时, 会在这个回调中, 把下载到的数据写到Flash上 */
void demo_download_recv_handler(void *handle, const aiot_download_recv_t *packet, void *userdata)
{
uint32_t data_buffer_len = 0;
int32_t last_percent = 0;
int32_t percent = 0;
multi_download_status_t *download_status = (multi_download_status_t *)userdata;
/* 目前只支持 packet->type 为 AIOT_DLRECV_HTTPBODY 的情况 */
if (!packet || AIOT_DLRECV_HTTPBODY != packet->type) {
return;
}
percent = packet->data.percent;
/* userdata可以存放 demo_download_recv_handler() 的不同次进入之间, 需要共享的数据 */
/* 这里用来存放上一次进入本回调函数时, 下载的固件进度百分比 */
if (userdata) {
last_percent = (download_status->last_percent);
}
data_buffer_len = packet->data.len;
/* 如果 percent 为负数, 说明发生了收包异常或者digest校验错误 */
if (percent < 0) {
printf("exception: percent = %d\r\n", percent);
if (userdata) {
free(userdata);
}
return;
}
/*
* TODO: 下载一段固件成功, 这个时候, 用户应该将
* 起始地址为 packet->data.buffer, 长度为 packet->data.len 的内存, 保存到flash上
*
* 如果烧写flash失败, 还应该调用 aiot_download_report_progress(handle, -4) 将失败上报给云平台
* 备注:协议中, 与云平台商定的错误码在 aiot_ota_protocol_errcode_t 类型中, 例如
* -1: 表示升级失败
* -2: 表示下载失败
* -3: 表示校验失败
* -4: 表示烧写失败
*
* 详情可见 https://help.aliyun.com/document_detail/85700.html
*/
/* percent 入参的值为 100 时, 说明SDK已经下载固件内容全部完成 */
if (percent == 100) {
g_finished_task_num++;
/*
* TODO: 这个时候, 一般用户就应该完成所有的固件烧录, 保存当前工作, 重启设备, 切换到新的固件上启动了
并且, 新的固件必须要以
aiot_ota_report_version(ota_handle, new_version);
这样的操作, 将升级后的新版本号(比如1.0.0升到1.1.0, 则new_version的值是"1.1.0")上报给云平台
云平台收到了新的版本号上报后, 才会判定升级成功, 否则会认为本次升级失败了
如果下载成功后升级失败, 还应该调用 aiot_download_report_progress(handle, -1) 将失败上报给云平台
*/
}
/* 简化输出, 只有距离上次的下载进度增加5%以上时, 才会打印进度, 并向服务器上报进度 */
if (percent - last_percent >= 5 || percent == 100) {
if (NULL != download_status) {
printf("file_id %d, download %03d%% done, +%d bytes\r\n", download_status->file_id, percent, data_buffer_len);
download_status->last_percent = percent;
if (g_finished_task_num == download_status->file_num) {
/* 考虑到多个线程并发下载, 仅仅在所有文件下载完成后才上报100%的进度 */
aiot_download_report_progress(handle, 100);
}
}
if (percent == 100 && userdata) {
free(userdata);
}
}
}
/* 执行aiot_download_recv的线程, 实现固件内容的请求和接收 */
void *demo_ota_download_thread(void *dl_handle)
{
int32_t ret = 0;
printf("\r\nstarting download thread in 2 seconds ......\r\n");
sleep(2);
/* 向固件服务器请求下载 */
/*
* TODO: 下面这样的写法, 就是以1个请求, 获取全部的固件内容
* 设备资源比较少, 或者网络较差时, 也可以分段下载, 需要组合
*
* aiot_download_setopt(dl_handle, AIOT_DLOPT_RANGE_START, ...);
* aiot_download_setopt(dl_handle, AIOT_DLOPT_RANGE_END, ...);
* aiot_download_send_request(dl_handle);
*
* 实现, 这种情况下, 需要把以上组合语句放置到循环中, 多次 send_request 和 recv
*
*/
aiot_download_send_request(dl_handle);
while (should_stop == 0) {
/* 从网络收取服务器回应的固件内容 */
ret = aiot_download_recv(dl_handle);
/* 固件全部下载完时, aiot_download_recv() 的返回值会等于 STATE_DOWNLOAD_FINISHED, 否则是当次获取的字节数 */
if (STATE_DOWNLOAD_FINISHED == ret) {
printf("download completed\r\n");
break;
}
if (STATE_DOWNLOAD_RENEWAL_REQUEST_SENT == ret) {
printf("download renewal request has been sent successfully\r\n");
continue;
}
if (ret <= STATE_SUCCESS) {
printf("download failed, error code is %d, try to send renewal request\r\n", ret);
continue;
}
}
/* 下载所有固件内容完成, 销毁下载会话, 线程自行退出 */
aiot_download_deinit(&dl_handle);
printf("download thread exit\r\n");
return NULL;
}
/* 用户通过 aiot_ota_setopt() 注册的OTA消息处理回调, 如果SDK收到了OTA相关的MQTT消息, 会自动识别, 调用这个回调函数 */
void demo_ota_recv_handler(void *ota_handle, aiot_ota_recv_t *ota_msg, void *userdata)
{
switch (ota_msg->type) {
case AIOT_OTARECV_FOTA: {
uint32_t res = 0;
uint16_t port = 443;
uint32_t max_buffer_len = (8 * 1024);
aiot_sysdep_network_cred_t cred;
void *dl_handle = NULL;
multi_download_status_t *download_status = NULL;
if (NULL == ota_msg->task_desc || ota_msg->task_desc->protocol_type != AIOT_OTA_PROTOCOL_HTTPS) {
break;
}
dl_handle = aiot_download_init();
if (NULL == dl_handle) {
break;
}
if (NULL != ota_msg->task_desc->file_name) {
printf("\r\nTotal file number is %d, current file id is %d, with file_name %s\r\n", ota_msg->task_desc->file_num,
ota_msg->task_desc->file_id, ota_msg->task_desc->file_name);
}
printf("OTA target firmware version: %s, size: %u Bytes \r\n", ota_msg->task_desc->version,
ota_msg->task_desc->size_total);
if (NULL != ota_msg->task_desc->extra_data) {
printf("extra data: %s\r\n", ota_msg->task_desc->extra_data);
}
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA;
cred.max_tls_fragment = 16384;
cred.x509_server_cert = ali_ca_cert;
cred.x509_server_cert_len = strlen(ali_ca_cert);
/* 设置下载时为TLS下载 */
aiot_download_setopt(dl_handle, AIOT_DLOPT_NETWORK_CRED, (void *)(&cred));
/* 设置下载时访问的服务器端口号 */
aiot_download_setopt(dl_handle, AIOT_DLOPT_NETWORK_PORT, (void *)(&port));
/* 设置下载的任务信息, 通过输入参数 ota_msg 中的 task_desc 成员得到, 内含下载地址, 固件大小, 固件签名等 */
aiot_download_setopt(dl_handle, AIOT_DLOPT_TASK_DESC, (void *)(ota_msg->task_desc));
/* 设置下载内容到达时, SDK将调用的回调函数 */
aiot_download_setopt(dl_handle, AIOT_DLOPT_RECV_HANDLER, (void *)(demo_download_recv_handler));
/* 设置单次下载最大的buffer长度, 每当这个长度的内存读满了后会通知用户 */
aiot_download_setopt(dl_handle, AIOT_DLOPT_BODY_BUFFER_MAX_LEN, (void *)(&max_buffer_len));
/* 设置 AIOT_DLOPT_RECV_HANDLER 的不同次调用之间共享的数据, 比如例程把进度存在这里 */
download_status = malloc(sizeof(multi_download_status_t));
if (NULL == download_status) {
aiot_download_deinit(&dl_handle);
break;
}
memset(download_status, 0, sizeof(multi_download_status_t));
download_status->file_id = ota_msg->task_desc->file_id;
download_status->file_num = ota_msg->task_desc->file_num;
aiot_download_setopt(dl_handle, AIOT_DLOPT_USERDATA, (void *)download_status);
/* 如果是第一个下载任务, 则上报进度0 */
if (0 == ota_msg->task_desc->file_id) {
aiot_download_report_progress(dl_handle, 0);
}
/* 启动专用的下载线程, 去完成固件内容的下载 */
res = pthread_create(&g_download_thread, NULL, demo_ota_download_thread, dl_handle);
if (res != 0) {
printf("pthread_create demo_ota_download_thread failed: %d\r\n", res);
aiot_download_deinit(&dl_handle);
free(download_status);
} else {
/* 下载线程被设置为 detach 类型, 固件内容获取完毕后可自主退出 */
pthread_detach(g_download_thread);
}
break;
}
default:
break;
}
}
/* MQTT事件回调函数, 当网络连接/重连/断开时被触发, 事件定义见core/aiot_mqtt_api.h */
void demo_mqtt_event_handler(void *handle, const aiot_mqtt_event_t *event, void *userdata)
{
switch (event->type) {
/* SDK因为用户调用了aiot_mqtt_connect()接口, 与mqtt服务器建立连接已成功 */
case AIOT_MQTTEVT_CONNECT: {
printf("AIOT_MQTTEVT_CONNECT\r\n");
/* TODO: 处理SDK建连成功, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
/* SDK因为网络状况被动断连后, 自动发起重连已成功 */
case AIOT_MQTTEVT_RECONNECT: {
printf("AIOT_MQTTEVT_RECONNECT\r\n");
/* TODO: 处理SDK重连成功, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
/* SDK因为网络的状况而被动断开了连接, network是底层读写失败, heartbeat是没有按预期得到服务端心跳应答 */
case AIOT_MQTTEVT_DISCONNECT: {
char *cause = (event->data.disconnect == AIOT_MQTTDISCONNEVT_NETWORK_DISCONNECT) ? ("network disconnect") :
("heartbeat disconnect");
printf("AIOT_MQTTEVT_DISCONNECT: %s\r\n", cause);
/* TODO: 处理SDK被动断连, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
default: {
}
}
}
/* MQTT默认消息处理回调, 当SDK从服务器收到MQTT消息时, 且无对应用户回调处理时被调用 */
void demo_mqtt_default_recv_handler(void *handle, const aiot_mqtt_recv_t *packet, void *userdata)
{
switch (packet->type) {
case AIOT_MQTTRECV_HEARTBEAT_RESPONSE: {
printf("heartbeat response\r\n");
/* TODO: 处理服务器对心跳的回应, 一般不处理 */
}
break;
case AIOT_MQTTRECV_SUB_ACK: {
printf("suback, res: -0x%04X, packet id: %d, max qos: %d\r\n",
-packet->data.sub_ack.res, packet->data.sub_ack.packet_id, packet->data.sub_ack.max_qos);
/* TODO: 处理服务器对订阅请求的回应, 一般不处理 */
}
break;
case AIOT_MQTTRECV_PUB: {
printf("pub, qos: %d, topic: %.*s\r\n", packet->data.pub.qos, packet->data.pub.topic_len, packet->data.pub.topic);
printf("pub, payload: %.*s\r\n", packet->data.pub.payload_len, packet->data.pub.payload);
/* TODO: 处理服务器下发的业务报文 */
}
break;
case AIOT_MQTTRECV_PUB_ACK: {
printf("puback, packet id: %d\r\n", packet->data.pub_ack.packet_id);
/* TODO: 处理服务器对QoS1上报消息的回应, 一般不处理 */
}
break;
default: {
}
}
}
/* 执行aiot_mqtt_process的线程, 包含心跳发送和QoS1消息重发 */
void *demo_mqtt_process_thread(void *args)
{
while (should_stop == 0) {
aiot_mqtt_process(args);
sleep(1);
}
return NULL;
}
/* 执行aiot_mqtt_recv的线程, 包含网络自动重连和从服务器收取MQTT消息 */
void *demo_mqtt_recv_thread(void *args)
{
int32_t res = STATE_SUCCESS;
while (should_stop == 0) {
res = aiot_mqtt_recv(args);
if (res < STATE_SUCCESS) {
sleep(1);
}
}
return NULL;
}
int main(int argc, char *argv[])
{
int32_t res = STATE_SUCCESS;
void *mqtt_handle = NULL;
uint16_t port = 443; /* 无论设备是否使用TLS连接阿里云平台, 目的端口都是443 */
aiot_sysdep_network_cred_t cred; /* 安全凭据结构体, 如果要用TLS, 这个结构体中配置CA证书等参数 */
void *ota_handle = NULL;
char *cur_version = NULL;
/* 配置SDK的底层依赖 */
aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
/* 配置SDK的日志输出 */
aiot_state_set_logcb(demo_state_logcb);
/* 创建SDK的安全凭据, 用于建立TLS连接 */
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA; /* 使用RSA证书校验MQTT服务端 */
cred.max_tls_fragment = 16384; /* 最大的分片长度为16K, 其它可选值还有4K, 2K, 1K, 0.5K */
cred.sni_enabled = 1; /* TLS建连时, 支持Server Name Indicator */
cred.x509_server_cert = ali_ca_cert; /* 用来验证MQTT服务端的RSA根证书 */
cred.x509_server_cert_len = strlen(ali_ca_cert); /* 用来验证MQTT服务端的RSA根证书长度 */
/* 创建1个MQTT客户端实例并内部初始化默认参数 */
mqtt_handle = aiot_mqtt_init();
if (NULL == mqtt_handle) {
printf("aiot_mqtt_init failed\r\n");
return -1;
}
/* 配置MQTT服务器地址 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_HOST, (void *)mqtt_host);
/* 配置MQTT服务器端口 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PORT, (void *)&port);
/* 配置设备productKey */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PRODUCT_KEY, (void *)product_key);
/* 配置设备deviceName */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_NAME, (void *)device_name);
/* 配置设备deviceSecret */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_SECRET, (void *)device_secret);
/* 配置网络连接的安全凭据, 上面已经创建好了 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_NETWORK_CRED, (void *)&cred);
/* 配置MQTT默认消息接收回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_RECV_HANDLER, (void *)demo_mqtt_default_recv_handler);
/* 配置MQTT事件回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_EVENT_HANDLER, (void *)demo_mqtt_event_handler);
/* 与MQTT例程不同的是, 这里需要增加创建OTA会话实例的语句 */
ota_handle = aiot_ota_init();
if (NULL == ota_handle) {
printf("aiot_ota_init failed\r\n");
aiot_mqtt_deinit(&mqtt_handle);
return -2;
}
/* 用以下语句, 把OTA会话和MQTT会话关联起来 */
aiot_ota_setopt(ota_handle, AIOT_OTAOPT_MQTT_HANDLE, mqtt_handle);
/* 用以下语句, 设置OTA会话的数据接收回调, SDK收到OTA相关推送时, 会进入这个回调函数 */
aiot_ota_setopt(ota_handle, AIOT_OTAOPT_RECV_HANDLER, demo_ota_recv_handler);
/* 与服务器建立MQTT连接 */
res = aiot_mqtt_connect(mqtt_handle);
if (res < STATE_SUCCESS) {
/* 尝试建立连接失败, 销毁MQTT实例, 回收资源 */
aiot_mqtt_deinit(&mqtt_handle);
aiot_ota_deinit(&ota_handle);
printf("aiot_mqtt_connect failed: -0x%04X\r\n\r\n", -res);
printf("please check variables like mqtt_host, produt_key, device_name, device_secret in demo\r\n");
return -3;
}
/* TODO: 非常重要!!!
*
* cur_version 要根据用户实际情况, 改成从设备的配置区获取, 要反映真实的版本号, 而不能像示例这样写为固定值
*
* 1. 如果设备从未上报过版本号, 在控制台网页将无法部署升级任务
* 2. 如果设备升级完成后, 上报的不是新的版本号, 在控制台网页将会显示升级失败
*
*/
/* 演示MQTT连接建立起来之后, 就可以上报当前设备的版本号了 */
cur_version = "1.0.0";
res = aiot_ota_report_version(ota_handle, cur_version);
if (res < STATE_SUCCESS) {
printf("aiot_ota_report_version failed: -0x%04X\r\n", -res);
}
/* 创建一个单独的线程, 专用于执行aiot_mqtt_process, 它会自动发送心跳保活, 以及重发QoS1的未应答报文 */
res = pthread_create(&g_mqtt_process_thread, NULL, demo_mqtt_process_thread, mqtt_handle);
if (res != 0) {
printf("pthread_create demo_mqtt_process_thread failed: %d\r\n", res);
return -1;
}
/* 创建一个单独的线程用于执行aiot_mqtt_recv, 它会循环收取服务器下发的MQTT消息, 并在断线时自动重连 */
res = pthread_create(&g_mqtt_recv_thread, NULL, demo_mqtt_recv_thread, mqtt_handle);
if (res != 0) {
printf("pthread_create demo_mqtt_recv_thread failed: %d\r\n", res);
return -1;
}
/* 主循环进入休眠 */
while (1) {
sleep(1);
}
should_stop = 1;
sleep(5);
/* 断开MQTT连接, 一般不会运行到这里 */
res = aiot_mqtt_disconnect(mqtt_handle);
if (res < STATE_SUCCESS) {
aiot_mqtt_deinit(&mqtt_handle);
aiot_ota_deinit(&ota_handle);
printf("aiot_mqtt_disconnect failed: -0x%04X\r\n", -res);
return -1;
}
/* 销毁OTA实例, 一般不会运行到这里 */
aiot_ota_deinit(&ota_handle);
return 0;
}

View File

@@ -0,0 +1,482 @@
/*
* 这个例程适用于`Linux`这类支持pthread的POSIX设备, 它演示了用SDK配置MQTT参数并建立连接, 之后创建3个线程
*
* + 一个线程用于保活长连接
* + 一个线程用于接收消息, 并在有消息到达时进入默认的数据回调, 在连接状态变化时进入事件回调
* + 一个线程用于从网络上HTTP下载待升级的固件, 这个线程由接收消息线程得到OTA升级的MQTT消息后启动
*
* 需要用户关注或修改的部分, 已用 `TODO` 在注释中标明
*
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include "aiot_state_api.h"
#include "aiot_sysdep_api.h"
#include "aiot_ota_api.h"
#include "aiot_mqtt_api.h"
/* TODO: 替换为自己设备的三元组 */
char *product_key = "${YourProductKey}";
char *device_name = "${YourDeviceName}";
char *device_secret = "${YourDeviceSecret}";
/*
TODO: 替换为自己实例的接入点
对于企业实例, 或者2021年07月30日之后含当日开通的物联网平台服务下公共实例
mqtt_host的格式为"${YourInstanceId}.mqtt.iothub.aliyuncs.com"
其中${YourInstanceId}: 请替换为您企业/公共实例的Id
对于2021年07月30日之前不含当日开通的物联网平台服务下公共实例
需要将mqtt_host修改为: mqtt_host = "${YourProductKey}.iot-as-mqtt.${YourRegionId}.aliyuncs.com"
其中, ${YourProductKey}请替换为设备所属产品的ProductKey。可登录物联网平台控制台在对应实例的设备详情页获取。
${YourRegionId}:请替换为您的物联网平台设备所在地域代码, 比如cn-shanghai等
该情况下完整mqtt_host举例: a1TTmBPIChA.iot-as-mqtt.cn-shanghai.aliyuncs.com
详情请见: https://help.aliyun.com/document_detail/147356.html
*/
char *mqtt_host = "${YourInstanceId}.mqtt.iothub.aliyuncs.com";
/* 位于portfiles/aiot_port文件夹下的系统适配函数集合 */
extern aiot_sysdep_portfile_t g_aiot_sysdep_portfile;
/* 位于external/ali_ca_cert.c中的服务器证书 */
extern const char *ali_ca_cert;
static pthread_t g_mqtt_process_thread; /* 用于MQTT的长连接保活线程 */
static pthread_t g_mqtt_recv_thread; /* 用于MQTT的循环收消息线程 */
static pthread_t g_download_thread; /* 用于HTTP的固件下载线程 */
/* TODO: 如果要关闭日志, 就把这个函数实现为空, 如果要减少日志, 可根据code选择不打印
*
* 例如: [1578463098.611][LK-0309] pub: /ota/device/upgrade/a13FN5TplKq/ota_demo
*
* 上面这条日志的code就是0309(十六进制), code值的定义见core/aiot_state_api.h
*
*/
/* 日志回调函数, SDK的日志会从这里输出 */
int32_t demo_state_logcb(int32_t code, char *message)
{
/* 下载固件的时候会有大量的HTTP收包日志, 通过code筛选出来关闭 */
if (STATE_HTTP_LOG_RECV_CONTENT != code) {
printf("%s", message);
}
return 0;
}
/* 下载收包回调, 用户调用 aiot_download_recv() 后, SDK收到数据会进入这个函数, 把下载到的数据交给用户 */
/* TODO: 一般来说, 设备升级时, 会在这个回调中, 把下载到的数据写到Flash上 */
void demo_download_recv_handler(void *handle, const aiot_download_recv_t *packet, void *userdata)
{
uint32_t data_buffer_len = 0;
uint32_t last_percent = 0;
int32_t percent = 0;
/* 目前只支持 packet->type 为 AIOT_DLRECV_HTTPBODY 的情况 */
if (!packet || AIOT_DLRECV_HTTPBODY != packet->type) {
return;
}
percent = packet->data.percent;
/* userdata可以存放 demo_download_recv_handler() 的不同次进入之间, 需要共享的数据 */
/* 这里用来存放上一次进入本回调函数时, 下载的固件进度百分比 */
if (userdata) {
last_percent = *((uint32_t *)(userdata));
}
data_buffer_len = packet->data.len;
/* 如果 percent 为负数, 说明发生了收包异常或者digest校验错误 */
if (percent < 0) {
printf("exception: percent = %d\r\n", percent);
if (userdata) {
free(userdata);
}
return;
}
/*
* TODO: 下载一段固件成功, 这个时候, 用户应该将
* 起始地址为 packet->data.buffer, 长度为 packet->data.len 的内存, 保存到flash上
*
* 如果烧写flash失败, 还应该调用 aiot_download_report_progress(handle, -4) 将失败上报给云平台
* 备注:协议中, 与云平台商定的错误码在 aiot_ota_protocol_errcode_t 类型中, 例如
* -1: 表示升级失败
* -2: 表示下载失败
* -3: 表示校验失败
* -4: 表示烧写失败
*
* 详情可见 https://help.aliyun.com/document_detail/85700.html
*/
/* percent 入参的值为 100 时, 说明SDK已经下载固件内容全部完成 */
if (percent == 100) {
/*
* TODO: 这个时候, 一般用户就应该完成所有的固件烧录, 保存当前工作, 重启设备, 切换到新的固件上启动了
并且, 新的固件必须要以
aiot_ota_report_version(ota_handle, new_version);
这样的操作, 将升级后的新版本号(比如1.0.0升到1.1.0, 则new_version的值是"1.1.0")上报给云平台
云平台收到了新的版本号上报后, 才会判定升级成功, 否则会认为本次升级失败了
如果下载成功后升级失败, 还应该调用 aiot_download_report_progress(handle, -1) 将失败上报给云平台
*/
}
/* 简化输出, 只有距离上次的下载进度增加5%以上时, 才会打印进度, 并向服务器上报进度 */
if (percent - last_percent >= 5 || percent == 100) {
printf("download %03d%% done, +%d bytes\r\n", percent, data_buffer_len);
aiot_download_report_progress(handle, percent);
if (userdata) {
*((uint32_t *)(userdata)) = percent;
}
if (percent == 100 && userdata) {
free(userdata);
}
}
}
/* 执行aiot_download_recv的线程, 实现固件内容的请求和接收 */
void *demo_ota_download_thread(void *dl_handle)
{
int32_t ret = 0;
printf("starting download thread in 2 seconds ......\r\n");
sleep(2);
/* 向固件服务器请求下载 */
/*
* TODO: 下面这样的写法, 就是以1个请求, 获取全部的固件内容
* 设备资源比较少, 或者网络较差时, 也可以分段下载, 需要组合
*
* aiot_download_setopt(dl_handle, AIOT_DLOPT_RANGE_START, ...);
* aiot_download_setopt(dl_handle, AIOT_DLOPT_RANGE_END, ...);
* aiot_download_send_request(dl_handle);
*
* 实现, 这种情况下, 需要把以上组合语句放置到循环中, 多次 send_request 和 recv
*
*/
aiot_download_send_request(dl_handle);
while (1) {
/* 从网络收取服务器回应的固件内容 */
ret = aiot_download_recv(dl_handle);
/* 固件全部下载完时, aiot_download_recv() 的返回值会等于 STATE_DOWNLOAD_FINISHED, 否则是当次获取的字节数 */
if (STATE_DOWNLOAD_FINISHED == ret) {
printf("download completed\r\n");
break;
}
if (STATE_DOWNLOAD_RENEWAL_REQUEST_SENT == ret) {
printf("download renewal request has been sent successfully\r\n");
continue;
}
if (ret <= STATE_SUCCESS) {
printf("download failed, error code is %d, try to send renewal request\r\n", ret);
continue;
}
}
/* 下载所有固件内容完成, 销毁下载会话, 线程自行退出 */
aiot_download_deinit(&dl_handle);
printf("download thread exit\r\n");
return NULL;
}
/* 用户通过 aiot_ota_setopt() 注册的OTA消息处理回调, 如果SDK收到了OTA相关的MQTT消息, 会自动识别, 调用这个回调函数 */
void demo_ota_recv_handler(void *ota_handle, aiot_ota_recv_t *ota_msg, void *userdata)
{
switch (ota_msg->type) {
case AIOT_OTARECV_FOTA: {
uint32_t res = 0;
uint16_t port = 443;
uint32_t max_buffer_len = (8 * 1024);
aiot_sysdep_network_cred_t cred;
void *dl_handle = NULL;
void *last_percent = NULL;
if (NULL == ota_msg->task_desc || ota_msg->task_desc->protocol_type != AIOT_OTA_PROTOCOL_HTTPS) {
break;
}
dl_handle = aiot_download_init();
if (NULL == dl_handle) {
break;
}
printf("OTA target firmware version: %s, size: %u Bytes \r\n", ota_msg->task_desc->version,
ota_msg->task_desc->size_total);
if (NULL != ota_msg->task_desc->extra_data) {
printf("extra data: %s\r\n", ota_msg->task_desc->extra_data);
}
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA;
cred.max_tls_fragment = 16384;
cred.x509_server_cert = ali_ca_cert;
cred.x509_server_cert_len = strlen(ali_ca_cert);
/* 设置下载时为TLS下载 */
aiot_download_setopt(dl_handle, AIOT_DLOPT_NETWORK_CRED, (void *)(&cred));
/* 设置下载时访问的服务器端口号 */
aiot_download_setopt(dl_handle, AIOT_DLOPT_NETWORK_PORT, (void *)(&port));
/* 设置下载的任务信息, 通过输入参数 ota_msg 中的 task_desc 成员得到, 内含下载地址, 固件大小, 固件签名等 */
aiot_download_setopt(dl_handle, AIOT_DLOPT_TASK_DESC, (void *)(ota_msg->task_desc));
/* 设置下载内容到达时, SDK将调用的回调函数 */
aiot_download_setopt(dl_handle, AIOT_DLOPT_RECV_HANDLER, (void *)(demo_download_recv_handler));
/* 设置单次下载最大的buffer长度, 每当这个长度的内存读满了后会通知用户 */
aiot_download_setopt(dl_handle, AIOT_DLOPT_BODY_BUFFER_MAX_LEN, (void *)(&max_buffer_len));
/* 设置 AIOT_DLOPT_RECV_HANDLER 的不同次调用之间共享的数据, 比如例程把进度存在这里 */
last_percent = malloc(sizeof(uint32_t));
if (NULL == last_percent) {
aiot_download_deinit(&dl_handle);
break;
}
memset(last_percent, 0, sizeof(uint32_t));
aiot_download_setopt(dl_handle, AIOT_DLOPT_USERDATA, (void *)last_percent);
/* 启动专用的下载线程, 去完成固件内容的下载 */
res = pthread_create(&g_download_thread, NULL, demo_ota_download_thread, dl_handle);
if (res != 0) {
printf("pthread_create demo_ota_download_thread failed: %d\r\n", res);
aiot_download_deinit(&dl_handle);
free(last_percent);
} else {
/* 下载线程被设置为 detach 类型, 固件内容获取完毕后可自主退出 */
pthread_detach(g_download_thread);
}
break;
}
default:
break;
}
}
/* MQTT事件回调函数, 当网络连接/重连/断开时被触发, 事件定义见core/aiot_mqtt_api.h */
void demo_mqtt_event_handler(void *handle, const aiot_mqtt_event_t *event, void *userdata)
{
switch (event->type) {
/* SDK因为用户调用了aiot_mqtt_connect()接口, 与mqtt服务器建立连接已成功 */
case AIOT_MQTTEVT_CONNECT: {
printf("AIOT_MQTTEVT_CONNECT\r\n");
/* TODO: 处理SDK建连成功, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
/* SDK因为网络状况被动断连后, 自动发起重连已成功 */
case AIOT_MQTTEVT_RECONNECT: {
printf("AIOT_MQTTEVT_RECONNECT\r\n");
/* TODO: 处理SDK重连成功, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
/* SDK因为网络的状况而被动断开了连接, network是底层读写失败, heartbeat是没有按预期得到服务端心跳应答 */
case AIOT_MQTTEVT_DISCONNECT: {
char *cause = (event->data.disconnect == AIOT_MQTTDISCONNEVT_NETWORK_DISCONNECT) ? ("network disconnect") :
("heartbeat disconnect");
printf("AIOT_MQTTEVT_DISCONNECT: %s\r\n", cause);
/* TODO: 处理SDK被动断连, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
default: {
}
}
}
/* MQTT默认消息处理回调, 当SDK从服务器收到MQTT消息时, 且无对应用户回调处理时被调用 */
void demo_mqtt_default_recv_handler(void *handle, const aiot_mqtt_recv_t *packet, void *userdata)
{
switch (packet->type) {
case AIOT_MQTTRECV_HEARTBEAT_RESPONSE: {
printf("heartbeat response\r\n");
/* TODO: 处理服务器对心跳的回应, 一般不处理 */
}
break;
case AIOT_MQTTRECV_SUB_ACK: {
printf("suback, res: -0x%04X, packet id: %d, max qos: %d\r\n",
-packet->data.sub_ack.res, packet->data.sub_ack.packet_id, packet->data.sub_ack.max_qos);
/* TODO: 处理服务器对订阅请求的回应, 一般不处理 */
}
break;
case AIOT_MQTTRECV_PUB: {
printf("pub, qos: %d, topic: %.*s\r\n", packet->data.pub.qos, packet->data.pub.topic_len, packet->data.pub.topic);
printf("pub, payload: %.*s\r\n", packet->data.pub.payload_len, packet->data.pub.payload);
/* TODO: 处理服务器下发的业务报文 */
}
break;
case AIOT_MQTTRECV_PUB_ACK: {
printf("puback, packet id: %d\r\n", packet->data.pub_ack.packet_id);
/* TODO: 处理服务器对QoS1上报消息的回应, 一般不处理 */
}
break;
default: {
}
}
}
/* 执行aiot_mqtt_process的线程, 包含心跳发送和QoS1消息重发 */
void *demo_mqtt_process_thread(void *args)
{
while (1) {
aiot_mqtt_process(args);
sleep(1);
}
return NULL;
}
/* 执行aiot_mqtt_recv的线程, 包含网络自动重连和从服务器收取MQTT消息 */
void *demo_mqtt_recv_thread(void *args)
{
int32_t res = STATE_SUCCESS;
while (1) {
res = aiot_mqtt_recv(args);
if (res < STATE_SUCCESS) {
sleep(1);
}
}
return NULL;
}
int main(int argc, char *argv[])
{
int32_t res = STATE_SUCCESS;
void *mqtt_handle = NULL;
uint16_t port = 443; /* 无论设备是否使用TLS连接阿里云平台, 目的端口都是443 */
aiot_sysdep_network_cred_t cred; /* 安全凭据结构体, 如果要用TLS, 这个结构体中配置CA证书等参数 */
void *ota_handle = NULL;
char *cur_version = NULL;
/* 配置SDK的底层依赖 */
aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
/* 配置SDK的日志输出 */
aiot_state_set_logcb(demo_state_logcb);
/* 创建SDK的安全凭据, 用于建立TLS连接 */
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA; /* 使用RSA证书校验MQTT服务端 */
cred.max_tls_fragment = 16384; /* 最大的分片长度为16K, 其它可选值还有4K, 2K, 1K, 0.5K */
cred.sni_enabled = 1; /* TLS建连时, 支持Server Name Indicator */
cred.x509_server_cert = ali_ca_cert; /* 用来验证MQTT服务端的RSA根证书 */
cred.x509_server_cert_len = strlen(ali_ca_cert); /* 用来验证MQTT服务端的RSA根证书长度 */
/* 创建1个MQTT客户端实例并内部初始化默认参数 */
mqtt_handle = aiot_mqtt_init();
if (NULL == mqtt_handle) {
printf("aiot_mqtt_init failed\r\n");
return -1;
}
/* 配置MQTT服务器地址 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_HOST, (void *)mqtt_host);
/* 配置MQTT服务器端口 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PORT, (void *)&port);
/* 配置设备productKey */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PRODUCT_KEY, (void *)product_key);
/* 配置设备deviceName */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_NAME, (void *)device_name);
/* 配置设备deviceSecret */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_SECRET, (void *)device_secret);
/* 配置网络连接的安全凭据, 上面已经创建好了 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_NETWORK_CRED, (void *)&cred);
/* 配置MQTT默认消息接收回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_RECV_HANDLER, (void *)demo_mqtt_default_recv_handler);
/* 配置MQTT事件回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_EVENT_HANDLER, (void *)demo_mqtt_event_handler);
/* 与MQTT例程不同的是, 这里需要增加创建OTA会话实例的语句 */
ota_handle = aiot_ota_init();
if (NULL == ota_handle) {
printf("aiot_ota_init failed\r\n");
aiot_mqtt_deinit(&mqtt_handle);
return -2;
}
/* 用以下语句, 把OTA会话和MQTT会话关联起来 */
aiot_ota_setopt(ota_handle, AIOT_OTAOPT_MQTT_HANDLE, mqtt_handle);
/* 用以下语句, 设置OTA会话的数据接收回调, SDK收到OTA相关推送时, 会进入这个回调函数 */
aiot_ota_setopt(ota_handle, AIOT_OTAOPT_RECV_HANDLER, demo_ota_recv_handler);
/* 与服务器建立MQTT连接 */
res = aiot_mqtt_connect(mqtt_handle);
if (res < STATE_SUCCESS) {
/* 尝试建立连接失败, 销毁MQTT实例, 回收资源 */
aiot_mqtt_deinit(&mqtt_handle);
aiot_ota_deinit(&ota_handle);
printf("aiot_mqtt_connect failed: -0x%04X\r\n\r\n", -res);
printf("please check variables like mqtt_host, produt_key, device_name, device_secret in demo\r\n");
return -3;
}
/* TODO: 非常重要!!!
*
* cur_version 要根据用户实际情况, 改成从设备的配置区获取, 要反映真实的版本号, 而不能像示例这样写为固定值
*
* 1. 如果设备从未上报过版本号, 在控制台网页将无法部署升级任务
* 2. 如果设备升级完成后, 上报的不是新的版本号, 在控制台网页将会显示升级失败
*
*/
/* 演示MQTT连接建立起来之后, 就可以上报当前设备的版本号了 */
cur_version = "1.0.0";
res = aiot_ota_report_version(ota_handle, cur_version);
if (res < STATE_SUCCESS) {
printf("aiot_ota_report_version failed: -0x%04X\r\n", -res);
}
/* 创建一个单独的线程, 专用于执行aiot_mqtt_process, 它会自动发送心跳保活, 以及重发QoS1的未应答报文 */
res = pthread_create(&g_mqtt_process_thread, NULL, demo_mqtt_process_thread, mqtt_handle);
if (res != 0) {
printf("pthread_create demo_mqtt_process_thread failed: %d\r\n", res);
return -1;
}
/* 创建一个单独的线程用于执行aiot_mqtt_recv, 它会循环收取服务器下发的MQTT消息, 并在断线时自动重连 */
res = pthread_create(&g_mqtt_recv_thread, NULL, demo_mqtt_recv_thread, mqtt_handle);
if (res != 0) {
printf("pthread_create demo_mqtt_recv_thread failed: %d\r\n", res);
return -1;
}
/* 主循环进入休眠 */
while (1) {
sleep(1);
}
/* 断开MQTT连接, 一般不会运行到这里 */
res = aiot_mqtt_disconnect(mqtt_handle);
if (res < STATE_SUCCESS) {
aiot_mqtt_deinit(&mqtt_handle);
aiot_ota_deinit(&ota_handle);
printf("aiot_mqtt_disconnect failed: -0x%04X\r\n", -res);
return -1;
}
/* 销毁MQTT实例, 一般不会运行到这里 */
res = aiot_mqtt_deinit(&mqtt_handle);
if (res < STATE_SUCCESS) {
printf("aiot_mqtt_deinit failed: -0x%04X\r\n", -res);
aiot_ota_deinit(&ota_handle);
return -1;
}
/* 销毁OTA实例, 一般不会运行到这里 */
aiot_ota_deinit(&ota_handle);
return 0;
}

View File

@@ -0,0 +1,392 @@
/*
* 这个例程适用于`Linux`这类支持pthread的POSIX设备, 它演示了用SDK配置MQTT参数并建立连接, 之后创建2个线程
*
* + 一个线程用于保活长连接
* + 一个线程用于接收消息, 并在有消息到达时进入默认的数据回调, 在连接状态变化时进入事件回调
*
* 接着在MQTT连接上发送设备端日志到云端, 注意日志上云通道默认是关闭的, 用户需要到云端控制台打开设备端日志服务开关。
*
* 需要用户关注或修改的部分, 已经用 TODO 在注释中标明
*
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include "aiot_state_api.h"
#include "aiot_sysdep_api.h"
#include "aiot_mqtt_api.h"
#include "aiot_logpost_api.h"
/* TODO: 替换为自己设备的三元组 */
char *product_key = "${YourProductKey}";
char *device_name = "${YourDeviceName}";
char *device_secret = "${YourDeviceSecret}";
/*
TODO: 替换为自己实例的接入点
对于企业实例, 或者2021年07月30日之后含当日开通的物联网平台服务下公共实例
mqtt_host的格式为"${YourInstanceId}.mqtt.iothub.aliyuncs.com"
其中${YourInstanceId}: 请替换为您企业/公共实例的Id
对于2021年07月30日之前不含当日开通的物联网平台服务下公共实例
需要将mqtt_host修改为: mqtt_host = "${YourProductKey}.iot-as-mqtt.${YourRegionId}.aliyuncs.com"
其中, ${YourProductKey}请替换为设备所属产品的ProductKey。可登录物联网平台控制台在对应实例的设备详情页获取。
${YourRegionId}:请替换为您的物联网平台设备所在地域代码, 比如cn-shanghai等
该情况下完整mqtt_host举例: a1TTmBPIChA.iot-as-mqtt.cn-shanghai.aliyuncs.com
详情请见: https://help.aliyun.com/document_detail/147356.html
*/
char *mqtt_host = "${YourInstanceId}.mqtt.iothub.aliyuncs.com";
/* 位于portfiles/aiot_port文件夹下的系统适配函数集合 */
extern aiot_sysdep_portfile_t g_aiot_sysdep_portfile;
/* 位于external/ali_ca_cert.c中的服务器证书 */
extern const char *ali_ca_cert;
static pthread_t g_mqtt_process_thread;
static pthread_t g_mqtt_recv_thread;
static uint8_t g_mqtt_process_thread_running = 0;
static uint8_t g_mqtt_recv_thread_running = 0;
/* TODO: 如果要关闭日志, 就把这个函数实现为空, 如果要减少日志, 可根据code选择不打印
*
* 例如: [1581501698.455][LK-0309] pub: /sys/a13FN5TplKq/logpost_basic_demo/thing/deviceinfo/update
*
* 上面这条日志的code就是0309(十六进制), code值的定义见core/aiot_state_api.h
*
*/
/* 日志回调函数, SDK的日志会从这里输出, 禁止在此函数中调用SDK API */
static int32_t demo_state_logcb(int32_t code, char *message)
{
printf("%s", message);
return 0;
}
/* MQTT事件回调函数, 当网络连接/重连/断开时被触发, 事件定义见core/aiot_mqtt_api.h */
void demo_mqtt_event_handler(void *handle, const aiot_mqtt_event_t *event, void *userdata)
{
switch (event->type) {
/* SDK因为用户调用了aiot_mqtt_connect()接口, 与mqtt服务器建立连接已成功 */
case AIOT_MQTTEVT_CONNECT: {
printf("AIOT_MQTTEVT_CONNECT\r\n");
}
break;
/* SDK因为网络状况被动断连后, 自动发起重连已成功 */
case AIOT_MQTTEVT_RECONNECT: {
printf("AIOT_MQTTEVT_RECONNECT\r\n");
}
break;
/* SDK因为网络的状况而被动断开了连接, network是底层读写失败, heartbeat是没有按预期得到服务端心跳应答 */
case AIOT_MQTTEVT_DISCONNECT: {
char *cause = (event->data.disconnect == AIOT_MQTTDISCONNEVT_NETWORK_DISCONNECT) ? ("network disconnect") :
("heartbeat disconnect");
printf("AIOT_MQTTEVT_DISCONNECT: %s\r\n", cause);
}
break;
default: {
}
}
}
/* MQTT默认消息处理回调, 当SDK从服务器收到MQTT消息时, 且无对应用户回调处理时被调用 */
void demo_mqtt_default_recv_handler(void *handle, const aiot_mqtt_recv_t *packet, void *userdata)
{
switch (packet->type) {
case AIOT_MQTTRECV_HEARTBEAT_RESPONSE: {
printf("heartbeat response\r\n");
}
break;
case AIOT_MQTTRECV_SUB_ACK: {
printf("suback, res: -0x%04X, packet id: %d, max qos: %d\r\n",
-packet->data.sub_ack.res, packet->data.sub_ack.packet_id, packet->data.sub_ack.max_qos);
}
break;
case AIOT_MQTTRECV_PUB: {
printf("pub, qos: %d, topic: %.*s\r\n", packet->data.pub.qos, packet->data.pub.topic_len, packet->data.pub.topic);
printf("pub, payload: %.*s\r\n", packet->data.pub.payload_len, packet->data.pub.payload);
}
break;
case AIOT_MQTTRECV_PUB_ACK: {
printf("puback, packet id: %d\r\n", packet->data.pub_ack.packet_id);
}
break;
default: {
}
}
}
/* 执行aiot_mqtt_process的线程, 包含心跳发送和QoS1消息重发 */
void *demo_mqtt_process_thread(void *args)
{
int32_t res = STATE_SUCCESS;
while (g_mqtt_process_thread_running) {
res = aiot_mqtt_process(args);
if (res == STATE_USER_INPUT_EXEC_DISABLED) {
break;
}
sleep(1);
}
return NULL;
}
/* 执行aiot_mqtt_recv的线程, 包含网络自动重连和从服务器收取MQTT消息 */
void *demo_mqtt_recv_thread(void *args)
{
int32_t res = STATE_SUCCESS;
while (g_mqtt_recv_thread_running) {
res = aiot_mqtt_recv(args);
if (res < STATE_SUCCESS) {
if (res == STATE_USER_INPUT_EXEC_DISABLED) {
break;
}
sleep(1);
}
}
return NULL;
}
int32_t demo_mqtt_start(void **handle, char *product_key, char *device_name, char *device_secret)
{
int32_t res = STATE_SUCCESS;
void *mqtt_handle = NULL;
uint16_t port = 443; /* 无论设备是否使用TLS连接阿里云平台, 目的端口都是443 */
aiot_sysdep_network_cred_t cred; /* 安全凭据结构体, 如果要用TLS, 这个结构体中配置CA证书等参数 */
/* 配置SDK的底层依赖 */
aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
/* 配置SDK的日志输出 */
aiot_state_set_logcb(demo_state_logcb);
/* 创建SDK的安全凭据, 用于建立TLS连接 */
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA; /* 使用RSA证书校验MQTT服务端 */
cred.max_tls_fragment = 16384; /* 最大的分片长度为16K, 其它可选值还有4K, 2K, 1K, 0.5K */
cred.sni_enabled = 1; /* TLS建连时, 支持Server Name Indicator */
cred.x509_server_cert = ali_ca_cert; /* 用来验证MQTT服务端的RSA根证书 */
cred.x509_server_cert_len = strlen(ali_ca_cert); /* 用来验证MQTT服务端的RSA根证书长度 */
/* 创建1个MQTT客户端实例并内部初始化默认参数 */
mqtt_handle = aiot_mqtt_init();
if (mqtt_handle == NULL) {
printf("aiot_mqtt_init failed\r\n");
return -1;
}
/* 如果以下代码不被注释, 则例程会用TCP而不是TLS连接云平台 */
/*
{
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_NONE;
}
*/
/* 配置MQTT服务器地址 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_HOST, (void *)mqtt_host);
/* 配置MQTT服务器端口 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PORT, (void *)&port);
/* 配置设备productKey */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PRODUCT_KEY, (void *)product_key);
/* 配置设备deviceName */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_NAME, (void *)device_name);
/* 配置设备deviceSecret */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_SECRET, (void *)device_secret);
/* 配置网络连接的安全凭据, 上面已经创建好了 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_NETWORK_CRED, (void *)&cred);
/* 配置MQTT默认消息接收回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_RECV_HANDLER, (void *)demo_mqtt_default_recv_handler);
/* 配置MQTT事件回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_EVENT_HANDLER, (void *)demo_mqtt_event_handler);
/* 与服务器建立MQTT连接 */
res = aiot_mqtt_connect(mqtt_handle);
if (res < STATE_SUCCESS) {
/* 尝试建立连接失败, 销毁MQTT实例, 回收资源 */
aiot_mqtt_deinit(&mqtt_handle);
printf("aiot_mqtt_connect failed: -0x%04X\r\n\r\n", -res);
printf("please check variables like mqtt_host, produt_key, device_name, device_secret in demo\r\n");
return -1;
}
/* 创建一个单独的线程, 专用于执行aiot_mqtt_process, 它会自动发送心跳保活, 以及重发QoS1的未应答报文 */
g_mqtt_process_thread_running = 1;
res = pthread_create(&g_mqtt_process_thread, NULL, demo_mqtt_process_thread, mqtt_handle);
if (res < 0) {
printf("pthread_create demo_mqtt_process_thread failed: %d\r\n", res);
aiot_mqtt_deinit(&mqtt_handle);
return -1;
}
/* 创建一个单独的线程用于执行aiot_mqtt_recv, 它会循环收取服务器下发的MQTT消息, 并在断线时自动重连 */
g_mqtt_recv_thread_running = 1;
res = pthread_create(&g_mqtt_recv_thread, NULL, demo_mqtt_recv_thread, mqtt_handle);
if (res < 0) {
printf("pthread_create demo_mqtt_recv_thread failed: %d\r\n", res);
g_mqtt_process_thread_running = 0;
pthread_join(g_mqtt_process_thread, NULL);
aiot_mqtt_deinit(&mqtt_handle);
return -1;
}
*handle = mqtt_handle;
return 0;
}
int32_t demo_mqtt_stop(void **handle)
{
int32_t res = STATE_SUCCESS;
void *mqtt_handle = NULL;
mqtt_handle = *handle;
g_mqtt_process_thread_running = 0;
g_mqtt_recv_thread_running = 0;
pthread_join(g_mqtt_process_thread, NULL);
pthread_join(g_mqtt_recv_thread, NULL);
/* 断开MQTT连接 */
res = aiot_mqtt_disconnect(mqtt_handle);
if (res < STATE_SUCCESS) {
aiot_mqtt_deinit(&mqtt_handle);
printf("aiot_mqtt_disconnect failed: -0x%04X\r\n", -res);
return -1;
}
/* 销毁MQTT实例 */
res = aiot_mqtt_deinit(&mqtt_handle);
if (res < STATE_SUCCESS) {
printf("aiot_mqtt_deinit failed: -0x%04X\r\n", -res);
return -1;
}
return 0;
}
/* 事件处理回调, 用户可通过此回调获取日志上报通道的开关状态 */
void demo_logpost_event_handler(void *handle, const aiot_logpost_event_t *event, void *userdata)
{
switch (event->type) {
/* 日志配置事件, 当设备连云成功或者用户在控制台页面控制日志开关时会收到此事件 */
case AIOT_LOGPOSTEVT_CONFIG_DATA: {
printf("user log switch state is: %d\r\n", event->data.config_data.on_off);
printf("toggle it using the switch in device detail page in https://iot.console.aliyun.com\r\n");
}
default:
break;
}
}
/* 上报日志到云端 */
void demo_send_log(void *handle, char *log)
{
int32_t res = 0;
aiot_logpost_msg_t msg;
memset(&msg, 0, sizeof(aiot_logpost_msg_t));
msg.timestamp = 0; /* 单位为ms的时间戳, 填写0则SDK将使用当前的时间戳 */
msg.loglevel = AIOT_LOGPOST_LEVEL_DEBUG; /* 日志级别 */
msg.module_name = "APP"; /* 日志对应的模块 */
msg.code = 200; /* 状态码 */
msg.msg_id = 0; /* 云端下行报文的消息标示符, 若无对应消息可直接填0 */
msg.content = log; /* 日志内容 */
res = aiot_logpost_send(handle, &msg);
if (res < 0) {
printf("aiot_logpost_send failed: -0x%04X\r\n", -res);
}
}
int main(int argc, char *argv[])
{
int32_t res = STATE_SUCCESS;
void *mqtt_handle = NULL, *logpost_handle = NULL;
uint8_t sys_log_switch = 1;
/* 建立MQTT连接, 并开启保活线程和接收线程 */
res = demo_mqtt_start(&mqtt_handle, product_key, device_name, device_secret);
if (res < 0) {
printf("demo_mqtt_start failed\r\n");
return -1;
}
/* 创建1个logpost客户端实例并内部初始化默认参数 */
logpost_handle = aiot_logpost_init();
if (logpost_handle == NULL) {
demo_mqtt_stop(&mqtt_handle);
printf("aiot_logpost_init failed\r\n");
return -1;
}
/* 配置logpost的系统日志开关, 打开后将上报网络延时信息 */
res = aiot_logpost_setopt(logpost_handle, AIOT_LOGPOSTOPT_SYS_LOG, (void *)&sys_log_switch);
if (res < STATE_SUCCESS) {
printf("aiot_logpost_setopt AIOT_LOGPOSTOPT_SYS_LOG failed, res: -0x%04X\r\n", -res);
aiot_logpost_deinit(&logpost_handle);
demo_mqtt_stop(&mqtt_handle);
return -1;
}
/* 配置logpost会话, 把它和MQTT会话的句柄关联起来 */
res = aiot_logpost_setopt(logpost_handle, AIOT_LOGPOSTOPT_MQTT_HANDLE, mqtt_handle);
if (res < STATE_SUCCESS) {
printf("aiot_logpost_setopt AIOT_LOGPOSTOPT_MQTT_HANDLE failed, res: -0x%04X\r\n", -res);
aiot_logpost_deinit(&logpost_handle);
demo_mqtt_stop(&mqtt_handle);
return -1;
}
res = aiot_logpost_setopt(logpost_handle, AIOT_LOGPOSTOPT_EVENT_HANDLER, (void *)demo_logpost_event_handler);
if (res < STATE_SUCCESS) {
printf("aiot_logpost_setopt AIOT_LOGPOSTOPT_EVENT_HANDLER failed, res: -0x%04X\r\n", -res);
aiot_logpost_deinit(&logpost_handle);
demo_mqtt_stop(&mqtt_handle);
return -1;
}
/* 主线程进入休眠, 等云平台的logpost回应到达时, 接收线程会调用 demo_logpost_recv_handler() */
while (1) {
sleep(10);
/* TODO: 用户可取消注释上报日志到云端, 注意: 日志模块完成初始化后上报通道默认为关闭状态, 日志模块会在收到设备建连内部事件后立即同步云端控制台的开关状态。
demo_send_log(logpost_handle, "log in while(1)");
*/
}
/* 销毁logpost实例, 一般不会运行到这里 */
res = aiot_logpost_deinit(&logpost_handle);
if (res < STATE_SUCCESS) {
demo_mqtt_stop(&mqtt_handle);
printf("aiot_logpost_deinit failed: -0x%04X\r\n", -res);
return -1;
}
/* 销毁MQTT实例, 退出线程, 一般不会运行到这里 */
res = demo_mqtt_stop(&mqtt_handle);
if (res < 0) {
printf("demo_start_stop failed\r\n");
return -1;
}
return 0;
}

View File

@@ -0,0 +1,325 @@
/*
* 这个例程适用于不支持通过pthread函数在主线程外开独立的固件下载线程的设备
* 它演示了用SDK配置MQTT参数并建立连接, 并在接收到OTA的mqtt消息后开始下载升级固件的过程
* 同时, 它演示了如何将固件的大小分为两半, 每次下载一半的做法. 用户可以进一步将固件分成更多更小的分段
*
* 需要用户关注或修改的部分, 已用 `TODO` 在注释中标明
*
*/
/* TODO: 本例子用到了sleep函数, 所以包含了unistd.h. 如果用户自己的库中有可以替代的函数, 则可以将unistd.h替换掉
*
* 本例子用到了malloc/free函数, 所以用到了stdlib.h, 用户如果自己库中有可以替代的函数, 则需要将stdlib.h替换掉
*
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include "aiot_state_api.h"
#include "aiot_sysdep_api.h"
#include "aiot_mqtt_api.h"
#include "aiot_ota_api.h"
#include "aiot_mqtt_download_api.h"
/* TODO: 替换为自己设备的三元组 */
char *product_key = "${YourProductKey}";
char *device_name = "${YourDeviceName}";
char *device_secret = "${YourDeviceSecret}";
/*
TODO: 替换为自己实例的接入点
对于企业实例, 或者2021年07月30日之后含当日开通的物联网平台服务下公共实例
mqtt_host的格式为"${YourInstanceId}.mqtt.iothub.aliyuncs.com"
其中${YourInstanceId}: 请替换为您企业/公共实例的Id
对于2021年07月30日之前不含当日开通的物联网平台服务下公共实例
需要将mqtt_host修改为: mqtt_host = "${YourProductKey}.iot-as-mqtt.${YourRegionId}.aliyuncs.com"
其中, ${YourProductKey}请替换为设备所属产品的ProductKey。可登录物联网平台控制台在对应实例的设备详情页获取。
${YourRegionId}:请替换为您的物联网平台设备所在地域代码, 比如cn-shanghai等
该情况下完整mqtt_host举例: a1TTmBPIChA.iot-as-mqtt.cn-shanghai.aliyuncs.com
详情请见: https://help.aliyun.com/document_detail/147356.html
*/
char *mqtt_host = "${YourInstanceId}.mqtt.iothub.aliyuncs.com";
/* 位于external/ali_ca_cert.c中的服务器证书 */
extern const char *ali_ca_cert;
void *g_ota_handle = NULL;
void *g_dl_handle = NULL;
uint32_t g_firmware_size = 0;
/* 位于portfiles/aiot_port文件夹下的系统适配函数集合 */
extern aiot_sysdep_portfile_t g_aiot_sysdep_portfile;
/* TODO: 如果要关闭日志, 就把这个函数实现为空, 如果要减少日志, 可根据code选择不打印
*
* 例如: [1578463098.611][LK-0309] pub: /ota/device/upgrade/a13FN5TplKq/ota_demo
*
* 上面这条日志的code就是0309(十六进制), code值的定义见core/aiot_state_api.h
*
*/
/* 日志回调函数, SDK的日志会从这里输出 */
int32_t demo_state_logcb(int32_t code, char *message)
{
printf("%s", message);
return 0;
}
/* MQTT事件回调函数, 当网络连接/重连/断开时被触发, 事件定义见core/aiot_mqtt_api.h */
void demo_mqtt_event_handler(void *handle, const aiot_mqtt_event_t *event, void *userdata)
{
switch (event->type) {
/* SDK因为用户调用了aiot_mqtt_connect()接口, 与mqtt服务器建立连接已成功 */
case AIOT_MQTTEVT_CONNECT: {
printf("AIOT_MQTTEVT_CONNECT\r\n");
/* TODO: 处理SDK建连成功, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
/* SDK因为网络状况被动断连后, 自动发起重连已成功 */
case AIOT_MQTTEVT_RECONNECT: {
printf("AIOT_MQTTEVT_RECONNECT\r\n");
/* TODO: 处理SDK重连成功, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
/* SDK因为网络的状况而被动断开了连接, network是底层读写失败, heartbeat是没有按预期得到服务端心跳应答 */
case AIOT_MQTTEVT_DISCONNECT: {
char *cause = (event->data.disconnect == AIOT_MQTTDISCONNEVT_NETWORK_DISCONNECT) ? ("network disconnect") :
("heartbeat disconnect");
printf("AIOT_MQTTEVT_DISCONNECT: %s\r\n", cause);
/* TODO: 处理SDK被动断连, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
default: {
}
}
}
/* MQTT默认消息处理回调, 当SDK从服务器收到MQTT消息时, 且无对应用户回调处理时被调用 */
void demo_mqtt_default_recv_handler(void *handle, const aiot_mqtt_recv_t *const packet, void *userdata)
{
switch (packet->type) {
case AIOT_MQTTRECV_HEARTBEAT_RESPONSE: {
printf("heartbeat response\r\n");
/* TODO: 处理服务器对心跳的回应, 一般不处理 */
}
break;
case AIOT_MQTTRECV_SUB_ACK: {
printf("suback, res: -0x%04X, packet id: %d, max qos: %d\r\n",
-packet->data.sub_ack.res, packet->data.sub_ack.packet_id, packet->data.sub_ack.max_qos);
/* TODO: 处理服务器对订阅请求的回应, 一般不处理 */
}
break;
case AIOT_MQTTRECV_PUB: {
printf("pub, qos: %d, topic: %.*s\r\n", packet->data.pub.qos, packet->data.pub.topic_len, packet->data.pub.topic);
printf("pub, payload: %.*s\r\n", packet->data.pub.payload_len, packet->data.pub.payload);
/* TODO: 处理服务器下发的业务报文 */
}
break;
case AIOT_MQTTRECV_PUB_ACK: {
printf("puback, packet id: %d\r\n", packet->data.pub_ack.packet_id);
/* TODO: 处理服务器对QoS1上报消息的回应, 一般不处理 */
}
break;
default: {
}
}
}
/* 下载收包回调, 用户调用 aiot_download_recv() 后, SDK收到数据会进入这个函数, 把下载到的数据交给用户 */
/* TODO: 一般来说, 设备升级时, 会在这个回调中, 把下载到的数据写到Flash上 */
void user_download_recv_handler(void *handle, const aiot_mqtt_download_recv_t *packet, void *userdata)
{
uint32_t data_buffer_len = 0;
/* 目前只支持 packet->type 为 AIOT_DLRECV_HTTPBODY 的情况 */
if (!packet || AIOT_MDRECV_DATA_RESP != packet->type) {
return;
}
/* 用户应在此实现文件本地固化的操作 */
FILE *file = fopen("mota_demo.bin", "ab");
fwrite(packet->data.data_resp.data, packet->data.data_resp.data_size, sizeof(int8_t), file);
fclose(file);
data_buffer_len = packet->data.data_resp.data_size;
printf("download %03d%% done, +%d bytes\r\n", packet->data.data_resp.percent, data_buffer_len);
}
/* 用户通过 aiot_ota_setopt() 注册的OTA消息处理回调, 如果SDK收到了OTA相关的MQTT消息, 会自动识别, 调用这个回调函数 */
void user_ota_recv_handler(void *ota_handle, aiot_ota_recv_t *ota_msg, void *userdata)
{
uint32_t request_size = 10 * 1024;
switch (ota_msg->type) {
case AIOT_OTARECV_FOTA: {
if (NULL == ota_msg->task_desc || ota_msg->task_desc->protocol_type != AIOT_OTA_PROTOCOL_MQTT) {
break;
}
if(g_dl_handle != NULL) {
aiot_mqtt_download_deinit(&g_dl_handle);
}
printf("OTA target firmware version: %s, size: %u Bytes\r\n", ota_msg->task_desc->version,
ota_msg->task_desc->size_total);
void *md_handler = aiot_mqtt_download_init();
aiot_mqtt_download_setopt(md_handler, AIOT_MDOPT_TASK_DESC, ota_msg->task_desc);
/* 设置下载一包的大小,对于资源受限设备可以调整该值大小 */
aiot_mqtt_download_setopt(md_handler, AIOT_MDOPT_DATA_REQUEST_SIZE, &request_size);
/* 部分场景下用户如果只需要下载文件的一部分即下载指定range的文件可以设置文件起始位置、终止位置。
* 若设置range区间下载单包报文的数据有CRC校验但SDK将不进行完整文件MD5校验
* 默认下载全部文件单包报文的数据有CRC校验并且SDK会对整个文件进行md5校验 */
// uint32_t range_start = 10, range_end = 50 * 1024 + 10;
// aiot_mqtt_download_setopt(md_handler, AIOT_MDOPT_RANGE_START, &range_start);
// aiot_mqtt_download_setopt(md_handler, AIOT_MDOPT_RANGE_END, &range_end);
aiot_mqtt_download_setopt(md_handler, AIOT_MDOPT_RECV_HANDLE, user_download_recv_handler);
g_dl_handle = md_handler;
}
break;
default:
break;
}
}
int main(int argc, char *argv[])
{
int32_t res = STATE_SUCCESS;
void *mqtt_handle = NULL;
uint16_t port = 1883; /* 无论设备是否使用TLS连接阿里云平台, 目的端口都是443 */
aiot_sysdep_network_cred_t cred; /* 安全凭据结构体, 如果要用TLS, 这个结构体中配置CA证书等参数 */
char *cur_version = NULL;
void *ota_handle = NULL;
/* 配置SDK的底层依赖 */
aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
/* 配置SDK的日志输出 */
aiot_state_set_logcb(demo_state_logcb);
/* 创建SDK的安全凭据, 用于建立TLS连接 */
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA; /* 使用RSA证书校验MQTT服务端 */
cred.max_tls_fragment = 16384; /* 最大的分片长度为16K, 其它可选值还有4K, 2K, 1K, 0.5K */
cred.sni_enabled = 1; /* TLS建连时, 支持Server Name Indicator */
cred.x509_server_cert = ali_ca_cert; /* 用来验证MQTT服务端的RSA根证书 */
cred.x509_server_cert_len = strlen(ali_ca_cert); /* 用来验证MQTT服务端的RSA根证书长度 */
/* 创建1个MQTT客户端实例并内部初始化默认参数 */
mqtt_handle = aiot_mqtt_init();
if (mqtt_handle == NULL) {
return -1;
}
/* 配置MQTT服务器地址 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_HOST, (void *)mqtt_host);
/* 配置MQTT服务器端口 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PORT, (void *)&port);
/* 配置设备productKey */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PRODUCT_KEY, (void *)product_key);
/* 配置设备deviceName */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_NAME, (void *)device_name);
/* 配置设备deviceSecret */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_SECRET, (void *)device_secret);
/* 配置网络连接的安全凭据, 上面已经创建好了 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_NETWORK_CRED, (void *)&cred);
/* 配置MQTT默认消息接收回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_RECV_HANDLER, (void *)demo_mqtt_default_recv_handler);
/* 配置MQTT事件回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_EVENT_HANDLER, (void *)demo_mqtt_event_handler);
/* 与MQTT例程不同的是, 这里需要增加创建OTA会话实例的语句 */
ota_handle = aiot_ota_init();
if (NULL == ota_handle) {
goto exit;
}
/* 用以下语句, 把OTA会话和MQTT会话关联起来 */
aiot_ota_setopt(ota_handle, AIOT_OTAOPT_MQTT_HANDLE, mqtt_handle);
/* 用以下语句, 设置OTA会话的数据接收回调, SDK收到OTA相关推送时, 会进入这个回调函数 */
aiot_ota_setopt(ota_handle, AIOT_OTAOPT_RECV_HANDLER, user_ota_recv_handler);
g_ota_handle = ota_handle;
/* 与服务器建立MQTT连接 */
res = aiot_mqtt_connect(mqtt_handle);
if (res < STATE_SUCCESS) {
printf("aiot_mqtt_connect failed: -0x%04X\r\n\r\n", -res);
printf("please check variables like mqtt_host, produt_key, device_name, device_secret in demo\r\n");
/* 尝试建立连接失败, 销毁MQTT实例, 回收资源 */
goto exit;
}
/* TODO: 非常重要!!!
*
* cur_version 要根据用户实际情况, 改成从设备的配置区获取, 要反映真实的版本号, 而不能像示例这样写为固定值
*
* 1. 如果设备从未上报过版本号, 在控制台网页将无法部署升级任务
* 2. 如果设备升级完成后, 上报的不是新的版本号, 在控制台网页将会显示升级失败
*
*/
cur_version = "1.0.0";
/* 演示MQTT连接建立起来之后, 就可以上报当前设备的版本号了 */
res = aiot_ota_report_version(ota_handle, cur_version);
if (res < STATE_SUCCESS) {
printf("report version failed, code is -0x%04X\r\n", -res);
}
while (1) {
aiot_mqtt_process(mqtt_handle);
aiot_mqtt_recv(mqtt_handle);
if(g_dl_handle != NULL) {
int32_t res = aiot_mqtt_download_process(g_dl_handle);
if(STATE_MQTT_DOWNLOAD_SUCCESS == res) {
/* 升级成功,这里重启并且上报新的版本号 */
printf("mqtt download ota success \r\n");
aiot_mqtt_download_deinit(&g_dl_handle);
break;
} else if(STATE_MQTT_DOWNLOAD_FAILED_RECVERROR == res
|| STATE_MQTT_DOWNLOAD_FAILED_TIMEOUT == res
|| STATE_MQTT_DOWNLOAD_FAILED_MISMATCH == res) {
printf("mqtt download ota failed \r\n");
aiot_mqtt_download_deinit(&g_dl_handle);
break;
}
}
}
aiot_ota_deinit(&ota_handle);
/* 断开MQTT连接, 一般不会运行到这里 */
res = aiot_mqtt_disconnect(mqtt_handle);
if (res < STATE_SUCCESS) {
printf("aiot_mqtt_disconnect failed: -0x%04X\r\n", -res);
goto exit;
}
exit:
while (1) {
/* 销毁MQTT实例, 一般不会运行到这里 */
res = aiot_mqtt_deinit(&mqtt_handle);
if (res < STATE_SUCCESS) {
printf("aiot_mqtt_deinit failed: -0x%04X\r\n", -res);
return -1;
} else {
break;
}
}
/* 销毁OTA实例, 一般不会运行到这里 */
aiot_ota_deinit(&ota_handle);
return 0;
}

View File

@@ -0,0 +1,297 @@
/*
* 这个例程适用于`Linux`这类支持pthread的POSIX设备, 它演示了用SDK配置MQTT参数并建立连接, 之后创建2个线程
*
* + 一个线程用于保活长连接
* + 一个线程用于接收消息, 并在有消息到达时进入默认的数据回调, 在连接状态变化时进入事件回调
*
* 需要用户关注或修改的部分, 已经用 TODO 在注释中标明
*
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include "aiot_state_api.h"
#include "aiot_sysdep_api.h"
#include "aiot_mqtt_api.h"
/* TODO: 替换为自己设备的三元组 */
char *product_key = "${YourProductKey}";
char *device_name = "${YourDeviceName}";
char *device_secret = "${YourDeviceSecret}";
/*
TODO: 替换为自己实例的接入点
对于企业实例, 或者2021年07月30日之后含当日开通的物联网平台服务下公共实例
mqtt_host的格式为"${YourInstanceId}.mqtt.iothub.aliyuncs.com"
其中${YourInstanceId}: 请替换为您企业/公共实例的Id
对于2021年07月30日之前不含当日开通的物联网平台服务下公共实例
需要将mqtt_host修改为: mqtt_host = "${YourProductKey}.iot-as-mqtt.${YourRegionId}.aliyuncs.com"
其中, ${YourProductKey}请替换为设备所属产品的ProductKey。可登录物联网平台控制台在对应实例的设备详情页获取。
${YourRegionId}:请替换为您的物联网平台设备所在地域代码, 比如cn-shanghai等
该情况下完整mqtt_host举例: a1TTmBPIChA.iot-as-mqtt.cn-shanghai.aliyuncs.com
详情请见: https://help.aliyun.com/document_detail/147356.html
*/
char *mqtt_host = "${YourInstanceId}.mqtt.iothub.aliyuncs.com";
/* 位于portfiles/aiot_port文件夹下的系统适配函数集合 */
extern aiot_sysdep_portfile_t g_aiot_sysdep_portfile;
/* 位于external/ali_ca_cert.c中的服务器证书 */
extern const char *ali_ca_cert;
static pthread_t g_mqtt_process_thread;
static pthread_t g_mqtt_recv_thread;
static uint8_t g_mqtt_process_thread_running = 0;
static uint8_t g_mqtt_recv_thread_running = 0;
/* TODO: 如果要关闭日志, 就把这个函数实现为空, 如果要减少日志, 可根据code选择不打印
*
* 例如: [1577589489.033][LK-0317] mqtt_basic_demo&gb80sFmX7yX
*
* 上面这条日志的code就是0317(十六进制), code值的定义见core/aiot_state_api.h
*
*/
/* 日志回调函数, SDK的日志会从这里输出 */
int32_t demo_state_logcb(int32_t code, char *message)
{
printf("%s", message);
return 0;
}
/* MQTT事件回调函数, 当网络连接/重连/断开时被触发, 事件定义见core/aiot_mqtt_api.h */
void demo_mqtt_event_handler(void *handle, const aiot_mqtt_event_t *event, void *userdata)
{
switch (event->type) {
/* SDK因为用户调用了aiot_mqtt_connect()接口, 与mqtt服务器建立连接已成功 */
case AIOT_MQTTEVT_CONNECT: {
printf("AIOT_MQTTEVT_CONNECT\n");
/* TODO: 处理SDK建连成功, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
/* SDK因为网络状况被动断连后, 自动发起重连已成功 */
case AIOT_MQTTEVT_RECONNECT: {
printf("AIOT_MQTTEVT_RECONNECT\n");
/* TODO: 处理SDK重连成功, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
/* SDK因为网络的状况而被动断开了连接, network是底层读写失败, heartbeat是没有按预期得到服务端心跳应答 */
case AIOT_MQTTEVT_DISCONNECT: {
char *cause = (event->data.disconnect == AIOT_MQTTDISCONNEVT_NETWORK_DISCONNECT) ? ("network disconnect") :
("heartbeat disconnect");
printf("AIOT_MQTTEVT_DISCONNECT: %s\n", cause);
/* TODO: 处理SDK被动断连, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
default: {
}
}
}
/* MQTT默认消息处理回调, 当SDK从服务器收到MQTT消息时, 且无对应用户回调处理时被调用 */
void demo_mqtt_default_recv_handler(void *handle, const aiot_mqtt_recv_t *packet, void *userdata)
{
switch (packet->type) {
case AIOT_MQTTRECV_HEARTBEAT_RESPONSE: {
printf("heartbeat response\n");
/* TODO: 处理服务器对心跳的回应, 一般不处理 */
}
break;
case AIOT_MQTTRECV_SUB_ACK: {
printf("suback, res: -0x%04X, packet id: %d, max qos: %d\n",
-packet->data.sub_ack.res, packet->data.sub_ack.packet_id, packet->data.sub_ack.max_qos);
/* TODO: 处理服务器对订阅请求的回应, 一般不处理 */
}
break;
case AIOT_MQTTRECV_PUB: {
printf("pub, qos: %d, topic: %.*s\n", packet->data.pub.qos, packet->data.pub.topic_len, packet->data.pub.topic);
printf("pub, payload: %.*s\n", packet->data.pub.payload_len, packet->data.pub.payload);
/* TODO: 处理服务器下发的业务报文 */
}
break;
case AIOT_MQTTRECV_PUB_ACK: {
printf("puback, packet id: %d\n", packet->data.pub_ack.packet_id);
/* TODO: 处理服务器对QoS1上报消息的回应, 一般不处理 */
}
break;
default: {
}
}
}
/* 执行aiot_mqtt_process的线程, 包含心跳发送和QoS1消息重发 */
void *demo_mqtt_process_thread(void *args)
{
int32_t res = STATE_SUCCESS;
while (g_mqtt_process_thread_running) {
res = aiot_mqtt_process(args);
if (res == STATE_USER_INPUT_EXEC_DISABLED) {
break;
}
sleep(1);
}
return NULL;
}
/* 执行aiot_mqtt_recv的线程, 包含网络自动重连和从服务器收取MQTT消息 */
void *demo_mqtt_recv_thread(void *args)
{
int32_t res = STATE_SUCCESS;
while (g_mqtt_recv_thread_running) {
res = aiot_mqtt_recv(args);
if (res < STATE_SUCCESS) {
if (res == STATE_USER_INPUT_EXEC_DISABLED) {
break;
}
sleep(1);
}
}
return NULL;
}
int main(int argc, char *argv[])
{
int32_t res = STATE_SUCCESS;
void *mqtt_handle = NULL;
uint16_t port = 443; /* 无论设备是否使用TLS连接阿里云平台, 目的端口都是443 */
aiot_sysdep_network_cred_t cred; /* 安全凭据结构体, 如果要用TLS, 这个结构体中配置CA证书等参数 */
/* 配置SDK的底层依赖 */
aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
/* 配置SDK的日志输出 */
aiot_state_set_logcb(demo_state_logcb);
/* 创建SDK的安全凭据, 用于建立TLS连接 */
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA; /* 使用RSA证书校验MQTT服务端 */
cred.max_tls_fragment = 16384; /* 最大的分片长度为16K, 其它可选值还有4K, 2K, 1K, 0.5K */
cred.sni_enabled = 1; /* TLS建连时, 支持Server Name Indicator */
cred.x509_server_cert = ali_ca_cert; /* 用来验证MQTT服务端的RSA根证书 */
cred.x509_server_cert_len = strlen(ali_ca_cert); /* 用来验证MQTT服务端的RSA根证书长度 */
/* 创建1个MQTT客户端实例并内部初始化默认参数 */
mqtt_handle = aiot_mqtt_init();
if (mqtt_handle == NULL) {
printf("aiot_mqtt_init failed\n");
return -1;
}
/* TODO: 如果以下代码不被注释, 则例程会用TCP而不是TLS连接云平台 */
/*
{
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_NONE;
}
*/
/* 配置MQTT服务器地址 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_HOST, (void *)mqtt_host);
/* 配置MQTT服务器端口 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PORT, (void *)&port);
/* 配置设备productKey */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PRODUCT_KEY, (void *)product_key);
/* 配置设备deviceName */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_NAME, (void *)device_name);
/* 配置设备deviceSecret */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_SECRET, (void *)device_secret);
/* 配置网络连接的安全凭据, 上面已经创建好了 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_NETWORK_CRED, (void *)&cred);
/* 配置MQTT默认消息接收回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_RECV_HANDLER, (void *)demo_mqtt_default_recv_handler);
/* 配置MQTT事件回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_EVENT_HANDLER, (void *)demo_mqtt_event_handler);
/* 与服务器建立MQTT连接 */
res = aiot_mqtt_connect(mqtt_handle);
if (res < STATE_SUCCESS) {
/* 尝试建立连接失败, 销毁MQTT实例, 回收资源 */
aiot_mqtt_deinit(&mqtt_handle);
printf("aiot_mqtt_connect failed: -0x%04X\n\r\n", -res);
printf("please check variables like mqtt_host, produt_key, device_name, device_secret in demo\r\n");
return -1;
}
/* MQTT 订阅topic功能示例, 请根据自己的业务需求进行使用 */
/* {
char *sub_topic = "/sys/${YourProductKey}/${YourDeviceName}/thing/event/+/post_reply";
res = aiot_mqtt_sub(mqtt_handle, sub_topic, NULL, 1, NULL);
if (res < 0) {
printf("aiot_mqtt_sub failed, res: -0x%04X\n", -res);
return -1;
}
} */
/* MQTT 发布消息功能示例, 请根据自己的业务需求进行使用 */
/* {
char *pub_topic = "/sys/${YourProductKey}/${YourDeviceName}/thing/event/property/post";
char *pub_payload = "{\"id\":\"1\",\"version\":\"1.0\",\"params\":{\"LightSwitch\":0}}";
res = aiot_mqtt_pub(mqtt_handle, pub_topic, (uint8_t *)pub_payload, (uint32_t)strlen(pub_payload), 0);
if (res < 0) {
printf("aiot_mqtt_sub failed, res: -0x%04X\n", -res);
return -1;
}
} */
/* 创建一个单独的线程, 专用于执行aiot_mqtt_process, 它会自动发送心跳保活, 以及重发QoS1的未应答报文 */
g_mqtt_process_thread_running = 1;
res = pthread_create(&g_mqtt_process_thread, NULL, demo_mqtt_process_thread, mqtt_handle);
if (res < 0) {
printf("pthread_create demo_mqtt_process_thread failed: %d\n", res);
return -1;
}
/* 创建一个单独的线程用于执行aiot_mqtt_recv, 它会循环收取服务器下发的MQTT消息, 并在断线时自动重连 */
g_mqtt_recv_thread_running = 1;
res = pthread_create(&g_mqtt_recv_thread, NULL, demo_mqtt_recv_thread, mqtt_handle);
if (res < 0) {
printf("pthread_create demo_mqtt_recv_thread failed: %d\n", res);
return -1;
}
/* 主循环进入休眠 */
while (1) {
sleep(1);
}
/* 断开MQTT连接, 一般不会运行到这里 */
g_mqtt_process_thread_running = 0;
g_mqtt_recv_thread_running = 0;
sleep(1);
pthread_join(g_mqtt_process_thread, NULL);
pthread_join(g_mqtt_recv_thread, NULL);
res = aiot_mqtt_disconnect(mqtt_handle);
if (res < STATE_SUCCESS) {
aiot_mqtt_deinit(&mqtt_handle);
printf("aiot_mqtt_disconnect failed: -0x%04X\n", -res);
return -1;
}
/* 销毁MQTT实例, 一般不会运行到这里 */
res = aiot_mqtt_deinit(&mqtt_handle);
if (res < STATE_SUCCESS) {
printf("aiot_mqtt_deinit failed: -0x%04X\n", -res);
return -1;
}
return 0;
}

View File

@@ -0,0 +1,277 @@
/*
* 这个例程适用于`Linux`这类支持pthread的POSIX设备, 它演示了用SDK配置MQTT参数并建立连接, 之后创建2个线程
*
* + 一个线程用于保活长连接
* + 一个线程用于接收消息, 并在有消息到达时进入默认的数据回调, 在连接状态变化时进入事件回调
*
* 需要用户关注或修改的部分, 已经用 TODO 在注释中标明
*
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include "aiot_state_api.h"
#include "aiot_sysdep_api.h"
#include "aiot_mqtt_api.h"
/* TODO: 替换为自己设备的三元组 */
char *product_key = "${YourProductKey}";
char *device_name = "${YourDeviceName}";
char *device_secret = "${YourDeviceSecret}";
/*
TODO: 替换为自己实例的接入点
对于企业实例, 或者2021年07月30日之后含当日开通的物联网平台服务下公共实例
mqtt_host的格式为"${YourInstanceId}.mqtt.iothub.aliyuncs.com"
其中${YourInstanceId}: 请替换为您企业/公共实例的Id
对于2021年07月30日之前不含当日开通的物联网平台服务下公共实例
需要将mqtt_host修改为: mqtt_host = "${YourProductKey}.iot-as-mqtt.${YourRegionId}.aliyuncs.com"
其中, ${YourProductKey}请替换为设备所属产品的ProductKey。可登录物联网平台控制台在对应实例的设备详情页获取。
${YourRegionId}:请替换为您的物联网平台设备所在地域代码, 比如cn-shanghai等
该情况下完整mqtt_host举例: a1TTmBPIChA.iot-as-mqtt.cn-shanghai.aliyuncs.com
详情请见: https://help.aliyun.com/document_detail/147356.html
*/
char *mqtt_host = "${YourInstanceId}.mqtt.iothub.aliyuncs.com";
/* 位于portfiles/aiot_port文件夹下的系统适配函数集合 */
extern aiot_sysdep_portfile_t g_aiot_sysdep_portfile;
/* 位于external/ali_ca_cert.c中的服务器证书 */
extern const char *ali_ca_cert;
static pthread_t g_mqtt_process_thread;
static pthread_t g_mqtt_recv_thread;
static uint8_t g_mqtt_process_thread_running = 0;
static uint8_t g_mqtt_recv_thread_running = 0;
/* TODO: 如果要关闭日志, 就把这个函数实现为空, 如果要减少日志, 可根据code选择不打印
*
* 例如: [1577589489.033][LK-0317] mqtt_broadcast_demo&gb80sFmX7yX
*
* 上面这条日志的code就是0317(十六进制), code值的定义见core/aiot_state_api.h
*
*/
/* 日志回调函数, SDK的日志会从这里输出 */
int32_t demo_state_logcb(int32_t code, char *message)
{
printf("%s", message);
return 0;
}
/* MQTT事件回调函数, 当网络连接/重连/断开时被触发, 事件定义见core/aiot_mqtt_api.h */
void demo_mqtt_event_handler(void *handle, const aiot_mqtt_event_t *event, void *userdata)
{
switch (event->type) {
/* SDK因为用户调用了aiot_mqtt_connect()接口, 与mqtt服务器建立连接已成功 */
case AIOT_MQTTEVT_CONNECT: {
printf("AIOT_MQTTEVT_CONNECT\n");
/* TODO: 处理SDK建连成功, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
/* SDK因为网络状况被动断连后, 自动发起重连已成功 */
case AIOT_MQTTEVT_RECONNECT: {
printf("AIOT_MQTTEVT_RECONNECT\n");
/* TODO: 处理SDK重连成功, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
/* SDK因为网络的状况而被动断开了连接, network是底层读写失败, heartbeat是没有按预期得到服务端心跳应答 */
case AIOT_MQTTEVT_DISCONNECT: {
char *cause = (event->data.disconnect == AIOT_MQTTDISCONNEVT_NETWORK_DISCONNECT) ? ("network disconnect") :
("heartbeat disconnect");
printf("AIOT_MQTTEVT_DISCONNECT: %s\n", cause);
/* TODO: 处理SDK被动断连, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
default: {
}
}
}
/* MQTT默认消息处理回调, 当SDK从服务器收到MQTT消息时, 且无对应用户回调处理时被调用 */
void demo_mqtt_default_recv_handler(void *handle, const aiot_mqtt_recv_t *packet, void *userdata)
{
switch (packet->type) {
case AIOT_MQTTRECV_HEARTBEAT_RESPONSE: {
printf("heartbeat response\n");
/* TODO: 处理服务器对心跳的回应, 一般不处理 */
}
break;
case AIOT_MQTTRECV_SUB_ACK: {
printf("suback, res: -0x%04X, packet id: %d, max qos: %d\n",
-packet->data.sub_ack.res, packet->data.sub_ack.packet_id, packet->data.sub_ack.max_qos);
/* TODO: 处理服务器对订阅请求的回应, 一般不处理 */
}
break;
case AIOT_MQTTRECV_PUB: {
printf("pub, qos: %d, topic: %.*s\n", packet->data.pub.qos, packet->data.pub.topic_len, packet->data.pub.topic);
printf("pub, payload: %.*s\n", packet->data.pub.payload_len, packet->data.pub.payload);
/* TODO: 处理服务器下发的业务报文, 此处打印broadcast的报文 */
/* 值得注意的是云端下发的广播topic格式为 /sys/${product_key}/${device_name}/broadcast/request/+ */
}
break;
case AIOT_MQTTRECV_PUB_ACK: {
printf("puback, packet id: %d\n", packet->data.pub_ack.packet_id);
/* TODO: 处理服务器对QoS1上报消息的回应, 一般不处理 */
}
break;
default: {
}
}
}
/* 执行aiot_mqtt_process的线程, 包含心跳发送和QoS1消息重发 */
void *demo_mqtt_process_thread(void *args)
{
int32_t res = STATE_SUCCESS;
while (g_mqtt_process_thread_running) {
res = aiot_mqtt_process(args);
if (res == STATE_USER_INPUT_EXEC_DISABLED) {
break;
}
sleep(1);
}
return NULL;
}
/* 执行aiot_mqtt_recv的线程, 包含网络自动重连和从服务器收取MQTT消息 */
void *demo_mqtt_recv_thread(void *args)
{
int32_t res = STATE_SUCCESS;
while (g_mqtt_recv_thread_running) {
res = aiot_mqtt_recv(args);
if (res < STATE_SUCCESS) {
if (res == STATE_USER_INPUT_EXEC_DISABLED) {
break;
}
sleep(1);
}
}
return NULL;
}
int main(int argc, char *argv[])
{
int32_t res = STATE_SUCCESS;
void *mqtt_handle = NULL;
uint16_t port = 443; /* 无论设备是否使用TLS连接阿里云平台, 目的端口都是443 */
aiot_sysdep_network_cred_t cred; /* 安全凭据结构体, 如果要用TLS, 这个结构体中配置CA证书等参数 */
/* 配置SDK的底层依赖 */
aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
/* 配置SDK的日志输出 */
aiot_state_set_logcb(demo_state_logcb);
/* 创建SDK的安全凭据, 用于建立TLS连接 */
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA; /* 使用RSA证书校验MQTT服务端 */
cred.max_tls_fragment = 16384; /* 最大的分片长度为16K, 其它可选值还有4K, 2K, 1K, 0.5K */
cred.sni_enabled = 1; /* TLS建连时, 支持Server Name Indicator */
cred.x509_server_cert = ali_ca_cert; /* 用来验证MQTT服务端的RSA根证书 */
cred.x509_server_cert_len = strlen(ali_ca_cert); /* 用来验证MQTT服务端的RSA根证书长度 */
/* 创建1个MQTT客户端实例并内部初始化默认参数 */
mqtt_handle = aiot_mqtt_init();
if (mqtt_handle == NULL) {
printf("aiot_mqtt_init failed\n");
return -1;
}
/* TODO: 如果以下代码不被注释, 则例程会用TCP而不是TLS连接云平台 */
/*
{
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_NONE;
}
*/
/* 配置MQTT服务器地址 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_HOST, (void *)mqtt_host);
/* 配置MQTT服务器端口 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PORT, (void *)&port);
/* 配置设备productKey */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PRODUCT_KEY, (void *)product_key);
/* 配置设备deviceName */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_NAME, (void *)device_name);
/* 配置设备deviceSecret */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_SECRET, (void *)device_secret);
/* 配置网络连接的安全凭据, 上面已经创建好了 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_NETWORK_CRED, (void *)&cred);
/* 配置MQTT默认消息接收回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_RECV_HANDLER, (void *)demo_mqtt_default_recv_handler);
/* 配置MQTT事件回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_EVENT_HANDLER, (void *)demo_mqtt_event_handler);
/* 与服务器建立MQTT连接 */
res = aiot_mqtt_connect(mqtt_handle);
if (res < STATE_SUCCESS) {
/* 尝试建立连接失败, 销毁MQTT实例, 回收资源 */
aiot_mqtt_deinit(&mqtt_handle);
printf("aiot_mqtt_connect failed: -0x%04X\n\r\n", -res);
printf("please check variables like mqtt_host, produt_key, device_name, device_secret in demo\r\n");
return -1;
}
/* 创建一个单独的线程, 专用于执行aiot_mqtt_process, 它会自动发送心跳保活, 以及重发QoS1的未应答报文 */
g_mqtt_process_thread_running = 1;
res = pthread_create(&g_mqtt_process_thread, NULL, demo_mqtt_process_thread, mqtt_handle);
if (res < 0) {
printf("pthread_create demo_mqtt_process_thread failed: %d\n", res);
return -1;
}
/* 创建一个单独的线程用于执行aiot_mqtt_recv, 它会循环收取服务器下发的MQTT消息, 并在断线时自动重连 */
g_mqtt_recv_thread_running = 1;
res = pthread_create(&g_mqtt_recv_thread, NULL, demo_mqtt_recv_thread, mqtt_handle);
if (res < 0) {
printf("pthread_create demo_mqtt_recv_thread failed: %d\n", res);
return -1;
}
/* 主循环进入休眠 */
while (1) {
sleep(1);
}
/* 断开MQTT连接, 一般不会运行到这里 */
g_mqtt_process_thread_running = 0;
g_mqtt_recv_thread_running = 0;
sleep(1);
pthread_join(g_mqtt_process_thread, NULL);
pthread_join(g_mqtt_recv_thread, NULL);
res = aiot_mqtt_disconnect(mqtt_handle);
if (res < STATE_SUCCESS) {
aiot_mqtt_deinit(&mqtt_handle);
printf("aiot_mqtt_disconnect failed: -0x%04X\n", -res);
return -1;
}
/* 销毁MQTT实例, 一般不会运行到这里 */
res = aiot_mqtt_deinit(&mqtt_handle);
if (res < STATE_SUCCESS) {
printf("aiot_mqtt_deinit failed: -0x%04X\n", -res);
return -1;
}
return 0;
}

View File

@@ -0,0 +1,313 @@
/*
* 这个例程适用于`Linux`这类支持pthread的POSIX设备, 它演示了用SDK配置MQTT参数并建立连接, 之后创建2个线程
*
* + 一个线程用于保活长连接
* + 一个线程用于接收消息, 并在有消息到达时进入默认的数据回调, 在连接状态变化时进入事件回调
*
* 需要用户关注或修改的部分, 已经用 TODO 在注释中标明
*
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include "aiot_state_api.h"
#include "aiot_sysdep_api.h"
#include "aiot_mqtt_api.h"
/* TODO: 替换为自己设备的三元组 */
char *product_key = "${YourProductKey}";
char *device_name = "${YourDeviceName}";
char *device_secret = "${YourDeviceSecret}";
/*
TODO: 替换为自己实例的接入点
对于企业实例, 或者2021年07月30日之后含当日开通的物联网平台服务下公共实例
mqtt_host的格式为"${YourInstanceId}.mqtt.iothub.aliyuncs.com"
其中${YourInstanceId}: 请替换为您企业/公共实例的Id
对于2021年07月30日之前不含当日开通的物联网平台服务下公共实例
需要将mqtt_host修改为: mqtt_host = "${YourProductKey}.iot-as-mqtt.${YourRegionId}.aliyuncs.com"
其中, ${YourProductKey}请替换为设备所属产品的ProductKey。可登录物联网平台控制台在对应实例的设备详情页获取。
${YourRegionId}:请替换为您的物联网平台设备所在地域代码, 比如cn-shanghai等
该情况下完整mqtt_host举例: a1TTmBPIChA.iot-as-mqtt.cn-shanghai.aliyuncs.com
详情请见: https://help.aliyun.com/document_detail/147356.html
*/
char *mqtt_host = "${YourInstanceId}.mqtt.iothub.aliyuncs.com";
/* 位于portfiles/aiot_port文件夹下的系统适配函数集合 */
extern aiot_sysdep_portfile_t g_aiot_sysdep_portfile;
/* 位于external/ali_ca_cert.c中的服务器证书 */
extern const char *ali_ca_cert;
static pthread_t g_mqtt_process_thread;
static pthread_t g_mqtt_recv_thread;
static uint8_t g_mqtt_process_thread_running = 0;
static uint8_t g_mqtt_recv_thread_running = 0;
/* TODO: 如果要关闭日志, 就把这个函数实现为空, 如果要减少日志, 可根据code选择不打印
*
* 例如: [1577589489.033][LK-0317] {YourDeviceName}&{YourProductKey}
*
* 上面这条日志的code就是0317(十六进制), code值的定义见core/aiot_state_api.h
*
*/
/* 日志回调函数, SDK的日志会从这里输出 */
int32_t demo_state_logcb(int32_t code, char *message)
{
printf("%s", message);
return 0;
}
/* MQTT事件回调函数, 当网络连接/重连/断开时被触发, 事件定义见core/aiot_mqtt_api.h */
void demo_mqtt_event_handler(void *handle, const aiot_mqtt_event_t *event, void *userdata)
{
switch (event->type) {
/* SDK因为用户调用了aiot_mqtt_connect()接口, 与mqtt服务器建立连接已成功 */
case AIOT_MQTTEVT_CONNECT: {
printf("AIOT_MQTTEVT_CONNECT\n");
/* TODO: 处理SDK建连成功, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
/* SDK因为网络状况被动断连后, 自动发起重连已成功 */
case AIOT_MQTTEVT_RECONNECT: {
printf("AIOT_MQTTEVT_RECONNECT\n");
/* TODO: 处理SDK重连成功, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
/* SDK因为网络的状况而被动断开了连接, network是底层读写失败, heartbeat是没有按预期得到服务端心跳应答 */
case AIOT_MQTTEVT_DISCONNECT: {
char *cause = (event->data.disconnect == AIOT_MQTTDISCONNEVT_NETWORK_DISCONNECT) ? ("network disconnect") :
("heartbeat disconnect");
printf("AIOT_MQTTEVT_DISCONNECT: %s\n", cause);
/* TODO: 处理SDK被动断连, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
default: {
}
}
}
/* MQTT默认消息处理回调, 当SDK从服务器收到MQTT消息时, 且无对应用户回调处理时被调用 */
void demo_mqtt_default_recv_handler(void *handle, const aiot_mqtt_recv_t *packet, void *userdata)
{
switch (packet->type) {
case AIOT_MQTTRECV_HEARTBEAT_RESPONSE: {
printf("heartbeat response\n");
/* TODO: 处理服务器对心跳的回应, 一般不处理 */
}
break;
case AIOT_MQTTRECV_SUB_ACK: {
printf("suback, res: -0x%04X, packet id: %d, max qos: %d\n",
-packet->data.sub_ack.res, packet->data.sub_ack.packet_id, packet->data.sub_ack.max_qos);
/* TODO: 处理服务器对订阅请求的回应, 一般不处理 */
}
break;
case AIOT_MQTTRECV_PUB: {
printf("pub, qos: %d, topic: %.*s\n", packet->data.pub.qos, packet->data.pub.topic_len, packet->data.pub.topic);
printf("pub, payload: %.*s\n", packet->data.pub.payload_len, packet->data.pub.payload);
/* TODO: 处理服务器下发的业务报文, 此处如果收到rrpc的报文, 那么应答时使用相同的topic即可 */
/* 更多信息请参考 https://help.aliyun.com/document_detail/90570.html */
/* 下面是一个rrpc的应答示例 */
/* {
char *payload = "pong";
char resp_topic[256];
if(packet->data.pub.topic_len > 256) {
break;
}
memset(resp_topic, 0, sizeof(resp_topic));
memcpy(resp_topic, packet->data.pub.topic, packet->data.pub.topic_len);
aiot_mqtt_pub(handle, resp_topic, (uint8_t *)payload, (uint32_t)strlen(payload), 0);
} */
}
break;
case AIOT_MQTTRECV_PUB_ACK: {
printf("puback, packet id: %d\n", packet->data.pub_ack.packet_id);
/* TODO: 处理服务器对QoS1上报消息的回应, 一般不处理 */
}
break;
default: {
}
}
}
/* 执行aiot_mqtt_process的线程, 包含心跳发送和QoS1消息重发 */
void *demo_mqtt_process_thread(void *args)
{
int32_t res = STATE_SUCCESS;
while (g_mqtt_process_thread_running) {
res = aiot_mqtt_process(args);
if (res == STATE_USER_INPUT_EXEC_DISABLED) {
break;
}
sleep(1);
}
return NULL;
}
/* 执行aiot_mqtt_recv的线程, 包含网络自动重连和从服务器收取MQTT消息 */
void *demo_mqtt_recv_thread(void *args)
{
int32_t res = STATE_SUCCESS;
while (g_mqtt_recv_thread_running) {
res = aiot_mqtt_recv(args);
if (res < STATE_SUCCESS) {
if (res == STATE_USER_INPUT_EXEC_DISABLED) {
break;
}
sleep(1);
}
}
return NULL;
}
int main(int argc, char *argv[])
{
int32_t res = STATE_SUCCESS;
void *mqtt_handle = NULL;
uint16_t port = 443; /* 无论设备是否使用TLS连接阿里云平台, 目的端口都是443 */
aiot_sysdep_network_cred_t cred; /* 安全凭据结构体, 如果要用TLS, 这个结构体中配置CA证书等参数 */
/* 配置SDK的底层依赖 */
aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
/* 配置SDK的日志输出 */
aiot_state_set_logcb(demo_state_logcb);
/* 创建SDK的安全凭据, 用于建立TLS连接 */
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA; /* 使用RSA证书校验MQTT服务端 */
cred.max_tls_fragment = 16384; /* 最大的分片长度为16K, 其它可选值还有4K, 2K, 1K, 0.5K */
cred.sni_enabled = 1; /* TLS建连时, 支持Server Name Indicator */
cred.x509_server_cert = ali_ca_cert; /* 用来验证MQTT服务端的RSA根证书 */
cred.x509_server_cert_len = strlen(ali_ca_cert); /* 用来验证MQTT服务端的RSA根证书长度 */
/* 创建1个MQTT客户端实例并内部初始化默认参数 */
mqtt_handle = aiot_mqtt_init();
if (mqtt_handle == NULL) {
printf("aiot_mqtt_init failed\n");
return -1;
}
/* TODO: 如果以下代码不被注释, 则例程会用TCP而不是TLS连接云平台 */
/*
{
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_NONE;
}
*/
/* 配置MQTT服务器地址 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_HOST, (void *)mqtt_host);
/* 配置MQTT服务器端口 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PORT, (void *)&port);
/* 配置设备productKey */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PRODUCT_KEY, (void *)product_key);
/* 配置设备deviceName */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_NAME, (void *)device_name);
/* 配置设备deviceSecret */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_SECRET, (void *)device_secret);
/* 配置网络连接的安全凭据, 上面已经创建好了 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_NETWORK_CRED, (void *)&cred);
/* 配置MQTT默认消息接收回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_RECV_HANDLER, (void *)demo_mqtt_default_recv_handler);
/* 配置MQTT事件回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_EVENT_HANDLER, (void *)demo_mqtt_event_handler);
/* 与服务器建立MQTT连接 */
res = aiot_mqtt_connect(mqtt_handle);
if (res < STATE_SUCCESS) {
/* 尝试建立连接失败, 销毁MQTT实例, 回收资源 */
aiot_mqtt_deinit(&mqtt_handle);
printf("aiot_mqtt_connect failed: -0x%04X\n\r\n", -res);
printf("please check variables like mqtt_host, produt_key, device_name, device_secret in demo\r\n");
return -1;
}
/* MQTT 订阅topic功能示例, 请根据自己的业务需求进行使用 */
{
char *sub_topic = "/{YourProductKey}/{YourDeviceName}/user/get";
res = aiot_mqtt_sub(mqtt_handle, sub_topic, NULL, 1, NULL);
if (res < 0) {
printf("aiot_mqtt_sub failed, res: -0x%04X\n", -res);
return -1;
}
}
/* MQTT 发布消息功能示例, 请根据自己的业务需求进行使用 */
/* {
char *pub_topic = "/sys/{YourProductKey}/{YourDeviceName}/thing/event/property/post";
char *pub_payload = "{\"id\":\"1\",\"version\":\"1.0\",\"params\":{\"LightSwitch\":0}}";
res = aiot_mqtt_pub(mqtt_handle, pub_topic, (uint8_t *)pub_payload, (uint32_t)strlen(pub_payload), 0);
if (res < 0) {
printf("aiot_mqtt_pub failed, res: -0x%04X\n", -res);
return -1;
}
} */
/* 创建一个单独的线程, 专用于执行aiot_mqtt_process, 它会自动发送心跳保活, 以及重发QoS1的未应答报文 */
g_mqtt_process_thread_running = 1;
res = pthread_create(&g_mqtt_process_thread, NULL, demo_mqtt_process_thread, mqtt_handle);
if (res < 0) {
printf("pthread_create demo_mqtt_process_thread failed: %d\n", res);
return -1;
}
/* 创建一个单独的线程用于执行aiot_mqtt_recv, 它会循环收取服务器下发的MQTT消息, 并在断线时自动重连 */
g_mqtt_recv_thread_running = 1;
res = pthread_create(&g_mqtt_recv_thread, NULL, demo_mqtt_recv_thread, mqtt_handle);
if (res < 0) {
printf("pthread_create demo_mqtt_recv_thread failed: %d\n", res);
return -1;
}
/* 主循环进入休眠 */
while (1) {
sleep(1);
}
/* 断开MQTT连接, 一般不会运行到这里 */
g_mqtt_process_thread_running = 0;
g_mqtt_recv_thread_running = 0;
sleep(1);
pthread_join(g_mqtt_process_thread, NULL);
pthread_join(g_mqtt_recv_thread, NULL);
res = aiot_mqtt_disconnect(mqtt_handle);
if (res < STATE_SUCCESS) {
aiot_mqtt_deinit(&mqtt_handle);
printf("aiot_mqtt_disconnect failed: -0x%04X\n", -res);
return -1;
}
/* 销毁MQTT实例, 一般不会运行到这里 */
res = aiot_mqtt_deinit(&mqtt_handle);
if (res < STATE_SUCCESS) {
printf("aiot_mqtt_deinit failed: -0x%04X\n", -res);
return -1;
}
return 0;
}

View File

@@ -0,0 +1,413 @@
/*
* 这个例程适用于`Linux`这类支持pthread的POSIX设备, 它演示了用SDK配置MQTT参数并建立连接, 之后创建2个线程
*
* + 一个线程用于保活长连接
* + 一个线程用于接收消息, 并在有消息到达时进入默认的数据回调, 在连接状态变化时进入事件回调
*
* 接着在MQTT连接上发送NTP查询请求, 如果云平台的回应报文到达, 从接收线程会调用NTP消息处理的回调函数, 把对时后的本地时间打印出来
*
* 需要用户关注或修改的部分, 已经用 TODO 在注释中标明
*
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include "aiot_state_api.h"
#include "aiot_sysdep_api.h"
#include "aiot_mqtt_api.h"
#include "aiot_ntp_api.h"
/* TODO: 替换为自己设备的三元组 */
char *product_key = "${YourProductKey}";
char *device_name = "${YourDeviceName}";
char *device_secret = "${YourDeviceSecret}";
/*
TODO: 替换为自己实例的接入点
对于企业实例, 或者2021年07月30日之后含当日开通的物联网平台服务下公共实例
mqtt_host的格式为"${YourInstanceId}.mqtt.iothub.aliyuncs.com"
其中${YourInstanceId}: 请替换为您企业/公共实例的Id
对于2021年07月30日之前不含当日开通的物联网平台服务下公共实例
需要将mqtt_host修改为: mqtt_host = "${YourProductKey}.iot-as-mqtt.${YourRegionId}.aliyuncs.com"
其中, ${YourProductKey}请替换为设备所属产品的ProductKey。可登录物联网平台控制台在对应实例的设备详情页获取。
${YourRegionId}:请替换为您的物联网平台设备所在地域代码, 比如cn-shanghai等
该情况下完整mqtt_host举例: a1TTmBPIChA.iot-as-mqtt.cn-shanghai.aliyuncs.com
详情请见: https://help.aliyun.com/document_detail/147356.html
*/
char *mqtt_host = "${YourInstanceId}.mqtt.iothub.aliyuncs.com";
/* 位于portfiles/aiot_port文件夹下的系统适配函数集合 */
extern aiot_sysdep_portfile_t g_aiot_sysdep_portfile;
/* 位于external/ali_ca_cert.c中的服务器证书 */
extern const char *ali_ca_cert;
static pthread_t g_mqtt_process_thread;
static pthread_t g_mqtt_recv_thread;
static uint8_t g_mqtt_process_thread_running = 0;
static uint8_t g_mqtt_recv_thread_running = 0;
/* TODO: 如果要关闭日志, 就把这个函数实现为空, 如果要减少日志, 可根据code选择不打印
*
* 例如: [1581501698.455][LK-0309] pub: /ext/ntp/a13FN5TplKq/ntp_basic_demo/request
*
* 上面这条日志的code就是0309(十六进制), code值的定义见core/aiot_state_api.h
*
*/
/* 日志回调函数, SDK的日志会从这里输出 */
static int32_t demo_state_logcb(int32_t code, char *message)
{
printf("%s", message);
return 0;
}
/* MQTT事件回调函数, 当网络连接/重连/断开时被触发, 事件定义见core/aiot_mqtt_api.h */
void demo_mqtt_event_handler(void *handle, const aiot_mqtt_event_t *event, void *userdata)
{
switch (event->type) {
/* SDK因为用户调用了aiot_mqtt_connect()接口, 与mqtt服务器建立连接已成功 */
case AIOT_MQTTEVT_CONNECT: {
printf("AIOT_MQTTEVT_CONNECT\n");
}
break;
/* SDK因为网络状况被动断连后, 自动发起重连已成功 */
case AIOT_MQTTEVT_RECONNECT: {
printf("AIOT_MQTTEVT_RECONNECT\n");
}
break;
/* SDK因为网络的状况而被动断开了连接, network是底层读写失败, heartbeat是没有按预期得到服务端心跳应答 */
case AIOT_MQTTEVT_DISCONNECT: {
char *cause = (event->data.disconnect == AIOT_MQTTDISCONNEVT_NETWORK_DISCONNECT) ? ("network disconnect") :
("heartbeat disconnect");
printf("AIOT_MQTTEVT_DISCONNECT: %s\n", cause);
}
break;
default: {
}
}
}
/* MQTT默认消息处理回调, 当SDK从服务器收到MQTT消息时, 且无对应用户回调处理时被调用 */
void demo_mqtt_default_recv_handler(void *handle, const aiot_mqtt_recv_t *packet, void *userdata)
{
switch (packet->type) {
case AIOT_MQTTRECV_HEARTBEAT_RESPONSE: {
printf("heartbeat response\n");
}
break;
case AIOT_MQTTRECV_SUB_ACK: {
printf("suback, res: -0x%04X, packet id: %d, max qos: %d\n",
-packet->data.sub_ack.res, packet->data.sub_ack.packet_id, packet->data.sub_ack.max_qos);
}
break;
case AIOT_MQTTRECV_PUB: {
printf("pub, qos: %d, topic: %.*s\n", packet->data.pub.qos, packet->data.pub.topic_len, packet->data.pub.topic);
printf("pub, payload: %.*s\n", packet->data.pub.payload_len, packet->data.pub.payload);
}
break;
case AIOT_MQTTRECV_PUB_ACK: {
printf("puback, packet id: %d\n", packet->data.pub_ack.packet_id);
}
break;
default: {
}
}
}
/* 执行aiot_mqtt_process的线程, 包含心跳发送和QoS1消息重发 */
void *demo_mqtt_process_thread(void *args)
{
int32_t res = STATE_SUCCESS;
while (g_mqtt_process_thread_running) {
res = aiot_mqtt_process(args);
if (res == STATE_USER_INPUT_EXEC_DISABLED) {
break;
}
sleep(1);
}
return NULL;
}
/* 执行aiot_mqtt_recv的线程, 包含网络自动重连和从服务器收取MQTT消息 */
void *demo_mqtt_recv_thread(void *args)
{
int32_t res = STATE_SUCCESS;
while (g_mqtt_recv_thread_running) {
res = aiot_mqtt_recv(args);
if (res < STATE_SUCCESS) {
if (res == STATE_USER_INPUT_EXEC_DISABLED) {
break;
}
sleep(1);
}
}
return NULL;
}
int32_t demo_mqtt_start(void **handle)
{
int32_t res = STATE_SUCCESS;
void *mqtt_handle = NULL;
uint16_t port = 443; /* 无论设备是否使用TLS连接阿里云平台, 目的端口都是443 */
aiot_sysdep_network_cred_t cred; /* 安全凭据结构体, 如果要用TLS, 这个结构体中配置CA证书等参数 */
/* 配置SDK的底层依赖 */
aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
/* 配置SDK的日志输出 */
aiot_state_set_logcb(demo_state_logcb);
/* 创建SDK的安全凭据, 用于建立TLS连接 */
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA; /* 使用RSA证书校验MQTT服务端 */
cred.max_tls_fragment = 16384; /* 最大的分片长度为16K, 其它可选值还有4K, 2K, 1K, 0.5K */
cred.sni_enabled = 1; /* TLS建连时, 支持Server Name Indicator */
cred.x509_server_cert = ali_ca_cert; /* 用来验证MQTT服务端的RSA根证书 */
cred.x509_server_cert_len = strlen(ali_ca_cert); /* 用来验证MQTT服务端的RSA根证书长度 */
/* 创建1个MQTT客户端实例并内部初始化默认参数 */
mqtt_handle = aiot_mqtt_init();
if (mqtt_handle == NULL) {
printf("aiot_mqtt_init failed\n");
return -1;
}
/* TODO: 如果以下代码不被注释, 则例程会用TCP而不是TLS连接云平台 */
/*
{
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_NONE;
}
*/
/* 配置MQTT服务器地址 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_HOST, (void *)mqtt_host);
/* 配置MQTT服务器端口 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PORT, (void *)&port);
/* 配置设备productKey */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PRODUCT_KEY, (void *)product_key);
/* 配置设备deviceName */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_NAME, (void *)device_name);
/* 配置设备deviceSecret */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_SECRET, (void *)device_secret);
/* 配置网络连接的安全凭据, 上面已经创建好了 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_NETWORK_CRED, (void *)&cred);
/* 配置MQTT默认消息接收回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_RECV_HANDLER, (void *)demo_mqtt_default_recv_handler);
/* 配置MQTT事件回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_EVENT_HANDLER, (void *)demo_mqtt_event_handler);
/* 与服务器建立MQTT连接 */
res = aiot_mqtt_connect(mqtt_handle);
if (res < STATE_SUCCESS) {
/* 尝试建立连接失败, 销毁MQTT实例, 回收资源 */
aiot_mqtt_deinit(&mqtt_handle);
printf("aiot_mqtt_connect failed: -0x%04X\n\r\n", -res);
printf("please check variables like mqtt_host, produt_key, device_name, device_secret in demo\r\n");
return -1;
}
/* 创建一个单独的线程, 专用于执行aiot_mqtt_process, 它会自动发送心跳保活, 以及重发QoS1的未应答报文 */
g_mqtt_process_thread_running = 1;
res = pthread_create(&g_mqtt_process_thread, NULL, demo_mqtt_process_thread, mqtt_handle);
if (res < 0) {
printf("pthread_create demo_mqtt_process_thread failed: %d\n", res);
aiot_mqtt_deinit(&mqtt_handle);
return -1;
}
/* 创建一个单独的线程用于执行aiot_mqtt_recv, 它会循环收取服务器下发的MQTT消息, 并在断线时自动重连 */
g_mqtt_recv_thread_running = 1;
res = pthread_create(&g_mqtt_recv_thread, NULL, demo_mqtt_recv_thread, mqtt_handle);
if (res < 0) {
printf("pthread_create demo_mqtt_recv_thread failed: %d\n", res);
g_mqtt_process_thread_running = 0;
pthread_join(g_mqtt_process_thread, NULL);
aiot_mqtt_deinit(&mqtt_handle);
return -1;
}
*handle = mqtt_handle;
return 0;
}
int32_t demo_mqtt_stop(void **handle)
{
int32_t res = STATE_SUCCESS;
void *mqtt_handle = NULL;
mqtt_handle = *handle;
g_mqtt_process_thread_running = 0;
g_mqtt_recv_thread_running = 0;
pthread_join(g_mqtt_process_thread, NULL);
pthread_join(g_mqtt_recv_thread, NULL);
/* 断开MQTT连接 */
res = aiot_mqtt_disconnect(mqtt_handle);
if (res < STATE_SUCCESS) {
aiot_mqtt_deinit(&mqtt_handle);
printf("aiot_mqtt_disconnect failed: -0x%04X\n", -res);
return -1;
}
/* 销毁MQTT实例 */
res = aiot_mqtt_deinit(&mqtt_handle);
if (res < STATE_SUCCESS) {
printf("aiot_mqtt_deinit failed: -0x%04X\n", -res);
return -1;
}
return 0;
}
/* 事件处理回调, */
void demo_ntp_event_handler(void *handle, const aiot_ntp_event_t *event, void *userdata)
{
switch (event->type) {
case AIOT_NTPEVT_INVALID_RESPONSE: {
printf("AIOT_NTPEVT_INVALID_RESPONSE\n");
}
break;
case AIOT_NTPEVT_INVALID_TIME_FORMAT: {
printf("AIOT_NTPEVT_INVALID_TIME_FORMAT\n");
}
break;
default: {
}
}
}
/* TODO: 数据处理回调, 当SDK从网络上收到ntp消息时被调用 */
void demo_ntp_recv_handler(void *handle, const aiot_ntp_recv_t *packet, void *userdata)
{
switch (packet->type) {
/* TODO: 结构体 aiot_ntp_recv_t{} 中包含当前时区下, 年月日时分秒的数值, 可在这里把它们解析储存起来 */
case AIOT_NTPRECV_LOCAL_TIME: {
printf("local time: %llu, %02d/%02d/%02d-%02d:%02d:%02d:%d\n",
(long long unsigned int)packet->data.local_time.timestamp,
packet->data.local_time.year,
packet->data.local_time.mon, packet->data.local_time.day, packet->data.local_time.hour, packet->data.local_time.min,
packet->data.local_time.sec, packet->data.local_time.msec);
}
break;
default: {
}
}
}
int main(int argc, char *argv[])
{
int32_t res = STATE_SUCCESS;
/*
* 这里用中国所在的东8区演示功能, 因此例程运行时打印的是北京时间
*
* TODO: 若是其它时区, 可做相应调整, 如西3区则把8改为-3, time_zone的合理取值是-12到+12的整数
*
*/
int8_t time_zone = 8;
void *mqtt_handle = NULL, *ntp_handle = NULL;
/* 建立MQTT连接, 并开启保活线程和接收线程 */
res = demo_mqtt_start(&mqtt_handle);
if (res < 0) {
printf("demo_mqtt_start failed\n");
return -1;
}
/* 创建1个ntp客户端实例并内部初始化默认参数 */
ntp_handle = aiot_ntp_init();
if (ntp_handle == NULL) {
demo_mqtt_stop(&mqtt_handle);
printf("aiot_ntp_init failed\n");
return -1;
}
res = aiot_ntp_setopt(ntp_handle, AIOT_NTPOPT_MQTT_HANDLE, mqtt_handle);
if (res < STATE_SUCCESS) {
printf("aiot_ntp_setopt AIOT_NTPOPT_MQTT_HANDLE failed, res: -0x%04X\n", -res);
aiot_ntp_deinit(&ntp_handle);
demo_mqtt_stop(&mqtt_handle);
return -1;
}
res = aiot_ntp_setopt(ntp_handle, AIOT_NTPOPT_TIME_ZONE, (int8_t *)&time_zone);
if (res < STATE_SUCCESS) {
printf("aiot_ntp_setopt AIOT_NTPOPT_TIME_ZONE failed, res: -0x%04X\n", -res);
aiot_ntp_deinit(&ntp_handle);
demo_mqtt_stop(&mqtt_handle);
return -1;
}
/* TODO: NTP消息回应从云端到达设备时, 会进入此处设置的回调函数 */
res = aiot_ntp_setopt(ntp_handle, AIOT_NTPOPT_RECV_HANDLER, (void *)demo_ntp_recv_handler);
if (res < STATE_SUCCESS) {
printf("aiot_ntp_setopt AIOT_NTPOPT_RECV_HANDLER failed, res: -0x%04X\n", -res);
aiot_ntp_deinit(&ntp_handle);
demo_mqtt_stop(&mqtt_handle);
return -1;
}
res = aiot_ntp_setopt(ntp_handle, AIOT_NTPOPT_EVENT_HANDLER, (void *)demo_ntp_event_handler);
if (res < STATE_SUCCESS) {
printf("aiot_ntp_setopt AIOT_NTPOPT_EVENT_HANDLER failed, res: -0x%04X\n", -res);
aiot_ntp_deinit(&ntp_handle);
demo_mqtt_stop(&mqtt_handle);
return -1;
}
/* 发送NTP查询请求给云平台 */
res = aiot_ntp_send_request(ntp_handle);
if (res < STATE_SUCCESS) {
aiot_ntp_deinit(&ntp_handle);
demo_mqtt_stop(&mqtt_handle);
return -1;
}
/* 主线程进入休眠, 等云平台的NTP回应到达时, 接收线程会调用 demo_ntp_recv_handler() */
while (1) {
sleep(1);
}
/* 销毁NTP实例, 一般不会运行到这里 */
res = aiot_ntp_deinit(&ntp_handle);
if (res < STATE_SUCCESS) {
demo_mqtt_stop(&mqtt_handle);
printf("aiot_ntp_deinit failed: -0x%04X\n", -res);
return -1;
}
/* 销毁MQTT实例, 退出线程, 一般不会运行到这里 */
res = demo_mqtt_stop(&mqtt_handle);
if (res < 0) {
printf("demo_start_stop failed\n");
return -1;
}
return 0;
}

View File

@@ -0,0 +1,377 @@
/*
* 这个例程适用于`Linux`这类支持pthread的POSIX设备, 它演示了用SDK配置MQTT参数并建立连接, 之后创建2个线程
*
* + 一个线程用于保活长连接
* + 一个线程用于接收消息, 并在有消息到达时进入默认的数据回调, 在连接状态变化时进入事件回调
*
* 接着演示了在MQTT连接上进行设备影子更新, 删除, 拉取等操作, 取消这些代码段落的注释即可观察运行效果
*
* 需要用户关注或修改的部分, 已经用 TODO 在注释中标明
*
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include "aiot_state_api.h"
#include "aiot_sysdep_api.h"
#include "aiot_mqtt_api.h"
#include "aiot_shadow_api.h"
/* TODO: 替换为自己设备的三元组 */
char *product_key = "${YourProductKey}";
char *device_name = "${YourDeviceName}";
char *device_secret = "${YourDeviceSecret}";
/*
TODO: 替换为自己实例的接入点
对于企业实例, 或者2021年07月30日之后含当日开通的物联网平台服务下公共实例
mqtt_host的格式为"${YourInstanceId}.mqtt.iothub.aliyuncs.com"
其中${YourInstanceId}: 请替换为您企业/公共实例的Id
对于2021年07月30日之前不含当日开通的物联网平台服务下公共实例
需要将mqtt_host修改为: mqtt_host = "${YourProductKey}.iot-as-mqtt.${YourRegionId}.aliyuncs.com"
其中, ${YourProductKey}请替换为设备所属产品的ProductKey。可登录物联网平台控制台在对应实例的设备详情页获取。
${YourRegionId}:请替换为您的物联网平台设备所在地域代码, 比如cn-shanghai等
该情况下完整mqtt_host举例: a1TTmBPIChA.iot-as-mqtt.cn-shanghai.aliyuncs.com
详情请见: https://help.aliyun.com/document_detail/147356.html
*/
char *mqtt_host = "${YourInstanceId}.mqtt.iothub.aliyuncs.com";
/* 位于portfiles/aiot_port文件夹下的系统适配函数集合 */
extern aiot_sysdep_portfile_t g_aiot_sysdep_portfile;
/* 位于external/ali_ca_cert.c中的服务器证书 */
extern const char *ali_ca_cert;
static pthread_t g_mqtt_process_thread;
static pthread_t g_mqtt_recv_thread;
static uint8_t g_mqtt_process_thread_running = 0;
static uint8_t g_mqtt_recv_thread_running = 0;
/* TODO: 如果要关闭日志, 就把这个函数实现为空, 如果要减少日志, 可根据code选择不打印
*
* 例如: [1577589489.033][LK-0317] shadow_basic_demo&a13FN5TplKq
*
* 上面这条日志的code就是0317(十六进制), code值的定义见core/aiot_state_api.h
*
*/
/* 日志回调函数, SDK的日志会从这里输出 */
int32_t demo_state_logcb(int32_t code, char *message)
{
printf("%s", message);
return 0;
}
/* MQTT事件回调函数, 当网络连接/重连/断开时被触发, 事件定义见core/aiot_mqtt_api.h */
void demo_mqtt_event_handler(void *handle, const aiot_mqtt_event_t *event, void *userdata)
{
switch (event->type) {
/* SDK因为用户调用了aiot_mqtt_connect()接口, 与mqtt服务器建立连接已成功 */
case AIOT_MQTTEVT_CONNECT: {
printf("AIOT_MQTTEVT_CONNECT\n");
}
break;
/* SDK因为网络状况被动断连后, 自动发起重连已成功 */
case AIOT_MQTTEVT_RECONNECT: {
printf("AIOT_MQTTEVT_RECONNECT\n");
}
break;
/* SDK因为网络的状况而被动断开了连接, network是底层读写失败, heartbeat是没有按预期得到服务端心跳应答 */
case AIOT_MQTTEVT_DISCONNECT: {
char *cause = (event->data.disconnect == AIOT_MQTTDISCONNEVT_NETWORK_DISCONNECT) ? ("network disconnect") :
("heartbeat disconnect");
printf("AIOT_MQTTEVT_DISCONNECT: %s\n", cause);
}
break;
default: {
}
}
}
/* 执行aiot_mqtt_process的线程, 包含心跳发送和QoS1消息重发 */
void *demo_mqtt_process_thread(void *args)
{
int32_t res = STATE_SUCCESS;
while (g_mqtt_process_thread_running) {
res = aiot_mqtt_process(args);
if (res == STATE_USER_INPUT_EXEC_DISABLED) {
break;
}
sleep(1);
}
return NULL;
}
/* 执行aiot_mqtt_recv的线程, 包含网络自动重连和从服务器收取MQTT消息 */
void *demo_mqtt_recv_thread(void *args)
{
int32_t res = STATE_SUCCESS;
while (g_mqtt_recv_thread_running) {
res = aiot_mqtt_recv(args);
if (res < STATE_SUCCESS) {
if (res == STATE_USER_INPUT_EXEC_DISABLED) {
break;
}
sleep(1);
}
}
return NULL;
}
/* 数据处理回调, 当SDK从网络上收到shadow消息时被调用 */
void demo_shadow_recv_handler(void *handle, const aiot_shadow_recv_t *recv, void *userdata)
{
printf("demo_shadow_recv_handler, type = %d, productKey = %s, deviceName = %s\r\n",
recv->type, recv->product_key, recv->device_name);
switch (recv->type) {
/* 当设备发送AIOT_SHADOWMSG_UPDATE, AIOT_SHADOWMSG_CLEAN_DESIRED或者AIOT_SHADOWMSG_DELETE_REPORTED消息后, 会收到此应答消息 */
case AIOT_SHADOWRECV_GENERIC_REPLY: {
const aiot_shadow_recv_generic_reply_t *generic_reply = &recv->data.generic_reply;
printf("payload = \"%.*s\", status = %s, timestamp = %ld\r\n",
generic_reply->payload_len,
generic_reply->payload,
generic_reply->status,
(unsigned long)generic_reply->timestamp);
}
break;
/* 当设备在线时, 若用户应用程序调用云端API主动更新设备影子, 设备便会收到此消息 */
case AIOT_SHADOWRECV_CONTROL: {
const aiot_shadow_recv_control_t *control = &recv->data.control;
printf("payload = \"%.*s\", version = %ld\r\n",
control->payload_len,
control->payload,
(unsigned long)control->version);
}
break;
/* 当设备发送AIOT_SHADOWMSG_GET消息主动获取设备影子时, 会收到此消息 */
case AIOT_SHADOWRECV_GET_REPLY: {
const aiot_shadow_recv_get_reply_t *get_reply = &recv->data.get_reply;
printf("payload = \"%.*s\", version = %ld\r\n",
get_reply->payload_len,
get_reply->payload,
(unsigned long)get_reply->version);
}
default:
break;
}
}
/* 发送更新设备影子reported值的请求 */
int32_t demo_update_shadow(void *shadow_handle, char *reported_data, int64_t version)
{
aiot_shadow_msg_t message;
memset(&message, 0, sizeof(aiot_shadow_msg_t));
message.type = AIOT_SHADOWMSG_UPDATE;
message.data.update.reported = reported_data;
message.data.update.version = version;
return aiot_shadow_send(shadow_handle, &message);
}
/* 发送删除设备影子中特定reported值的请求 */
int32_t demo_delete_shadow_report(void *shadow_handle, char *reported, int64_t version)
{
aiot_shadow_msg_t message;
memset(&message, 0, sizeof(aiot_shadow_msg_t));
message.type = AIOT_SHADOWMSG_DELETE_REPORTED;
message.data.delete_reporte.reported = reported;
message.data.delete_reporte.version = version;
return aiot_shadow_send(shadow_handle, &message);
}
/* 发送清除设备影子中所有desired值的请求 */
int32_t demo_clean_shadow_desired(void *shadow_handle, int64_t version)
{
aiot_shadow_msg_t message;
memset(&message, 0, sizeof(aiot_shadow_msg_t));
message.type = AIOT_SHADOWMSG_CLEAN_DESIRED;
message.data.clean_desired.version = version;
return aiot_shadow_send(shadow_handle, &message);
}
/* 发送获取设备影子的请求 */
int32_t demo_get_shadow(void *shadow_handle)
{
aiot_shadow_msg_t message;
memset(&message, 0, sizeof(aiot_shadow_msg_t));
message.type = AIOT_SHADOWMSG_GET;
return aiot_shadow_send(shadow_handle, &message);
}
int main(int argc, char *argv[])
{
int32_t res = STATE_SUCCESS;
void *mqtt_handle = NULL;
void *shadow_handle = NULL;
uint16_t port = 1883; /* 无论设备是否使用TLS连接阿里云平台, 目的端口都是443 */
aiot_sysdep_network_cred_t cred; /* 安全凭据结构体, 如果要用TLS, 这个结构体中配置CA证书等参数 */
/* 配置SDK的底层依赖 */
aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
/* 配置SDK的日志输出 */
aiot_state_set_logcb(demo_state_logcb);
/* 创建SDK的安全凭据, 用于建立TLS连接 */
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA; /* 使用RSA证书校验MQTT服务端 */
cred.max_tls_fragment = 16384; /* 最大的分片长度为16K, 其它可选值还有4K, 2K, 1K, 0.5K */
cred.sni_enabled = 1; /* TLS建连时, 支持Server Name Indicator */
cred.x509_server_cert = ali_ca_cert; /* 用来验证MQTT服务端的RSA根证书 */
cred.x509_server_cert_len = strlen(ali_ca_cert); /* 用来验证MQTT服务端的RSA根证书长度 */
/* 创建1个MQTT客户端实例并内部初始化默认参数 */
mqtt_handle = aiot_mqtt_init();
if (mqtt_handle == NULL) {
printf("aiot_mqtt_init failed\n");
return -1;
}
/* 配置MQTT服务器地址 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_HOST, (void *)mqtt_host);
/* 配置MQTT服务器端口 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PORT, (void *)&port);
/* 配置设备productKey */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PRODUCT_KEY, (void *)product_key);
/* 配置设备deviceName */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_NAME, (void *)device_name);
/* 配置设备deviceSecret */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_SECRET, (void *)device_secret);
/* 配置网络连接的安全凭据, 上面已经创建好了 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_NETWORK_CRED, (void *)&cred);
/* 配置MQTT事件回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_EVENT_HANDLER, (void *)demo_mqtt_event_handler);
/* 创建1个shadow客户端实例并内部初始化默认参数 */
shadow_handle = aiot_shadow_init();
if (shadow_handle == NULL) {
printf("aiot_shadow_init failed\n");
return -1;
}
/* 配置MQTT实例句柄 */
aiot_shadow_setopt(shadow_handle, AIOT_SHADOWOPT_MQTT_HANDLE, mqtt_handle);
/* 配置SHADOW默认消息接收回调函数 */
aiot_shadow_setopt(shadow_handle, AIOT_SHADOWOPT_RECV_HANDLER, (void *)demo_shadow_recv_handler);
/* 与服务器建立MQTT连接 */
res = aiot_mqtt_connect(mqtt_handle);
if (res < STATE_SUCCESS) {
/* 尝试建立连接失败, 销毁MQTT实例, 回收资源 */
aiot_mqtt_deinit(&mqtt_handle);
printf("aiot_mqtt_connect failed: -0x%04X\n\r\n", -res);
printf("please check variables like mqtt_host, produt_key, device_name, device_secret in demo\r\n");
return -1;
}
/* 创建一个单独的线程, 专用于执行aiot_mqtt_process, 它会自动发送心跳保活, 以及重发QoS1的未应答报文 */
g_mqtt_process_thread_running = 1;
res = pthread_create(&g_mqtt_process_thread, NULL, demo_mqtt_process_thread, mqtt_handle);
if (res < 0) {
printf("pthread_create demo_mqtt_process_thread failed: %d\n", res);
return -1;
}
/* 创建一个单独的线程用于执行aiot_mqtt_recv, 它会循环收取服务器下发的MQTT消息, 并在断线时自动重连 */
g_mqtt_recv_thread_running = 1;
res = pthread_create(&g_mqtt_recv_thread, NULL, demo_mqtt_recv_thread, mqtt_handle);
if (res < 0) {
printf("pthread_create demo_mqtt_recv_thread failed: %d\n", res);
return -1;
}
/* 主循环进入休眠 */
while (1) {
/* TODO: 以下代码演示了发送请求拉取设备影子, 设备影子将在demo_shadow_recv_handler回调函数中返回, 用户可取消注释观察演示效果 */
/*
res = demo_get_shadow(shadow_handle);
if (res < 0) {
printf("demo_get_shadow failed, res = -0x%04x\r\n", -res);
}
*/
sleep(2);
/* TODO: 以下代码演示了如何清除设备影子中的desired值, 同时将version设置为1, 用户可取消注释观察演示效果 */
/*
res = demo_clean_shadow_desired(shadow_handle, 1);
if (res < 0) {
printf("demo_clean_shadow_desired failed, res = -0x%04x\r\n", -res);
}
*/
sleep(2);
/* TODO: 以下代码演示了如何删除设备影子中特定的reported值, 同时将version设置为2, 用户可取消注释观察演示效果 */
/*
res = demo_delete_shadow_report(shadow_handle, "{\"LightSwitch\":\"null\"}", 2);
if (res < 0) {
printf("demo_delete_shadow_report failed, res = -0x%04x\r\n", -res);
}
*/
sleep(2);
/* TODO: 以下代码演示了如何更新设备影子中的reported值, 同时将version重置为0, 用户可取消注释观察演示效果 */
/*
res = demo_update_shadow(shadow_handle, "{\"LightSwitch\":1}", 0);
if (res < 0) {
printf("demo_delete_shadow_report failed, res = -0x%04x\r\n", -res);
}
*/
sleep(10);
}
/* 断开MQTT连接, 一般不会运行到这里 */
res = aiot_mqtt_disconnect(mqtt_handle);
if (res < STATE_SUCCESS) {
aiot_mqtt_deinit(&mqtt_handle);
printf("aiot_mqtt_disconnect failed: -0x%04X\n", -res);
return -1;
}
/* 销毁SHADOW实例, 一般不会运行到这里 */
res = aiot_shadow_deinit(&shadow_handle);
if (res < STATE_SUCCESS) {
printf("aiot_shadow_deinit failed: -0x%04X\n", -res);
return -1;
}
/* 销毁MQTT实例, 一般不会运行到这里 */
res = aiot_mqtt_deinit(&mqtt_handle);
if (res < STATE_SUCCESS) {
printf("aiot_mqtt_deinit failed: -0x%04X\n", -res);
return -1;
}
g_mqtt_process_thread_running = 0;
g_mqtt_recv_thread_running = 0;
pthread_join(g_mqtt_process_thread, NULL);
pthread_join(g_mqtt_recv_thread, NULL);
return 0;
}

View File

@@ -0,0 +1,832 @@
/*
* 这个例程用于用户在移植后验证是否移植成功的。
* 通过一系列的测试case验证移植的相关接口是否正常工作
* + 正常结束后会输出TEST SUCCESS其它会输出TEST ERROR + ERRORCODE
* + 如遇长时间卡住也为测试失败
*
* 该测试工具会使用多线程/多任务的能力在RTOS下使用需要自行适配任务创建。
* 需要用户关注或修改的部分, 已经用 TODO 在注释中标明
*
*/
#include "aiot_sysdep_api.h"
#include "aiot_state_api.h"
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#define DEBUG_INFO(...) do{ printf("Line[%d]: ",__LINE__); printf(__VA_ARGS__); printf("\r\n");}while(0);
/* 定义入口函数类型 */
typedef void *(*TASK_FUNC)(void* argv);
/* 定义适配任务创建函数*/
void task_start(TASK_FUNC entry,void* argv);
/*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> TODO START >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/
/*
* TODO: task_start功能实现创建任务并执行任务待任务结束后自行退出
* @param[in] entry 任务函数入口
* @param[in] argv 任务函数参数
*/
#include<pthread.h>
void task_start(TASK_FUNC entry,void* argv)
{
pthread_t id;
pthread_create(&id, NULL, (void*(*)(void *))entry, argv);
}
/*TODO: 堆最大空间,单位字节 */
#define HEAP_MAX ( 20 * 1024 )
/*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< TODO END <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/
/**
* 系统接口测试结果
*/
typedef enum {
TEST_SUCCESS,
TEST_ERR_RANDOM,
TEST_ERR_MALLOC,
TEST_ERR_HEAP,
TEST_ERR_SLEEP,
TEST_ERR_MUTEX,
TEST_ERR_NETWORK,
TEST_ERR_GENERIC,
} sysdep_test_result_t;
static const char *result_string[] = {
"TEST_SUCCESS",
"TEST_ERR_RANDOM",
"TEST_ERR_MALLOC",
"TEST_ERR_HEAP",
"TEST_ERR_SLEEP",
"TEST_ERR_MUTEX",
"TEST_ERR_NETWORK",
"TEST_ERR_GENERIC",
};
typedef struct {
aiot_sysdep_portfile_t *sysdep;
void* user_data;
char* name;
} task_handler_input_t;
/**
* sysdep测试入口原型定义
*/
typedef sysdep_test_result_t (*sysdep_test_func)(aiot_sysdep_portfile_t* sysdep);
typedef struct
{
uint32_t total_size;//堆总大小
} heap_cfg_t;
typedef struct
{
uint16_t count;
/* 最大重复次数 */
uint16_t repeat_cnt_max;
} random_cfg_t;
typedef struct
{
uint64_t sleep_ms;
uint8_t sleep_end;
} time_cfg_t;
typedef struct
{
void* mutex;
int32_t value;
uint32_t timeout_ms;
} mutex_cfg_t;
typedef struct
{
char* host;
uint16_t port;
uint32_t connect_timeout_ms;
uint32_t recv_timeout_ms;
int32_t recv_buf_len;
uint32_t send_timeout_ms;
uint32_t cycle_test_times;
} network_cfg_t;
/**
* 随机生成随机数列表,重复出现次数不高于指定值
*/
static sysdep_test_result_t random_repeat_test(aiot_sysdep_portfile_t* sysdep, random_cfg_t* rd)
{
typedef uint8_t random_value_t;
random_value_t *random_values = NULL;
int count = 0;
random_value_t temp = 0;
sysdep_test_result_t ret = TEST_SUCCESS;
/* 差值 */
int diff = 0;
/* 递变标志 */
int flag = 0;
random_values = sysdep->core_sysdep_malloc(sizeof(random_value_t) * rd->count, NULL);
if(random_values == NULL){
DEBUG_INFO("\r\nmalloc fail");
ret = TEST_ERR_MALLOC;
goto end;
}
memset(random_values, 0, sizeof(random_value_t) * rd->count);
/* 生成随机数,并统计重复次数 计算差值*/
for(count = 0; count < rd->count; count++) {
sysdep->core_sysdep_rand((uint8_t*)&temp, sizeof(random_value_t));
temp %= rd->count;
/* 检测是否为规律递变 */
if(count == 1 ){
diff = random_values[count] - random_values[count - 1];
}else if (count >= 2){
if(diff == random_values[count] - random_values[count - 1]){
flag++;
}
}
if(random_values[temp] <= rd->repeat_cnt_max) {
random_values[temp]++;
}
else {
DEBUG_INFO("randowm value [%d] times [%d] over max counter [%d]", temp, random_values[temp], rd->repeat_cnt_max);
ret = TEST_ERR_RANDOM;
goto end;
}
}
/* 规律递变则返回错误 */
if(rd->count >= 3 && flag >= rd->count - 2){
DEBUG_INFO("random value error");
ret = TEST_ERR_RANDOM;
goto end;
}
ret = TEST_SUCCESS;
end:
if(random_values != NULL){
sysdep->core_sysdep_free(random_values);
}
return ret;
}
/**
* 随机数测试入口
*/
sysdep_test_result_t random_test(aiot_sysdep_portfile_t* sysdep)
{
random_cfg_t rd;
/* 随机数接口要求生成的随机数按字节随机 */
rd.count = 256;
/* 随机数重复次数不能超过该值 */
rd.repeat_cnt_max = 10;
return random_repeat_test(sysdep, &rd);
}
static sysdep_test_result_t heap_malloc_max_test(aiot_sysdep_portfile_t* sysdep, heap_cfg_t* hp)
{
/**
* 最大空间申请、释放
*/
uint32_t user_malloc_max = hp->total_size - 100;
void * ptr = sysdep->core_sysdep_malloc(user_malloc_max, "");
if(ptr == NULL) {
DEBUG_INFO("malloc error");
return TEST_ERR_MALLOC;
}
/* 空间读写测试 */
uint8_t read_value;
for(int i = 0; i < user_malloc_max; i++) {
((uint8_t*)ptr)[i] = (uint8_t)i;
read_value = ((uint8_t*)ptr)[i];
if(read_value != (uint8_t)i) {
sysdep->core_sysdep_free(ptr);
DEBUG_INFO("heap read and write fail");
return TEST_ERR_MALLOC;
}
}
sysdep->core_sysdep_free(ptr);
return TEST_SUCCESS;
}
/**
* 堆回收测试
*/
static sysdep_test_result_t heap_recycle_test(aiot_sysdep_portfile_t* sysdep, heap_cfg_t* hp)
{
/* 待申请的堆空间大小 */
const uint32_t size_list[]= {1,2,4,8,16,32,64,128,256,512,1024,10*1024,100*1024,1024*1024};
/* 存储堆指针 */
void* malloc_list[sizeof(size_list) / sizeof(uint32_t)] = {NULL};
int malloc_list_size = sizeof(size_list) / sizeof(uint32_t);
/* 申请空间 */
int malloc_cnt = 0;
int i = 0;
for(i = 0; i < malloc_list_size; i++) {
malloc_list[i] = sysdep->core_sysdep_malloc(size_list[i], "");
if(malloc_list[i] == NULL) {
break;
}
}
malloc_cnt = i;
/**
* 非相邻空间释放
*/
for(i = 0; i < malloc_cnt; i+=2) {
sysdep->core_sysdep_free(malloc_list[i]);
}
for(i = 1; i < malloc_cnt; i+=2) {
sysdep->core_sysdep_free(malloc_list[i]);
}
/**
* 最后申请最大空间
*/
return heap_malloc_max_test(sysdep, hp);
}
/**
* 堆测试入口
*/
sysdep_test_result_t heap_test(aiot_sysdep_portfile_t* sysdep)
{
sysdep_test_result_t ret = TEST_SUCCESS;
heap_cfg_t hp;
hp.total_size = HEAP_MAX;
ret = heap_malloc_max_test(sysdep,&hp);
if(ret != TEST_SUCCESS) {
return ret;
}
return heap_recycle_test(sysdep, &hp);
}
static sysdep_test_result_t network_tcp_test(aiot_sysdep_portfile_t* sysdep, network_cfg_t* cfg)
{
void* network_hd = sysdep->core_sysdep_network_init();
core_sysdep_socket_type_t type = CORE_SYSDEP_SOCKET_TCP_CLIENT;
sysdep->core_sysdep_network_setopt(network_hd, CORE_SYSDEP_NETWORK_SOCKET_TYPE, &type);
sysdep->core_sysdep_network_setopt(network_hd, CORE_SYSDEP_NETWORK_HOST, cfg->host);
sysdep->core_sysdep_network_setopt(network_hd, CORE_SYSDEP_NETWORK_PORT, (void*)&cfg->port);
sysdep->core_sysdep_network_setopt(network_hd, CORE_SYSDEP_NETWORK_CONNECT_TIMEOUT_MS, &cfg->connect_timeout_ms);
/**
* 建连测试
*/
if(0 != sysdep->core_sysdep_network_establish(network_hd)) {
DEBUG_INFO("network establish test error:%d", TEST_ERR_NETWORK);
return TEST_ERR_NETWORK;
}
/**
* 接收等待超时测试
*/
char* buf = sysdep->core_sysdep_malloc(cfg->recv_buf_len + 1, "");
if(buf == NULL){
DEBUG_INFO("malloc fail");
return TEST_ERR_MALLOC;
}
uint64_t start = sysdep->core_sysdep_time();
sysdep->core_sysdep_network_recv(network_hd, (uint8_t*)buf, cfg->recv_buf_len, cfg->recv_timeout_ms, NULL);
uint64_t stop = sysdep->core_sysdep_time();
if(stop - start < cfg->recv_timeout_ms) {
DEBUG_INFO("network receive test error:%d, start [%"PRIu64"], end [%"PRIu64"]", TEST_ERR_NETWORK, start, stop);
return TEST_ERR_NETWORK;
}
/**
* 通过Http Get请求来验证TCP是否工作正常
*/
char* http_get = "GET / HTTP/1.1\r\n"\
"Host: www.aliyun.com\r\n"\
"Connection: keep-alive\r\n"\
"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36\r\n"\
"Origin: https://www.aliyun.com\r\n"\
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\n"\
"\r\n";
int32_t send_len = strlen(http_get);
int32_t send_result = sysdep->core_sysdep_network_send(network_hd, (uint8_t*)http_get, send_len, cfg->send_timeout_ms, NULL);
if(send_result != send_len) {
DEBUG_INFO("network send length test error:%d, send [%d], result [%d]", TEST_ERR_NETWORK, send_len, send_result);
return TEST_ERR_NETWORK;
}
int32_t buf_len = 0;
uint8_t byte_read;
int32_t content_len = 0;
int32_t ret;
char* str_ptr = NULL;
char* target_str = "Content-Length:";
int state = 0;
while(1) {
ret = sysdep->core_sysdep_network_recv(network_hd, (uint8_t*)&byte_read, 1, cfg->recv_timeout_ms, NULL);
/* 需支持按字节读取 */
if(ret == 1) {
if(state == 0) {
/* 提取一行 */
buf[buf_len++] = byte_read;
if(byte_read != '\n') {
continue;
}
buf[buf_len] = '\0';
/*空行表示header结束*/
if(buf_len == 2 && buf[0] == '\r' && buf[1] == '\n') {
state = 1;
buf_len = 0;
continue;
}
/*搜索应答长度*/
str_ptr = strstr(buf,target_str);
if(str_ptr == NULL) {
buf_len = 0;
continue;
}
str_ptr += strlen(target_str);
if(str_ptr >= &buf[buf_len]) {
buf_len = 0;
continue;
}
if( 1 == sscanf(str_ptr,"%d",&content_len)) {
buf_len = 0;
}
} else {
buf[buf_len++] = byte_read;
}
}
else {
break;
}
}
sysdep->core_sysdep_network_deinit(&network_hd);
sysdep->core_sysdep_free(buf);
buf = NULL;
if(network_hd != NULL){
DEBUG_INFO("network deinit error");
return TEST_ERR_NETWORK;
}
/* http的正文长度校验 */
if(buf_len == 0 || buf_len != content_len) {
DEBUG_INFO("[NETWORK_TEST.RECV] test error:%d, send [%d], result [%d]", TEST_ERR_NETWORK, buf_len, content_len);
return TEST_ERR_NETWORK;
} else {
DEBUG_INFO("[NETWORK_TEST.RECV] test success");
return TEST_SUCCESS;
}
}
sysdep_test_result_t network_tcp_cycle_test(aiot_sysdep_portfile_t* sysdep, network_cfg_t* cfg)
{
void* network_hd = NULL;
int count = cfg->cycle_test_times;
while(count-- > 0) {
network_hd = sysdep->core_sysdep_network_init();
if(network_hd == NULL) {
DEBUG_INFO("network tcp init test error");
return TEST_ERR_NETWORK;
}
core_sysdep_socket_type_t type = CORE_SYSDEP_SOCKET_TCP_CLIENT;
sysdep->core_sysdep_network_setopt(network_hd, CORE_SYSDEP_NETWORK_SOCKET_TYPE, &type);
sysdep->core_sysdep_network_setopt(network_hd, CORE_SYSDEP_NETWORK_HOST, cfg->host);
sysdep->core_sysdep_network_setopt(network_hd, CORE_SYSDEP_NETWORK_PORT, (void*)&cfg->port);
sysdep->core_sysdep_network_setopt(network_hd, CORE_SYSDEP_NETWORK_CONNECT_TIMEOUT_MS, &cfg->connect_timeout_ms);
if(0 != sysdep->core_sysdep_network_establish(network_hd)) {
DEBUG_INFO("network tcp establish test error:%d", TEST_ERR_NETWORK);
return TEST_ERR_NETWORK;
}
sysdep->core_sysdep_network_deinit(&network_hd);
}
if( TEST_SUCCESS != heap_test(sysdep) ) {
DEBUG_INFO("network deinit then heap test error:%d", TEST_ERR_NETWORK);
return TEST_ERR_NETWORK;
} else {
return TEST_SUCCESS;
}
}
static sysdep_test_result_t network_tcp_send_length_test(aiot_sysdep_portfile_t* sysdep, network_cfg_t* cfg)
{
sysdep_test_result_t ret = TEST_SUCCESS;
void* network_hd = sysdep->core_sysdep_network_init();
core_sysdep_socket_type_t type = CORE_SYSDEP_SOCKET_TCP_CLIENT;
sysdep->core_sysdep_network_setopt(network_hd, CORE_SYSDEP_NETWORK_SOCKET_TYPE, &type);
sysdep->core_sysdep_network_setopt(network_hd, CORE_SYSDEP_NETWORK_HOST, cfg->host);
sysdep->core_sysdep_network_setopt(network_hd, CORE_SYSDEP_NETWORK_PORT, (void*)&cfg->port);
sysdep->core_sysdep_network_setopt(network_hd, CORE_SYSDEP_NETWORK_CONNECT_TIMEOUT_MS, &cfg->connect_timeout_ms);
/**
* 建连测试
*/
if(0 != sysdep->core_sysdep_network_establish(network_hd)) {
DEBUG_INFO("network establish test error:%d", TEST_ERR_NETWORK);
return TEST_ERR_NETWORK;
}
/**
* 通过Http Get请求来验证TCP发送接口是否具备长包发送能力
*/
const char* http_get = "GET / HTTP/1.1\r\n"\
"Host: www.aliyun.com\r\n"\
"Connection: keep-alive\r\n"\
"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36\r\n"\
"Origin: https://www.aliyun.com\r\n"\
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\n"\
"\r\n"\
"invalid test append data,invalid test append data,invalid test append data,invalid test append data,"\
"invalid test append data,invalid test append data,invalid test append data,invalid test append data,"\
"invalid test append data,invalid test append data,invalid test append data,invalid test append data,"\
"invalid test append data,invalid test append data,invalid test append data,invalid test append data,"\
"invalid test append data,invalid test append data,invalid test append data,invalid test append data,"\
"invalid test append data,invalid test append data,invalid test append data,invalid test append data,"\
"invalid test append data,invalid test append data,invalid test append data,invalid test append data,"\
"invalid test append data,invalid test append data,invalid test append data,invalid test append data,"\
"invalid test append data,invalid test append data,invalid test append data,invalid test append data,"\
"invalid test append data,invalid test append data,invalid test append data,invalid test append data,"\
"invalid test append data,invalid test append data,invalid test append data,invalid test append data,"\
"invalid test append data,invalid test append data,invalid test append data,invalid test append data,"\
"invalid test append data,invalid test append data,invalid test append data,invalid test append data,"\
"invalid test append data,invalid test append data,invalid test append data,invalid test append data,"\
"invalid test append data,invalid test append data,invalid test append data,invalid test append data,"\
"invalid test append data,invalid test append data,invalid test append data,invalid test append data,"\
"invalid test append data,invalid test append data,invalid test append data,invalid test append data,"\
"invalid test append data,invalid test append data,invalid test append data,invalid test append data,"\
"invalid test append data,invalid test append data,invalid test append data,invalid test append data,"\
"invalid test append data,invalid test append data,invalid test append data,invalid test append data,";
int32_t send_len = strlen(http_get);
int32_t send_result = sysdep->core_sysdep_network_send(network_hd, (uint8_t*)http_get, send_len, cfg->send_timeout_ms, NULL);
if(send_result != send_len) {
DEBUG_INFO("network send length test error:%d, send [%d], result [%d]", TEST_ERR_NETWORK, send_len, send_result);
ret = TEST_ERR_NETWORK;
}
sysdep->core_sysdep_network_deinit(&network_hd);
return ret;
}
sysdep_test_result_t network_test(aiot_sysdep_portfile_t* sysdep)
{
sysdep_test_result_t ret;
sysdep->core_sysdep_sleep(100);
network_cfg_t cfg;
cfg.host = "www.aliyun.com";
cfg.port = 80;
cfg.connect_timeout_ms = 5000;
cfg.recv_timeout_ms = 5000;
cfg.recv_buf_len = 1024;
cfg.send_timeout_ms = 5000;
cfg.cycle_test_times = 10;
ret = network_tcp_test(sysdep, &cfg);
if(ret != TEST_SUCCESS) {
return ret;
}
ret = network_tcp_send_length_test(sysdep,&cfg);
if(ret != TEST_SUCCESS){
return ret;
}
ret = network_tcp_cycle_test(sysdep, &cfg);
return ret;
}
static void *enter_sleep(void* user_data)
{
task_handler_input_t* user = (task_handler_input_t*)user_data;
time_cfg_t* tm = user->user_data;
DEBUG_INFO("%s enter wanna sleep: %"PRIu64"ms", user->name, tm->sleep_ms);
user->sysdep->core_sysdep_sleep(tm->sleep_ms);
tm->sleep_end = 1;
return 0;
}
sysdep_test_result_t time_sleep_test(aiot_sysdep_portfile_t* sysdep)
{
uint64_t start,stop;
time_cfg_t time_cfg;
sysdep_test_result_t ret= TEST_SUCCESS;
time_cfg_t* sleep1 = NULL;
time_cfg_t* sleep2 = NULL;
task_handler_input_t input1;
task_handler_input_t input2;
int64_t wait_total;
uint64_t temp;
time_cfg.sleep_ms = 30 * 1000;
DEBUG_INFO("sleep %llu ms test",(long long unsigned int)time_cfg.sleep_ms);
start = sysdep->core_sysdep_time();
sysdep->core_sysdep_sleep(time_cfg.sleep_ms);
stop = sysdep->core_sysdep_time();
/* 睡眠异常 */
if(stop - start < time_cfg.sleep_ms) {
DEBUG_INFO("sleep test error: %d",TEST_ERR_SLEEP);
ret = TEST_ERR_SLEEP;
goto end;
}
/* 睡眠时间超过10% */
if(stop - start > (uint64_t)(time_cfg.sleep_ms * 1.1)) {
DEBUG_INFO("sleep test error: %d",TEST_ERR_SLEEP);
ret = TEST_ERR_SLEEP;
goto end;
}
/**
* 并发睡眠测试,两个并发睡眠消耗的时间应小于两次睡眠时间之和
*/
sleep1 = sysdep->core_sysdep_malloc(sizeof(time_cfg_t),"");
sleep2 = sysdep->core_sysdep_malloc(sizeof(time_cfg_t),"");
if(sleep1 == NULL || sleep2 == NULL){
ret = TEST_ERR_SLEEP;
goto end;
}
sleep1->sleep_ms = 10 * 1000;
sleep1->sleep_end = 0;
sleep2->sleep_ms = 10 * 1000;
sleep2->sleep_end = 0;
input1.name = "sleep_test_task_1";
input1.sysdep = sysdep;
input1.user_data = sleep1;
input2.name = "sleep_test_task_2";
input2.sysdep = sysdep;
input2.user_data = sleep2;
start = sysdep->core_sysdep_time();
task_start(enter_sleep,&input1);
task_start(enter_sleep,&input2);
/**
* 等待睡眠结束
*/
wait_total = sleep1->sleep_ms + sleep2->sleep_ms + 100;
while((sleep1->sleep_end == 0 || sleep2->sleep_end == 0) && wait_total-- > 0) {
sysdep->core_sysdep_sleep(1);
}
/**
* 检查所有线程是否都退出
*/
while (sleep1->sleep_end == 0 || sleep2->sleep_end == 0){
sysdep->core_sysdep_sleep(1);
}
/* 两个线程应该在规定时间内退出 */
if(wait_total < 0) {
DEBUG_INFO("concurrent sleep test error: wait sleep timeout fail");
ret = TEST_ERR_SLEEP;
goto end;
}
/* 总睡眠时间应小于各睡眠时间之和 */
stop = sysdep->core_sysdep_time();
temp = sleep1->sleep_ms + sleep2->sleep_ms;
if(stop - start >= temp) {
DEBUG_INFO("sleep %"PRIu64"ms start:[%"PRIu64"] stop:[%"PRIu64"]\r\n unexpected ", sleep1->sleep_ms, start, stop);
ret = TEST_ERR_SLEEP;
}
else {
DEBUG_INFO("sleep %"PRIu64"ms start:[%"PRIu64"] stop:[%"PRIu64"] expected ", sleep1->sleep_ms, start, stop);
ret = TEST_SUCCESS;
}
end:
if(sleep1 != NULL)
sysdep->core_sysdep_free(sleep1);
if(sleep2 != NULL)
sysdep->core_sysdep_free(sleep2);
return ret;
}
static void* mutex_synchronize_test(void* user_data)
{
task_handler_input_t* input = (task_handler_input_t*)user_data;
mutex_cfg_t* cfg = input->user_data;
while(1) {
input->sysdep->core_sysdep_mutex_lock(cfg->mutex);
if(cfg->value < 0) {
input->sysdep->core_sysdep_mutex_unlock(cfg->mutex);
break;
}
cfg->value++;
input->sysdep->core_sysdep_mutex_unlock(cfg->mutex);
input->sysdep->core_sysdep_sleep(cfg->timeout_ms);
}
return NULL;
}
/**
* 互斥锁测试
*/
sysdep_test_result_t mutex_test(aiot_sysdep_portfile_t* sysdep)
{
sysdep_test_result_t ret;
void* mutex = NULL;
mutex_cfg_t *mutex_cfg_2 = NULL;
task_handler_input_t *input_1 = NULL;
task_handler_input_t *input_2 = NULL;
int32_t wait_ms = 3000;
/**
* 互斥锁申请释放及内存泄漏测试
*/
for(int i = 0; i < 1000; i++) {
mutex = sysdep->core_sysdep_mutex_init();
sysdep->core_sysdep_mutex_lock(mutex);
sysdep->core_sysdep_mutex_unlock(mutex);
sysdep->core_sysdep_mutex_deinit(&mutex);
}
if( TEST_SUCCESS != heap_test(sysdep)) {
DEBUG_INFO("[MUTEX_TEST.CYCLE] test failed\r\n");
return ret = TEST_ERR_MUTEX;
}
/**
* 互斥锁功能测试
*/
mutex_cfg_t *mutex_cfg_1 = sysdep->core_sysdep_malloc(sizeof(mutex_cfg_t), "");
if(mutex_cfg_1 == NULL) {
DEBUG_INFO("mutex test but malloc fail");
ret = TEST_ERR_MUTEX;
goto end;
}
mutex_cfg_1->mutex = sysdep->core_sysdep_mutex_init();
if(mutex_cfg_1->mutex == NULL) {
ret = TEST_ERR_MALLOC;
DEBUG_INFO("[MUTEX_TEST]malloc fail:%d", ret);
goto end;
}
input_1 = sysdep->core_sysdep_malloc(sizeof(task_handler_input_t), "");
if(input_1 == NULL){
DEBUG_INFO("mutex test but malloc fail");
ret = TEST_ERR_MALLOC;
goto end;
}
input_1->name = "mutex_test_task1";
input_1->sysdep = sysdep;
input_1->user_data = mutex_cfg_1;
mutex_cfg_1->timeout_ms = 100;
mutex_cfg_1->value = 0;
task_start(mutex_synchronize_test,input_1);
mutex_cfg_2 = sysdep->core_sysdep_malloc(sizeof(mutex_cfg_t), "");
if(mutex_cfg_2 == NULL) {
ret = TEST_ERR_MALLOC;
DEBUG_INFO("[MUTEX_TEST]malloc fail:%d", ret);
goto end;
}
mutex_cfg_2->mutex = sysdep->core_sysdep_mutex_init();
if(mutex_cfg_2->mutex == NULL) {
ret = TEST_ERR_MALLOC;
DEBUG_INFO("[MUTEX_TEST]malloc fail:%d", ret);
goto end;
}
input_2 = sysdep->core_sysdep_malloc(sizeof(task_handler_input_t), "");
input_2->name = "mutex_test_task2";
input_2->sysdep = sysdep;
input_2->user_data = mutex_cfg_2;
mutex_cfg_2->timeout_ms = 100;
mutex_cfg_2->value = 0;
task_start(mutex_synchronize_test,input_2);
sysdep->core_sysdep_sleep(wait_ms);
/**
* 对mutex_cfg_1->mutex加锁mutex_test_task1会被锁住mutex_cfg_1->value应该不会发生变化
*/
int32_t v1,v2;
DEBUG_INFO("mutex lock task1, unlock task2 %d ms", wait_ms);
sysdep->core_sysdep_mutex_lock(mutex_cfg_1->mutex);
v1 = mutex_cfg_1->value;
v2 = mutex_cfg_2->value;
sysdep->core_sysdep_sleep(wait_ms);
int32_t v1_,v2_;
v1_ = mutex_cfg_1->value;
v2_ = mutex_cfg_2->value;
DEBUG_INFO("task1 value [%d --> %d], task2 value [%d --> %d] ", v1, v1_, v2, v2_);
if(v1 != v1_ || v2 == v2_) {
DEBUG_INFO("mutex test failed ");
ret = TEST_ERR_MUTEX;
goto end;
}
sysdep->core_sysdep_mutex_unlock(mutex_cfg_1->mutex);
/**
* 对mutex_cfg_2->mutex加锁mutex_test_task2会被锁住mutex_cfg_2->value应该不会发生变化
*/
DEBUG_INFO("unlock task1, lock task2 %d ms", wait_ms);
sysdep->core_sysdep_mutex_lock(mutex_cfg_2->mutex);
v1 = mutex_cfg_1->value;
v2 = mutex_cfg_2->value;
sysdep->core_sysdep_sleep(wait_ms);
v1_ = mutex_cfg_1->value;
v2_ = mutex_cfg_2->value;
DEBUG_INFO("task1 value [%d --> %d], task2 value [%d --> %d] ", v1, v1_, v2, v2_);
if(v1 == v1_ || v2 != v2_) {
DEBUG_INFO("mutex test failed");
ret = TEST_ERR_MUTEX;
goto end;
}
sysdep->core_sysdep_mutex_unlock(mutex_cfg_2->mutex);
/**
* mutex_cfg_2->mutex不加锁mutex_cfg_1->mutex不加锁对应value应该正常变化
*/
DEBUG_INFO("unlock task1, lock task2 %d ms\r\n", wait_ms);
v1 = mutex_cfg_1->value;
v2 = mutex_cfg_2->value;
sysdep->core_sysdep_sleep(wait_ms);
v1_ = mutex_cfg_1->value;
v2_ = mutex_cfg_2->value;
DEBUG_INFO("task1 value [%d --> %d], task2 value [%d --> %d]\r\n", v1, v1_, v2, v2_);
if(v1 == v1_ || v2 == v2_) {
DEBUG_INFO("[MUTEX_TEST.UNLOCK] test failed \r\n");
ret = TEST_ERR_MUTEX;
goto end;
}
end:
/**
* 线程退出,回归
*/
if(mutex_cfg_1 != NULL) {
mutex_cfg_1->value = -10;
sysdep->core_sysdep_sleep(500);
if(mutex_cfg_1->mutex != NULL){
sysdep->core_sysdep_mutex_deinit(&(mutex_cfg_1->mutex));
}
sysdep->core_sysdep_free(mutex_cfg_1);
}
if(mutex_cfg_2 != NULL){
mutex_cfg_2->value = -10;
sysdep->core_sysdep_sleep(500);
if(mutex_cfg_2->mutex != NULL){
sysdep->core_sysdep_mutex_deinit(&(mutex_cfg_2->mutex));
}
sysdep->core_sysdep_free(mutex_cfg_2);
}
if(input_1 != NULL){
sysdep->core_sysdep_free(input_1);
}
if(input_2 != NULL){
sysdep->core_sysdep_free(input_2);
}
return TEST_SUCCESS;
}
typedef struct {
char* name;
sysdep_test_func func;
} sysdep_test_suite;
/**
* 测试项列表
*/
sysdep_test_suite test_list[]= {
{"RANDOM_TEST ", random_test},
{"HEAP_TEST ", heap_test},
{"TIME_TEST ", time_sleep_test},
{"NETWORK_TEST", network_test},
{"MUTEX_TEST ", mutex_test},
};
/**
* sysdep的接口实现包含系统时间、内存管理、网络、锁、随机数、等接口实现
*/
extern aiot_sysdep_portfile_t g_aiot_sysdep_portfile;
int main(int argc, char *argv[])
{
aiot_sysdep_portfile_t *sysdep = &g_aiot_sysdep_portfile;
int32_t size = sizeof(test_list) / sizeof(test_list[0]);
sysdep_test_result_t ret = TEST_SUCCESS;
DEBUG_INFO("TOTAL TEST START");
for(int32_t i = 0; i < size; i++) {
DEBUG_INFO("TEST [%d/%d] [%s] .....................................[START]", i + 1, size, test_list[i].name);
ret = (test_list[i].func)(sysdep);
if(TEST_SUCCESS != ret) {
DEBUG_INFO("TEST [%d/%d] [%s] .....................................[FAILED] [%s]", i + 1, size, test_list[i].name, result_string[ret]);
break;
} else {
DEBUG_INFO("TEST [%d/%d] [%s] .....................................[SUCCESS]", i + 1, size, test_list[i].name);
}
}
if(ret == TEST_SUCCESS) {
DEBUG_INFO("TOTAL TEST SUCCESS");
} else {
DEBUG_INFO("TOTAL TEST FAILED %s", result_string[ret]);
}
return 0;
}

View File

@@ -0,0 +1,520 @@
/*
* 这个例程适用于`Linux`这类支持pthread的POSIX设备, 它演示了用SDK配置MQTT参数并建立连接, 之后创建2个线程
*
* + 一个线程用于保活长连接
* + 一个线程用于接收消息, 并在有消息到达时进入默认的数据回调, 在连接状态变化时进入事件回调
*
* 接着在MQTT连接上发送TASK查询请求, 如果云平台的回应报文到达, 从接收线程会调用TASK消息处理的回调函数, 把对时后的本地时间打印出来
*
* 需要用户关注或修改的部分, 已经用 TODO 在注释中标明
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include "aiot_state_api.h"
#include "aiot_sysdep_api.h"
#include "aiot_mqtt_api.h"
#include "aiot_task_api.h"
/* TODO: 替换为自己设备的三元组 */
char *product_key = "${YourProductKey}";
char *device_name = "${YourDeviceName}";
char *device_secret = "${YourDeviceSecret}";
/*
TODO: 替换为自己实例的接入点
对于企业实例, 或者2021年07月30日之后含当日开通的物联网平台服务下公共实例
mqtt_host的格式为"${YourInstanceId}.mqtt.iothub.aliyuncs.com"
其中${YourInstanceId}: 请替换为您企业/公共实例的Id
对于2021年07月30日之前不含当日开通的物联网平台服务下公共实例
需要将mqtt_host修改为: mqtt_host = "${YourProductKey}.iot-as-mqtt.${YourRegionId}.aliyuncs.com"
其中, ${YourProductKey}请替换为设备所属产品的ProductKey。可登录物联网平台控制台在对应实例的设备详情页获取。
${YourRegionId}:请替换为您的物联网平台设备所在地域代码, 比如cn-shanghai等
该情况下完整mqtt_host举例: a1TTmBPIChA.iot-as-mqtt.cn-shanghai.aliyuncs.com
详情请见: https://help.aliyun.com/document_detail/147356.html
*/
char *mqtt_host = "${YourInstanceId}.mqtt.iothub.aliyuncs.com";
/* 位于portfiles/aiot_port文件夹下的系统适配函数集合 */
extern aiot_sysdep_portfile_t g_aiot_sysdep_portfile;
/* 位于external/ali_ca_cert.c中的服务器证书 */
extern const char *ali_ca_cert;
static task_desc_t *g_local_task_desc = NULL;
static pthread_t g_mqtt_process_thread;
static pthread_t g_mqtt_recv_thread;
static pthread_t g_task_thread; /* 用于执行任务的线程 */
static uint8_t g_mqtt_process_thread_running = 0;
static uint8_t g_mqtt_recv_thread_running = 0;
void demo_free_local_task(task_desc_t **task);
void *demo_task_thread(void *data)
{
#if 0
while (1) {
/* TODO: 执行任务 */
}
#endif
sleep(5);
return NULL;
}
/* 日志回调函数, SDK的日志会从这里输出 */
static int32_t demo_state_logcb(int32_t code, char *message)
{
printf("%s", message);
return 0;
}
/* MQTT事件回调函数, 当网络连接/重连/断开时被触发, 事件定义见core/aiot_mqtt_api.h */
void demo_mqtt_event_handler(void *handle, const aiot_mqtt_event_t *event, void *userdata)
{
switch (event->type) {
/* SDK因为用户调用了aiot_mqtt_connect()接口, 与mqtt服务器建立连接已成功 */
case AIOT_MQTTEVT_CONNECT: {
printf("AIOT_MQTTEVT_CONNECT\n");
}
break;
/* SDK因为网络状况被动断连后, 自动发起重连已成功 */
case AIOT_MQTTEVT_RECONNECT: {
printf("AIOT_MQTTEVT_RECONNECT\n");
}
break;
/* SDK因为网络的状况而被动断开了连接, network是底层读写失败, heartbeat是没有按预期得到服务端心跳应答 */
case AIOT_MQTTEVT_DISCONNECT: {
char *cause = (event->data.disconnect == AIOT_MQTTDISCONNEVT_NETWORK_DISCONNECT) ? ("network disconnect") :
("heartbeat disconnect");
printf("AIOT_MQTTEVT_DISCONNECT: %s\n", cause);
}
break;
default: {
}
}
}
/* MQTT默认消息处理回调, 当SDK从服务器收到MQTT消息时, 且无对应用户回调处理时被调用 */
void demo_mqtt_default_recv_handler(void *handle, const aiot_mqtt_recv_t *packet, void *userdata)
{
switch (packet->type) {
case AIOT_MQTTRECV_HEARTBEAT_RESPONSE: {
printf("heartbeat response\n");
}
break;
case AIOT_MQTTRECV_SUB_ACK: {
printf("suback, res: -0x%04X, packet id: %d, max qos: %d\n",
-packet->data.sub_ack.res, packet->data.sub_ack.packet_id, packet->data.sub_ack.max_qos);
}
break;
case AIOT_MQTTRECV_PUB: {
printf("pub, qos: %d, topic: %.*s\n", packet->data.pub.qos, packet->data.pub.topic_len, packet->data.pub.topic);
printf("pub, payload: %.*s\n", packet->data.pub.payload_len, packet->data.pub.payload);
}
break;
case AIOT_MQTTRECV_PUB_ACK: {
printf("puback, packet id: %d\n", packet->data.pub_ack.packet_id);
}
break;
default: {
}
}
}
/* 执行aiot_mqtt_process的线程, 包含心跳发送和QoS1消息重发 */
void *demo_mqtt_process_thread(void *args)
{
int32_t res = STATE_SUCCESS;
while (g_mqtt_process_thread_running) {
res = aiot_mqtt_process(args);
if (res == STATE_USER_INPUT_EXEC_DISABLED) {
break;
}
sleep(1);
}
return NULL;
}
/* 执行aiot_mqtt_recv的线程, 包含网络自动重连和从服务器收取MQTT消息 */
void *demo_mqtt_recv_thread(void *args)
{
int32_t res = STATE_SUCCESS;
while (g_mqtt_recv_thread_running) {
res = aiot_mqtt_recv(args);
if (res < STATE_SUCCESS) {
if (res == STATE_USER_INPUT_EXEC_DISABLED) {
break;
}
sleep(1);
}
}
return NULL;
}
int32_t demo_mqtt_start(void **handle)
{
int32_t res = STATE_SUCCESS;
void *mqtt_handle = NULL;
uint16_t port = 1883; /* 无论设备是否使用TLS连接阿里云平台, 目的端口都是443 */
aiot_sysdep_network_cred_t cred; /* 安全凭据结构体, 如果要用TLS, 这个结构体中配置CA证书等参数 */
/* 配置SDK的底层依赖 */
aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
/* 配置SDK的日志输出 */
aiot_state_set_logcb(demo_state_logcb);
/* 创建SDK的安全凭据, 用于建立TLS连接 */
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA; /* 使用RSA证书校验MQTT服务端 */
cred.max_tls_fragment = 16384; /* 最大的分片长度为16K, 其它可选值还有4K, 2K, 1K, 0.5K */
cred.sni_enabled = 1; /* TLS建连时, 支持Server Name Indicator */
cred.x509_server_cert = ali_ca_cert; /* 用来验证MQTT服务端的RSA根证书 */
cred.x509_server_cert_len = strlen(ali_ca_cert); /* 用来验证MQTT服务端的RSA根证书长度 */
/* 创建1个MQTT客户端实例并内部初始化默认参数 */
mqtt_handle = aiot_mqtt_init();
if (mqtt_handle == NULL) {
printf("aiot_mqtt_init failed\n");
return -1;
}
/* TODO: 如果以下代码不被注释, 则例程会用TCP而不是TLS连接云平台 */
/*
{
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_NONE;
}
*/
/* 配置MQTT服务器地址 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_HOST, (void *)mqtt_host);
/* 配置MQTT服务器端口 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PORT, (void *)&port);
/* 配置设备productKey */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PRODUCT_KEY, (void *)product_key);
/* 配置设备deviceName */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_NAME, (void *)device_name);
/* 配置设备deviceSecret */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_SECRET, (void *)device_secret);
/* 配置网络连接的安全凭据, 上面已经创建好了 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_NETWORK_CRED, (void *)&cred);
/* 配置MQTT默认消息接收回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_RECV_HANDLER, (void *)demo_mqtt_default_recv_handler);
/* 配置MQTT事件回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_EVENT_HANDLER, (void *)demo_mqtt_event_handler);
/* 与服务器建立MQTT连接 */
res = aiot_mqtt_connect(mqtt_handle);
if (res < STATE_SUCCESS) {
/* 尝试建立连接失败, 销毁MQTT实例, 回收资源 */
aiot_mqtt_deinit(&mqtt_handle);
printf("aiot_mqtt_connect failed: -0x%04X\n\r\n", -res);
printf("please check variables like mqtt_host, produt_key, device_name, device_secret in demo\r\n");
return -1;
}
/* 创建一个单独的线程, 专用于执行aiot_mqtt_process, 它会自动发送心跳保活, 以及重发QoS1的未应答报文 */
g_mqtt_process_thread_running = 1;
res = pthread_create(&g_mqtt_process_thread, NULL, demo_mqtt_process_thread, mqtt_handle);
if (res < 0) {
printf("pthread_create demo_mqtt_process_thread failed: %d\n", res);
aiot_mqtt_deinit(&mqtt_handle);
return -1;
}
/* 创建一个单独的线程用于执行aiot_mqtt_recv, 它会循环收取服务器下发的MQTT消息, 并在断线时自动重连 */
g_mqtt_recv_thread_running = 1;
res = pthread_create(&g_mqtt_recv_thread, NULL, demo_mqtt_recv_thread, mqtt_handle);
if (res < 0) {
printf("pthread_create demo_mqtt_recv_thread failed: %d\n", res);
g_mqtt_process_thread_running = 0;
pthread_join(g_mqtt_process_thread, NULL);
aiot_mqtt_deinit(&mqtt_handle);
return -1;
}
*handle = mqtt_handle;
return 0;
}
int32_t demo_mqtt_stop(void **handle)
{
int32_t res = STATE_SUCCESS;
void *mqtt_handle = NULL;
mqtt_handle = *handle;
g_mqtt_process_thread_running = 0;
g_mqtt_recv_thread_running = 0;
pthread_join(g_mqtt_process_thread, NULL);
pthread_join(g_mqtt_recv_thread, NULL);
/* 断开MQTT连接 */
res = aiot_mqtt_disconnect(mqtt_handle);
if (res < STATE_SUCCESS) {
aiot_mqtt_deinit(&mqtt_handle);
printf("aiot_mqtt_disconnect failed: -0x%04X\n", -res);
return -1;
}
/* 销毁MQTT实例 */
res = aiot_mqtt_deinit(&mqtt_handle);
if (res < STATE_SUCCESS) {
printf("aiot_mqtt_deinit failed: -0x%04X\n", -res);
return -1;
}
return 0;
}
static void _demo_deep_free_base(char *in)
{
if (NULL == in) {
return;
}
free(in);
}
static void *_demo_deep_copy_base(char *in)
{
uint32_t len = 0;
void *tmp = NULL;
if (NULL == in) {
return NULL;
}
len = strlen(in) + 1;
tmp = (char *)malloc(len);
if (NULL == tmp) {
return NULL;
}
memset(tmp, 0, len);
memcpy(tmp, in, strlen(in));
return tmp;
}
void demo_copy_task_to_local_task(task_desc_t **dst_desc, const task_desc_t *in_desc)
{
if (NULL == *dst_desc) {
*dst_desc = (task_desc_t *)malloc(sizeof(task_desc_t));
}
(*dst_desc)->status = in_desc->status;
(*dst_desc)->progress = in_desc->progress;
(*dst_desc)->handle = in_desc->handle;
(*dst_desc)->task_id = _demo_deep_copy_base(in_desc->task_id);
(*dst_desc)->job_document = _demo_deep_copy_base(in_desc->job_document);
(*dst_desc)->sign_method = _demo_deep_copy_base(in_desc->sign_method);
(*dst_desc)->sign = _demo_deep_copy_base(in_desc->sign);
(*dst_desc)->document_file_url = _demo_deep_copy_base(in_desc->document_file_url);
(*dst_desc)->status_details = _demo_deep_copy_base(in_desc->status_details);
}
void demo_free_local_task(task_desc_t **task)
{
if (NULL != *task) {
_demo_deep_free_base((*task)->task_id);
_demo_deep_free_base((*task)->job_document);
_demo_deep_free_base((*task)->sign_method);
_demo_deep_free_base((*task)->sign);
_demo_deep_free_base((*task)->document_file_url);
_demo_deep_free_base((*task)->status_details);
free(*task);
}
*task = NULL;
}
void demo_task_recv_handler(void *handle, const aiot_task_recv_t *packet, void *userdata)
{
switch (packet->type) {
case AIOT_TASKRECV_NOTIFY: {
const task_desc_t *in_desc = &(packet->data.notify);
printf("revice task notify, task_id:[%s],status:[%d],job_document[%s],document_file_url:[%s],\
sign_method:[%s],sign[%s]\r\n",
in_desc->task_id, in_desc->status, in_desc->job_document,
in_desc->document_file_url, in_desc->sign_method, in_desc->sign);
/* 1.在handle里面没有任务记录的情况下, 把云端下行的task保存到handle里面的default_task_desc字段 */
if (NULL == g_local_task_desc) {
demo_copy_task_to_local_task(&g_local_task_desc, in_desc);
/* 启动任务. 这里仅为参考实现, 用户可以根据实际情况来适配 */
int res = pthread_create(&g_task_thread, NULL, demo_task_thread, g_local_task_desc);
if (res != 0) {
printf("pthread_create demo_task_thread failed: %d\r\n", res);
} else {
/* 下载线程被设置为 detach 类型, 固件内容获取完毕后可自主退出 */
pthread_detach(g_task_thread);
}
/* 变更任务状态.TODO: 这里仅为参考实现, 任务执行完毕时, 要将状态设置为AIOT_TASK_STATUS_SUCCEEDED*/
g_local_task_desc->status = AIOT_TASK_STATUS_IN_PROGRESS;
aiot_task_update(handle, g_local_task_desc);
demo_free_local_task(&g_local_task_desc);
break;
}
/* 2.如果状态被云端设置为终态, 则在这里将本地的任务清理掉 */
if (in_desc->status == AIOT_TASK_STATUS_CANCELLED || in_desc->status == AIOT_TASK_STATUS_REMOVED
|| in_desc->status == AIOT_TASK_STATUS_TIMED_OUT) {
/* TODO: 清理本地任务, 停下线程 */
/* 如果该任务是记录在handle里面的默认任务, 则要把它的内存清理掉; 如果是记录在handle外的, 需要用户自己维护内存 */
if (NULL != g_local_task_desc && 0 == strcmp(in_desc->task_id, g_local_task_desc->task_id)) {
/* 释放本地任务内存 */
demo_free_local_task(&g_local_task_desc);
}
break;
}
/* 3.在本地已有任务记录的情况,云端可能更新了当前这个任务的描述, 需要检查一下更新的内容 */
if (in_desc->status == AIOT_TASK_STATUS_IN_PROGRESS) {
if (NULL != g_local_task_desc && 0 == strcmp(in_desc->task_id, g_local_task_desc->task_id)) {
/* TODO: 更新本地的任务描述. 用户可能要暂停当前的任务再更新, 这一点取决于用户 */
break;
}
}
/* 4.如果不是上述情况, 说明来了一个新任务. 此时用户已经有一个任务在执行了, 又来了一个任务, 用户可以在main中自己创建一个列表 */
/* 将这个列表作为userdata传进来, 并把任务记录在这个列表里面, 自己维护*/
break;
}
case AIOT_TASKRECV_GET_DETAIL_REPLY: {
const task_get_detail_reply_t *in_reply = &(packet->data.get_detail_reply);
printf("revice task get detail, code:[%d]\r\n", in_reply->code);
if (200 == in_reply->code) {
printf("revice task get detail reply, task_id:[%s],status:[%d]\r\n",
in_reply->task.task_id, in_reply->task.status);
if (in_reply->task.status != AIOT_TASK_STATUS_NOT_FOUND) {
printf("job_document[%s],document_file_url:[%s], sign_method:[%s],sign[%s]\r\n",
in_reply->task.job_document, in_reply->task.document_file_url,
in_reply->task.sign_method, in_reply->task.sign);
task_desc_t task;
memset(&task, 0, sizeof(task));
memcpy(&task, &(in_reply->task), sizeof(task));
/* TODO: 执行任务, 可以通过起线程的方式 */
/* 变更任务状态. TODO: 这里仅为参考实现, 用户可以根据实际情况来适配. 任务执行完毕时, 要将状态设置为AIOT_TASK_STATUS_SUCCEEDED */
task.status = AIOT_TASK_STATUS_IN_PROGRESS;
task.progress = 88;
aiot_task_update(handle, &task);
}
}
break;
}
case AIOT_TASKRECV_GET_LIST_REPLY: {
const task_get_list_reply_t *in_reply = &(packet->data.get_list_reply);
printf("revice task get list reply, number:[%d]\r\n", in_reply->number);
for (int i = 0; i < in_reply->number; i++) {
printf("task list[%d]:task_id[%s],status[%d]\r\n", i, in_reply->tasks[i].task_id,
in_reply->tasks[i].status);
aiot_task_get_task_detail(handle, in_reply->tasks[i].task_id);
}
break;
}
case AIOT_TASKRECV_UPDATE_REPLY: {
const task_update_reply_t *update_reply = &(packet->data.update_reply);
printf("revice task update reply, code:[%d]\r\n", update_reply->code);
if (200 == update_reply->code) {
printf("revice task update reply, task_id:[%s]\r\n", update_reply->task_id);
}
if (71012 == update_reply->code) {
printf("aiot_task_update task's status_details value must be json format\r\n");
}
/* TODO */
break;
}
default:
/* TODO */
break;
}
}
int main(int argc, char *argv[])
{
int32_t res = STATE_SUCCESS;
void *mqtt_handle = NULL, *task_handle = NULL;
/* 建立MQTT连接, 并开启保活线程和接收线程 */
res = demo_mqtt_start(&mqtt_handle);
if (res < 0) {
printf("demo_mqtt_start failed\n");
return -1;
}
/* 创建1个task客户端实例并内部初始化默认参数 */
task_handle = aiot_task_init();
if (task_handle == NULL) {
demo_mqtt_stop(&mqtt_handle);
printf("aiot_task_init failed\n");
return -1;
}
res = aiot_task_setopt(task_handle, AIOT_TASKOPT_MQTT_HANDLE, mqtt_handle);
if (res < STATE_SUCCESS) {
printf("aiot_task_setopt AIOT_TASKOPT_MQTT_HANDLE failed, res: -0x%04X\n", -res);
aiot_task_deinit(&task_handle);
demo_mqtt_stop(&mqtt_handle);
return -1;
}
res = aiot_task_setopt(task_handle, AIOT_TASKOPT_RECV_HANDLER, demo_task_recv_handler);
if (res < STATE_SUCCESS) {
printf("aiot_task_setopt AIOT_TASKOPT_RECV_HANDLER failed, res: -0x%04X\n", -res);
aiot_task_deinit(&task_handle);
demo_mqtt_stop(&mqtt_handle);
return -1;
}
/* 发送task获取下一个可执行的任务的请求给云平台 */
res = aiot_task_get_task_detail(task_handle, NULL);
if (res < STATE_SUCCESS) {
aiot_task_deinit(&task_handle);
demo_mqtt_stop(&mqtt_handle);
return -1;
}
/* 主线程进入休眠, 等云平台的task回应到达时, 接收线程会调用 demo_task_recv_handler() */
while (1) {
sleep(1);
}
/* 销毁task实例, 一般不会运行到这里 */
res = aiot_task_deinit(&task_handle);
if (res < STATE_SUCCESS) {
demo_mqtt_stop(&mqtt_handle);
printf("aiot_task_deinit failed: -0x%04X\n", -res);
return -1;
}
/* 销毁MQTT实例, 退出线程, 一般不会运行到这里 */
res = demo_mqtt_stop(&mqtt_handle);
if (res < 0) {
printf("demo_start_stop failed\n");
return -1;
}
return 0;
}