历经千辛万苦,终于把一型一密的连接阿里生活物联网搞定了
当前网络上能找到的绝大部分连阿里平台都是基于阿里的SDK或者是通信模块的SDK,而今天我要讲的是,在模块的open方案上,不借助阿里的SDK,仅使用paho-MQTT C库在RTOS下自己去实现一型一密连接阿里生活物联网平台。
一、准备条件
1、阿里生活物联网(飞燕平台)的账号,创建好产品。
2、了解设备连接平台的基本流程,了解阿里三要素(如何使用Paho-MQTTC接入物联网平台收发消息_阿里云物联网平台-阿里云帮助中心)
3、了解一型一密动态注册,动态注册分两种,一个是免预注册,一个是预注册,可以去阿里官网(如何使用一型一密方式进行设备注册认证_阿里云物联网平台-阿里云帮助中心)查看相关资料。该文着重讲一型一密预注册。
4、了解MQTT.fx工具的使用,可以排除一些问题
5、paho-MQTT 移植,如果模块支持,这一步可以省略。相关移植方法直接参考paht-mqtt github
二、流程及注意事项
1、大致流程
(1)阿里平台上先将设备的IMEI注册好
(2)在设备端烧录设备的productKey和productSecret(所有设备都一样)
(3)设备端通过阿里的组包顺序和加密方式算出MQTT客户端的ID 用户名 密码
(4)创建SSL网络连接,然后使用(3)计算出来的MQTT客户端信息连接阿里云
(5)平台接收到连接,认证通过后会下发三要素(productKey deviceName deviceSecret),设备将三要素写到本地flash永久保存。
(6)断开注册的连接,通过三要素再次发起连接,进行订阅和发布主题
2、注意事项:
(1)如果是非量产状态,平台上有个动态注册的开关,需要开启
(2)量产后,动态注册开关可以不用开启,只要设置产品可动态注册即可
(3)设备动态注册如果成功获取三要素后,再次发起注册会失败(平台不再返回三要素),所以成功获取三要素后需要把三要素写到flash里面。下次通过三要素直接连接平台进行发布和订阅内容。如果需要测试多次注册,可以通过平台的ResetThing接口进行重置注册状态
三、相关代码
1、动态注册函数
//动态注册函数
static void ALI_Mqtt_regist(void *argv)
{
MQTTClient regclient = {0};//定义MQTT客户端
Network network = {0}; //定义网络参数
unsigned char sendbuf[64] = {0}, readbuf[128] = {0};//mqtt 发送和接收buf
int rc = 0, count = 0;
/* invoke aiotMqttSign to generate mqtt connect parameters */
char clientId[150] = {0};
char username[65] = {0};
char password[65] = {0};
printf("++++++++++++++++++++start ALI_Mqtt_regist\n");
char* device_name = user_app_cfg.imei; //使用模块imei作为deviceName
char* product_key = user_app_cfg.productInfo.productKey; //"a1*******";阿里平台上的productKey,user_app_cfg是自定义的一个结构体,里面存放产品信息
char* product_secret = user_app_cfg.productInfo.productSecret; //"w********";阿里平台上的productSecret
int product_secret_len = strlen(product_secret);
char content[128] = {0};//按阿里的数据结果拼接需要加密的密码
sprintf(content,"deviceName%sproductKey%srandom123",device_name,product_key);
char mqttPassword[90] = {0};//记录通过sha256加密后的MQTT密码密码
uint8_t passwordtemp[32] = {0};//临时变量,加密后的密码的32位的,需要转成64位16进制ASCII
//utils_hmac_sha256函数在阿里提供的aiot_mqtt_sign.c文件中可以找到,可以自己添加头文件方便引用
utils_hmac_sha256((const char*)content,strlen((const char*)content),product_secret,product_secret_len,(char *)passwordtemp);
for(int i=0;i<sizeof(passwordtemp);i++)
sprintf(mqttPassword+strlen(mqttPassword),"%02X",passwordtemp[i]);
char mqttClientId[128] = {0};//需要按阿里的拼接顺序组件
sprintf((char *)mqttClientId,"%s|securemode=2,authType=register,random=123,signmethod=hmacsha256|",device_name);//mode=-2时,type=regnwl,表示免预注册;mode=2时type=register,表示预注册。
char mqttUserName[64]={0};
sprintf((char *)mqttUserName,"%s&%s",device_name,product_key);
printf("mqttClientID:%s\n",mqttClientId);
printf("mqttUserName:%s\n",mqttUserName);
printf("mqttPassword:%s\n",mqttPassword);
char serip[128] = {0};
sprintf(serip,"%s.iot-as-mqtt.cn-shanghai.aliyuncs.com",user_app_cfg.productInfo.productKey);
//注意,动态注册的MQTT连接必须是SSL加密的,如果不加密,无法正常建立连接。
//另外加密证书rootCA_path,可以使用阿里的证书,也可以用其他的证书但会告警。
//通信模块的加密方式不一样,配置的SSLConfi参数也不一样,具体看模块。
SSLConfig SSLConfig = {
.en = SSL_ENABLE,
#if SSL_ENABLE
.profileIdx = PROFILE_IDX,
.serverName = serip,
.serverPort = SERVER_PORT,
.protocol = 0,
.dbgLevel = 0,
.sessionReuseEn = 0,
.vsn = SSL_VSN_ALL,
.verify = SSL_VERIFY_MODE,
.cert.from = SSL_CERT_FROM_BUF,
.cert.path.rootCA = rootCA_path,
.cert.path.clientKey = NULL,
.cert.path.clientCert = NULL,
.cipherList = SSL_CIPHER_LIST,
.CTRDRBGSeed.data = NULL,
.CTRDRBGSeed.len = 0
#endif
};
int sta = 1;
char* address = serip;
//初始化连接
MQTTPacket_connectData connectData = MQTTPacket_connectData_initializer;
connectData.willFlag = 0;
connectData.MQTTVersion = 3;
connectData.clientID.cstring = mqttClientId;
connectData.username.cstring = mqttUserName;
connectData.password.cstring = mqttPassword;
connectData.keepAliveInterval = 300;//阿里最大支持300sKeepAlike,动态注册的连接可以不设置保活
connectData.cleansession = 1;
int ret = 0;
int cnt = 0;
while(1)
{
switch (sta)
{
case M_LOGIC_INIT_NETWORK:
NetworkInit(&network, &SSLConfig, PROFILE_IDX);//初始化网络
MQTTClientInit(®client, &network, 30000, sendbuf, sizeof(sendbuf), readbuf, sizeof(readbuf));//初始化MQTT客户端
regclient.defaultMessageHandler = messageArrived;//指定数据接收的回调函数
if ((rc = NetworkConnect(&network, address, SERVER_PORT)) != 0)
{
printf("Return code is %d", rc);
MQTTClientDeinit(®client);
}
else
sta = M_LOGIC_CONNECT_SERVER;
break;
case M_LOGIC_CONNECT_SERVER:
if ((rc = MQTTConnect(®client, &connectData)) != 0)
{
printf("Return code is %d", rc);
NetworkDisconnect(&network);
MQTTClientDeinit(®client);
sta = M_LOGIC_INIT_NETWORK;
rtos_sleep(10);
}
else
{
printf("MQTT Connected");
cnt = 0;
if ((rc = MQTTStartTask(®client)) != 0)
{
mqtt_exam_log("Return code is %d", rc);
NetworkDisconnect(&network);
MQTTClientDeinit(®client);
sta = M_LOGIC_INIT_NETWORK;
}
else
{
sta = M_LOGIC_WAIT;
}
}
break;
case M_LOGIC_WAIT:
{
ret = 1;
}
break;
default:
break;
}
if(ret == 0)
{
rtos_sleep(1);
continue;
}
//永久等待消息,当MQTT回调函数接收到注册信息后,给消息队列发U_EVENT_REGDATA事件
memset(&emsg,0,sizeof(UDE_QUEUE_MSG_INFO_T));
rtos_queue_wait(mqtt_event_queue, &emsg, sizeof(UDE_QUEUE_MSG_INFO_T), QL_WAIT_FOREVER);
switch (emsg.eType)
{
case U_EVENT_REGDATA:
{
//已经获取注册信息,断开当前连接,发起新连接
printf("U_EVENT_REGDATA...\n");
NetworkDisconnect(&network);
MQTTClientDeinit(®client);
goto REG_END;
}
break;
default:
break;
}
}
REG_END:
printf("REG_END\n");
}
2、MQTT回调函数
//MQTT回调接口
static void messageArrived(MessageData* data)
{
printf("recv topic:%s\n", data->topicName->lenstring.data);
printf("recv paloay:%s\n", data->message->payload);
//如果是动态预注册,数据通过/ext/register返回,如果是免预注册则是/ext/regnwl
if(strstr(data->topicName->lenstring.data,"/ext/register") != NULL)
{
cJSON* cjson_all = NULL;
cJSON* cjson_child = NULL;
cjson_all = cJSON_Parse((char*) data->message->payload);
cjson_child = cJSON_GetObjectItem(cjson_all, "productKey");
if(cjson_child != NULL)
{
printf("productKey:%s\n",cjson_child->valuestring);
}
cjson_child = cJSON_GetObjectItem(cjson_all, "deviceName");
if(cjson_child != NULL)
{
printf("deviceName:%s\n",cjson_child->valuestring);
}
cjson_child = cJSON_GetObjectItem(cjson_all, "deviceSecret");
if(cjson_child != NULL)
{
printf("deviceSecret:%s\n",cjson_child->valuestring);
}
//需要保存3要素及注册状态
write_id_key2Flash();
write_product2Flash(2);
cJSON_Delete(cjson_all);
//给消息队列发信号,通知已经注册成功
UDE_QUEUE_MSG_INFO_T emsg;
emsg.etime = time(NULL);
emsg.eType = U_EVENT_REGDATA;
emsg.data = ®
ql_rtos_queue_release(mqtt_event_queue, sizeof(UDE_QUEUE_MSG_INFO_T), &emsg, QL_NO_WAIT);
}
}
3、动态注册成功后,无需SSL加密,直接通过3要素连接阿里平台进行数据通信
static void MQTTEchoTask(void *argv)
{
UDE_QUEUE_MSG_INFO_T emsg;
MQTTClient client = {0};
Network network = {0};
unsigned char sendbuf[512] = {0}, readbuf[512] = {0};//最好使用动态内存,不然占很大的ram
int rc = 0, count = 0;
/* invoke aiotMqttSign to generate mqtt connect parameters */
char clientId[150] = {0};
char username[65] = {0};
char password[65] = {0};
char serverip[128] = {0};
sprintf(serverip,"%s.iot-as-mqtt.cn-shanghai.aliyuncs.com",user_app_cfg.productKey);
//通过注册获取的三要素,及阿里提供的aiot_mqtt_sign.c文件里的aiotMqttSign计算出MQTT的ID、用户名、密码
if ((rc = aiotMqttSign(user_app_cfg.productKey, user_app_cfg.deviceName, user_app_cfg.deviceSecretKey, clientId, username, password) < 0)) {
printf("aiotMqttSign -%0x4x\n", -rc);
return;
}
printf("clientid: %s\n", clientId);
printf("username: %s\n", username);
printf("password: %s\n", password);
SSLConfig SSLConfig = {
.en = 0,
};
char* address = serverip;
MQTTPacket_connectData connectData = MQTTPacket_connectData_initializer;
connectData.willFlag = 0;
connectData.MQTTVersion = 3;
connectData.clientID.cstring = clientId;
connectData.username.cstring = username;
connectData.password.cstring = password;
connectData.keepAliveInterval = 300;
connectData.cleansession = 1;
int ret = 0;
int sta = 0;
while(1)
{
switch (sta)
{
case M_LOGIC_INIT_NETWORK:
NetworkInit(&network, &SSLConfig, PROFILE_IDX);
MQTTClientInit(&client, &network, 30000, sendbuf, sizeof(sendbuf), readbuf, sizeof(readbuf));
client.defaultMessageHandler = messageArrived;
if ((rc = NetworkConnect(&network, address, SERVER_PORT)) != 0)
{
printf("Return code is %d", rc);
MQTTClientDeinit(&client);
rtos_sleep(5);
}
else
sta = M_LOGIC_CONNECT_SERVER;
break;
case M_LOGIC_CONNECT_SERVER:
if ((rc = MQTTConnect(&client, &connectData)) != 0)
{
printf("Return code is %d", rc);
NetworkDisconnect(&network);
MQTTClientDeinit(&client);
sta = M_LOGIC_INIT_NETWORK;
}
else
{
printf("MQTT Connected");
user_app_cfg.connectSta = 1;
if ((rc = MQTTStartTask(&client)) != 0)
{
printf("Return code is %d", rc);
NetworkDisconnect(&network);
MQTTClientDeinit(&client);
sta = M_LOGIC_INIT_NETWORK;
}
else
{
sta = M_LOGIC_SUBSCRIBE_NTP;
}
}
break;
case M_LOGIC_SUBSCRIBE_NTP:
{
char topic[128] = {0};
sprintf(topic,"/ext/ntp/%s/%s/response",p_productKey,user_app_cfg.keyid.devid);
if (rc = MQTTSubscribe(&client, topic, 2, messageArrived) != 0)
{
printf("Return code is %d", rc);
rc = MQTTDisconnect(&client);
if(rc == SUCCESS)
printf("MQTT Disconnected by client");
else
printf("MQTT Disconnected failed by client");
NetworkDisconnect(&network);
MQTTClientDeinit(&client);
}
else
{
if(user_app_cfg.timesync == 0)
sta = M_LOGIC_SYNCTIME;
else
sta = M_LOGIC_SUBSCRIBE_SER;
}
}
break;
case M_LOGIC_SYNCTIME:
{
MQTTMessage message;
mqtt_pack_msg_syncTime(&message);//封装阿里的同步时间数据
char reqtopic[128] = {0};
sprintf(reqtopic,"/ext/ntp/%s/%s/request",user_app_cfg.productKey,user_app_cfg.deviceName);
if ((rc = MQTTPublish(&client, reqtopic, &message)) != 0)
{
printf("Return code from MQTT subscribe is %d", rc);
rc = MQTTDisconnect(&client);
if(rc == SUCCESS)
printf("MQTT Disconnected by client");
else
printf("MQTT Disconnected failed by client");
NetworkDisconnect(&network);
MQTTClientDeinit(&client);
sta = M_LOGIC_INIT_NETWORK;
}
else
sta = M_LOGIC_SUBSCRIBE_SER;
}break;
case M_LOGIC_SUBSCRIBE_SER:
{
//订阅一些信息
}break;
}
if(ret==0)
{
rtos_sleep(1);
continue;
}
//永久等待消息
ql_rtos_queue_wait(mqtt_event_queue, &emsg, sizeof(UDE_QUEUE_MSG_INFO_T), QL_WAIT_FOREVER);
//解析消息数据
...
}
四、总结
1、仔细看官方指导文档
2、阿里生活物联网(飞燕平台)和阿里物联网存在一定差异
3、遇到问题多提工单,阿里客户处理速度还是挺快的