当前位置:   article > 正文

物联网实战--入门篇之(十)安卓QT--后端开发

物联网实战--入门篇之(十)安卓QT--后端开发

目录

一、项目配置

二、MQTT连接

三、数据解析

四、数据更新

五、数据发送

六、指令下发


一、项目配置

        按常规新建一个Quick空项目后,我们需要对项目内容稍微改造、规划下。

        首先根据我们的需要在.pro文件内添加必要的模块,其中quick就是qml了,core那是核心模块,必须的,network是网络模块,MQTT需要使用网络。

        然后就是为QML文件和图片文件各自建立一个资源文件,这样编译的时候会把这些资源带上,否则的话打包发布的时候你需要把QML文件和图片文件放到指定文件夹内,这在安卓里就不方便了。

        最后就是如何加载前端QML文件的问题了,如下图所示,后端通过QML加载引擎QQmlApplicationEngine把QML主文件加载进来就能显示界面了,下一行是前端到后端的设置接口名称,这样在QML文件里就可以用theMainInterface这个名称引用后端的函数了,完成开关、调速等动作。

        在新工程默认的文件里,加载前端文件这一步是在main.c文件里完成的,我们这里为了整体前后端的交互,特意建了一个MainInterface的类,在主函数中直接定义这个类的变量即可,这样整个工程结构比较清晰。

二、MQTT连接

        QT标准库里没有mqtt,需要自己单独下载GitCode - 开发者的代码家园,我的项目里已经集成了,只要右键添加进来即可,这里主要是要写个自己的MQTT管理类,把状态保活、话题订阅等任务内部处理掉,就是BaseMqtt类了,里面还有个内容是域名解析需要处理。

  1. #include "BaseMqtt.h"
  2. BaseMqtt::BaseMqtt(QObject *parent) : QObject(parent)
  3. {
  4. isConnected=false;
  5. checkTimer = new QTimer(this);
  6. checkTimer->setInterval(1*100);//心跳检测
  7. checkTimer->start();
  8. m_heartTime=0;
  9. connect(checkTimer, SIGNAL(timeout()),this,SLOT(slotCheckTimeout()));
  10. m_mqttClient=nullptr;
  11. m_hostAddress="";
  12. }
  13. BaseMqtt::~BaseMqtt(void)
  14. {
  15. qDebug()<<"~BaseMqtt, hostName="<<m_connectParam.hostName;
  16. }
  17. void BaseMqtt::slotLookUpHost(QHostInfo info)
  18. {
  19. if(info.error() == QHostInfo::NoError)
  20. {
  21. foreach (QHostAddress address, info.addresses())
  22. {
  23. m_hostAddress=address.toString();
  24. qDebug()<<m_connectParam.hostName<<" mqtt found ip= "<<m_hostAddress;
  25. break;
  26. }
  27. }
  28. }
  29. void BaseMqtt::slotCheckTimeout(void)
  30. {
  31. m_heartTime++;
  32. if(m_hostAddress.isEmpty())
  33. {
  34. if(m_heartTime%10==0)
  35. {
  36. qDebug("mqtt start dns!");
  37. QHostInfo::lookupHost(m_connectParam.hostName, this, SLOT(slotLookUpHost(QHostInfo)));
  38. }
  39. return;
  40. }
  41. if(m_mqttClient==nullptr)
  42. {
  43. if(!m_connectParam.certPath.isEmpty())//使用SSL连接
  44. {
  45. QFile file;
  46. QByteArray client_key_text, client_crt_text, root_crt_text;
  47. QString certPath=m_connectParam.certPath;
  48. QSslSocket ssl_socket;
  49. file.setFileName(certPath+"/client.key");
  50. file.open(QIODevice::ReadOnly | QIODevice::Text);
  51. client_key_text = file.readAll();
  52. file.close();
  53. file.setFileName(certPath+"/client.crt");
  54. file.open(QIODevice::ReadOnly | QIODevice::Text);
  55. client_crt_text = file.readAll();
  56. file.close();
  57. file.setFileName(certPath+"/rootCA.crt");
  58. file.open(QIODevice::ReadOnly | QIODevice::Text);
  59. root_crt_text = file.readAll();
  60. file.close();
  61. QSslKey client_key(client_key_text, QSsl::Rsa);
  62. QSslCertificate client_crt(client_crt_text);
  63. ssl_socket.setPrivateKey(client_key);
  64. ssl_socket.setLocalCertificate(client_crt);
  65. QSslConfiguration ssl_config=QSslConfiguration::defaultConfiguration();
  66. ssl_config.setCaCertificates(QSslCertificate::fromData(root_crt_text)); //QSslCertificate::fromPath(certPath+"/rootCA.crt");
  67. ssl_config.setPrivateKey(ssl_socket.privateKey());
  68. ssl_config.setLocalCertificate(ssl_socket.localCertificate());
  69. ssl_config.setPeerVerifyMode(QSslSocket::QueryPeer);
  70. ssl_config.setPeerVerifyDepth(10);
  71. ssl_config.setProtocol(QSsl::TlsV1_2);
  72. m_mqttClient = new QMQTT::Client(m_hostAddress, m_connectParam.hostPort, ssl_config, true, this);
  73. // qDebug()<<"\n###SSL PrivateKey="<<ssl_config.privateKey();
  74. // qDebug()<<"###SSL Certificate="<<ssl_config.localCertificate();
  75. // qDebug()<<"###SSL rootCA="<<ssl_config.caCertificates();
  76. // qDebug()<<"hostName="<<m_hostAddress<<", hostPort="<<m_connectParam.hostPort;
  77. // qDebug()<<"userName="<<m_connectParam.userName<<", passWord="<<m_connectParam.passWord<<", clientID="<<m_connectParam.clientID;
  78. }
  79. else//普通连接
  80. {
  81. QHostAddress host(m_hostAddress);
  82. m_mqttClient = new QMQTT::Client(host, m_connectParam.hostPort, this);
  83. }
  84. signalSlotInit();
  85. m_mqttClient->setUsername(m_connectParam.userName);
  86. m_mqttClient->setPassword(m_connectParam.passWord);
  87. m_mqttClient->setClientId(m_connectParam.clientID);
  88. m_mqttClient->setAutoReconnect(true);
  89. m_mqttClient->setCleanSession(true);
  90. m_mqttClient->setKeepAlive(30);
  91. m_mqttClient->connectToHost();
  92. }
  93. else if(this->mqttIsConnected())
  94. {
  95. for(auto iter : m_subTopicList)//订阅话题
  96. {
  97. if(iter.isSubed==false)
  98. {
  99. mqttSubscribeMessage(iter.subTopic, iter.qos);
  100. break;
  101. }
  102. }
  103. if(m_heartTime%200==0)//保持连接
  104. {
  105. // qDebug()<<"mqtt send keep";
  106. mqttPublishMessage("dev/sub/data1", QByteArray("heart"));
  107. }
  108. }
  109. else
  110. {
  111. // qDebug()<<"BaseMqtt no connected!";
  112. }
  113. }
  114. void BaseMqtt::mqttConnect(QString hostName, u16 hostPort, QString userName, QByteArray passWord, QString clientID, QString certPath)
  115. {
  116. clientID=clientID+QString("_")+takeHostMac().remove(":");
  117. m_connectParam.hostName=hostName;
  118. m_connectParam.hostPort=hostPort;
  119. m_connectParam.userName=userName;
  120. m_connectParam.passWord=passWord;
  121. m_connectParam.clientID=clientID;
  122. m_connectParam.certPath=certPath;
  123. u8 *pData=(u8*)hostName.toUtf8().data();
  124. if(pData[0]>='0' && pData[0]<='9')//判断是否为域名,使用域名时 域名的第一个字符不能是数字
  125. {
  126. m_hostAddress=hostName;
  127. }
  128. }
  129. bool BaseMqtt::mqttIsConnected(void)
  130. {
  131. // if(m_mqttClient!=nullptr)
  132. // return m_mqttClient->isConnectedToHost();
  133. return isConnected;
  134. }
  135. void BaseMqtt::mqttPublishMessage(QString topicFilter, QByteArray msgBa)
  136. {
  137. if(m_mqttClient==nullptr || m_mqttClient->isConnectedToHost()==false)
  138. return;
  139. QMQTT::Message message;
  140. message.setTopic(topicFilter);
  141. message.setPayload(msgBa);
  142. m_mqttClient->publish(message);
  143. }
  144. void BaseMqtt::mqttPingresp(void)
  145. {
  146. // m_mqttClient->pingresp();
  147. }
  148. void BaseMqtt::mqttSubscribeMessage(QString topicFilter, quint8 qos)
  149. {
  150. if(m_mqttClient==nullptr)
  151. return;
  152. m_mqttClient->subscribe(topicFilter, qos);
  153. }
  154. void BaseMqtt::mqttUnsubscribeMessage(QString topicFilter)
  155. {
  156. if(m_mqttClient==nullptr)
  157. return;
  158. m_mqttClient->unsubscribe(topicFilter);
  159. }
  160. void BaseMqtt::signalSlotInit(void)
  161. {
  162. connect(m_mqttClient, SIGNAL(connected()), this, SLOT(slotMqttConnected()));
  163. connect(m_mqttClient, SIGNAL(disconnected()), this, SLOT(slotMqttDisconnected()));
  164. connect(m_mqttClient, SIGNAL(error(QMQTT::ClientError)), this, SLOT(slotMqttError(QMQTT::ClientError)));
  165. connect(m_mqttClient, SIGNAL(pingresp()), this, SLOT(slotMqttPingresp()));
  166. connect(m_mqttClient, SIGNAL(published(QMQTT::Message,quint16)), this, SLOT(slotMqttPuslished(QMQTT::Message,quint16)));
  167. connect(m_mqttClient, SIGNAL(received(QMQTT::Message)), this, SLOT(slotMqttReceived(QMQTT::Message)));
  168. connect(m_mqttClient, SIGNAL(subscribed(QString,quint8)), this, SLOT(slotMqttSubscribed(QString,quint8)));
  169. connect(m_mqttClient, SIGNAL(unsubscribed(QString)), this, SLOT(slotMqttUnsubscribed(QString)));
  170. }
  171. void BaseMqtt::mqttAddTopic(QString topic, u8 qos)
  172. {
  173. for(auto iter : m_subTopicList)
  174. {
  175. if(iter.subTopic==topic)
  176. {
  177. qDebug()<<"have the same topic="<<topic;
  178. return;
  179. }
  180. }
  181. SubTopicStruct tag_subTopic;
  182. tag_subTopic.subTopic=topic;
  183. tag_subTopic.isSubed=false;
  184. tag_subTopic.qos=qos;
  185. m_subTopicList.append(tag_subTopic);
  186. qDebug()<<"mqttAddTopic="<<topic;
  187. }
  188. void BaseMqtt::mqttDelTopic(QString topic)
  189. {
  190. int i=0;
  191. for(auto iter : m_subTopicList)
  192. {
  193. if(topic==iter.subTopic)
  194. {
  195. if(iter.isSubed==true)
  196. {
  197. this->mqttUnsubscribeMessage(topic);
  198. }
  199. m_subTopicList.removeAt(i);
  200. qDebug()<<"remove topic="<<topic;
  201. return;
  202. }
  203. i++;
  204. }
  205. }
  206. void BaseMqtt::slotMqttConnected(void)
  207. {
  208. isConnected=true;
  209. emit sigMqttConnected();
  210. qDebug()<<"clientId="<<m_mqttClient->clientId()<<"connected";
  211. }
  212. void BaseMqtt::slotMqttDisconnected(void)
  213. {
  214. isConnected=false;
  215. int nSize=m_subTopicList.size();
  216. for(int i=0; i<nSize; i++)
  217. {
  218. m_subTopicList[i].isSubed=false;
  219. }
  220. qDebug()<<"clientId="<<m_mqttClient->clientId()<<"disconnected";
  221. emit sigMqttDisconnected();
  222. }
  223. void BaseMqtt::slotMqttError(const QMQTT::ClientError error)
  224. {
  225. qDebug()<<"clientId="<<m_mqttClient->clientId()<<"slotMqttError="<<error;
  226. }
  227. void BaseMqtt::slotMqttPingresp(void)
  228. {
  229. // qDebug("BaseMqtt::slotMqttPingresp");
  230. }
  231. void BaseMqtt::slotMqttPuslished(const QMQTT::Message &message, quint16 msgid)
  232. {
  233. msgid=message.id();
  234. msgid=msgid;
  235. // qDebug("BaseMqtt::slotMqttPuslished, msgid=%d ", msgid);
  236. // qDebug()<<"msg="<<message.payload().toHex();
  237. }
  238. void BaseMqtt::slotMqttReceived(const QMQTT::Message &message)
  239. {
  240. emit sigtMqttReceived(message);
  241. }
  242. void BaseMqtt::slotMqttSubscribed(const QString &topic, const quint8 qos)
  243. {
  244. int i=0;
  245. // qDebug()<<"slotMqttSubscribed, topic="<<topic<<", qos="<<qos;
  246. for(auto iter : m_subTopicList)
  247. {
  248. if(iter.subTopic==topic)
  249. {
  250. m_subTopicList[i].isSubed=true;
  251. break;
  252. }
  253. i++;
  254. }
  255. emit sigMqttSubscribed(topic, qos);
  256. }
  257. void BaseMqtt::slotMqttUnsubscribed(const QString &topic)
  258. {
  259. int i=0;
  260. for(auto iter : m_subTopicList)
  261. {
  262. if(iter.subTopic==topic)
  263. {
  264. m_subTopicList[i].isSubed=false;
  265. break;
  266. }
  267. i++;
  268. }
  269. emit sigMqttUnsubscribed(topic);
  270. }
  271. QString BaseMqtt::takeHostMac(void)
  272. {
  273. DrvCommon drv_com;
  274. return drv_com.takeRandMac();
  275. }

        对于应用层就很简单了,就是创建对象、连接和添加订阅话题即可。其中有个槽函数slotMqttReceived就是用来接收设备发来的数据的。

三、数据解析

        数据解析跟嵌入式端是差不多的,下面是代码,像剥洋葱一样,查找帧头、校验、根据命令类型执行解析。

  1. void MainInterface::slotMqttReceived(const QMQTT::Message &message)
  2. {
  3. QByteArray msg_ba=message.payload();
  4. u8 *pData=(u8*)msg_ba.data();
  5. // qDebug()<<"mqtt recv="<<msg_ba.toHex(':');
  6. u8 head[2]={0xAA, 0x55};
  7. pData=drv_com.memstr(pData, msg_ba.size(), head, 2);
  8. if(pData!=nullptr)
  9. {
  10. u16 total_len=pData[2]<<8 | pData[3];
  11. u16 crcValue=pData[total_len]<<8 | pData[total_len+1];
  12. if(crcValue==drv_com.crc16(pData, total_len))
  13. {
  14. pData+=4;
  15. u32 device_sn=pData[0]<<24|pData[1]<<16|pData[2]<<8|pData[3];
  16. pData+=4;
  17. m_currDevSn=device_sn;
  18. u8 cmd_type=pData[0];
  19. pData+=1;
  20. qDebug("recv device_sn=%08X, cmd_type=%d", device_sn, cmd_type);
  21. m_keepTime=m_secCounts;
  22. switch(cmd_type)
  23. {
  24. case AIR_CMD_HEART:
  25. {
  26. break;
  27. }
  28. case AIR_CMD_DATA:
  29. {
  30. int temp=pData[0]<<8|pData[1];//温度 原始数据
  31. float temp_f=(temp-1000)/10.f;//温度浮点数据
  32. pData+=2;
  33. int humi=pData[0]<<8|pData[1];
  34. float humi_f=humi/10.f;
  35. pData+=2;
  36. int pm25=pData[0]<<8|pData[1];
  37. pData+=2;
  38. u8 speed=pData[0];
  39. pData+=1;
  40. u8 state=pData[0];
  41. pData+=1;
  42. qDebug("temp_f=%.1f C, humi_f=%.1f%%, pm25=%d ug/m3, speed=%d, state=%d", temp_f, humi_f, pm25, speed,state);
  43. QString dev_sn_str=QString::asprintf("%08X", device_sn);
  44. QString temp_str=QString::asprintf("%.0f", temp_f);
  45. QString humi_str=QString::asprintf("%.0f", humi_f);
  46. QString pm25_str=QString::asprintf("%03d", pm25);
  47. int alarm_level=0;
  48. if(pm25<20)alarm_level=0;
  49. else if(pm25<30)alarm_level=1;
  50. else alarm_level=2;
  51. emit siqUpdateSensorValues(dev_sn_str, temp_str, humi_str, pm25_str);
  52. emit siqUpdateAlarmLevel(alarm_level);
  53. emit siqUpdateSwitchState(state);
  54. break;
  55. }
  56. case AIR_CMD_SET_SPEED:
  57. {
  58. break;
  59. }
  60. }
  61. }
  62. }
  63. }

        这里我们需要把数据送到前端去显示,所以定义了几个信号内容,如下图所示,从上到下依次是状态数据,污染等级和开关状态,这些数据都是设备端发送上来的,通过后端处理加工后发到前端显示。这里对于污染等级的数值可以自定义,我这边为了方便测试是20、30两个分界线,小米的净化器应该是30和80两条线。

 四、数据更新

        对于前端显示,这里先提一下如何接收后端发来的数据的,如下图所示。以Connections对象为基础,设置它的属性target为theMainInterface,这个其实就是我们加载QML文件时候设置的前后端交互名称,这里用上了,相当于是信号发射者;信号接收器就是C++文件里定义的信号函数前面加个on,然后首字母改成大写就可以了,这里是s改为S,这样这里就能接收到后端发送过来的传感器数据了,很简单吧。至于如何显示,放到前端部分再讲解。

五、数据发送

        数据发送底层就是跟嵌入式端一样,组合后通过mqtt发送出去就行了,有点区别就是这时候要带上目标的序列号dev_sn,这样带有序列号的话题设备端才能收到数据。

  1. void MainInterface::airSendLevel(u32 dev_sn, int cmd_type, u8 *cmd_buff, u16 cmd_len)
  2. {
  3. u8 make_buff[500]={0};
  4. u16 make_len=0;
  5. make_buff[make_len++]=0xAA;
  6. make_buff[make_len++]=0x55;
  7. make_buff[make_len++]=0;
  8. make_buff[make_len++]=0;
  9. make_buff[make_len++]=dev_sn>>24;
  10. make_buff[make_len++]=dev_sn>>16;
  11. make_buff[make_len++]=dev_sn>>8;
  12. make_buff[make_len++]=dev_sn;
  13. make_buff[make_len++]=cmd_type;
  14. memcpy(&make_buff[make_len], cmd_buff, cmd_len);
  15. make_len+=cmd_len;
  16. make_buff[2]=make_len>>8;
  17. make_buff[3]=make_len;
  18. u16 crcValue=drv_com.crc16(make_buff, make_len);
  19. make_buff[make_len++]=crcValue>>8;
  20. make_buff[make_len++]=crcValue;
  21. QByteArray msg_ba((char*)make_buff, make_len);
  22. QString topic=QString::asprintf("air/dev/sub/%08X", dev_sn);
  23. if(m_mqttClient)
  24. {
  25. m_mqttClient->mqttPublishMessage(topic, msg_ba);
  26. }
  27. }
六、指令下发

        在应用层,主要就是开关和调速两个功能,这里要看下这两个函数的定义,比较特别,在头文件定义的函数名称前多了Q_INVOKABLE,添加了这个关键字后,这个函数就可以在QML文件里直接调用了,是不是很方便。置于函数的内容应该比较简单了,就是组合下报文给底层函数发送出去就行了,这里的命令定义跟嵌入式端是一样的,两边有改动的话一定要及时同步,不然就乱了。对于调速设置,函数的输入是0~1的浮点数,当小于0.1的时候会强制等于0.1,这样调速最低时才不会停掉,只是慢速转动。

        对于这里的后端,总的来说没什么难点,主要还是做好前后端数据交互的准备。这里有个细节提一下,细心的也会发现,有个定时器槽函数slotCheckTimeout(),虽然这里没什么用,但是在其他有复杂任务或者多线程的时候很有用,它可以定时执行任务,相当于局部的main函数了,以后有机会再慢慢学习。

本项目的交流QQ群:701889554

   写于2024-4-2

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/418235
推荐阅读
相关标签
  

闽ICP备14008679号