当前位置: 华文问答 > 数码

采用 qt for android是不是就能绕过java用c++开发Android?

2013-05-22数码

一、项目配置

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

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

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

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

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

二、MQTT连接

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

#include "BaseMqtt.h" BaseMqtt::BaseMqtt(QObject *parent) : QObject(parent) { isConnected=false; checkTimer = new QTimer(this); checkTimer->setInterval(1*100);//心跳检测 checkTimer->start(); m_heartTime=0; connect(checkTimer, SIGNAL(timeout()),this,SLOT(slotCheckTimeout())); m_mqttClient=nullptr; m_hostAddress=""; } BaseMqtt::~BaseMqtt(void) { qDebug()<<"~BaseMqtt, hostName="<<m_connectParam.hostName; } void BaseMqtt::slotLookUpHost(QHostInfo info) { if(info.error() == QHostInfo::NoError) { foreach (QHostAddress address, info.addresses()) { m_hostAddress=address.toString(); qDebug()<<m_connectParam.hostName<<" mqtt found ip= "<<m_hostAddress; break; } } } void BaseMqtt::slotCheckTimeout(void) { m_heartTime++; if(m_hostAddress.isEmpty()) { if(m_heartTime==0) { qDebug("mqtt start dns!"); QHostInfo::lookupHost(m_connectParam.hostName, this, SLOT(slotLookUpHost(QHostInfo))); } return; } if(m_mqttClient==nullptr) { if(!m_connectParam.certPath.isEmpty())//使用SSL连接 { QFile file; QByteArray client_key_text, client_crt_text, root_crt_text; QString certPath=m_connectParam.certPath; QSslSocket ssl_socket; file.setFileName(certPath+"/client.key"); file.open(QIODevice::ReadOnly | QIODevice::Text); client_key_text = file.readAll(); file.close(); file.setFileName(certPath+"/client.crt"); file.open(QIODevice::ReadOnly | QIODevice::Text); client_crt_text = file.readAll(); file.close(); file.setFileName(certPath+"/rootCA.crt"); file.open(QIODevice::ReadOnly | QIODevice::Text); root_crt_text = file.readAll(); file.close(); QSslKey client_key(client_key_text, QSsl::Rsa); QSslCertificate client_crt(client_crt_text); ssl_socket.setPrivateKey(client_key); ssl_socket.setLocalCertificate(client_crt); QSslConfiguration ssl_config=QSslConfiguration::defaultConfiguration(); ssl_config.setCaCertificates(QSslCertificate::fromData(root_crt_text)); //QSslCertificate::fromPath(certPath+"/rootCA.crt"); ssl_config.setPrivateKey(ssl_socket.privateKey()); ssl_config.setLocalCertificate(ssl_socket.localCertificate()); ssl_config.setPeerVerifyMode(QSslSocket::QueryPeer); ssl_config.setPeerVerifyDepth(10); ssl_config.setProtocol(QSsl::TlsV1_2); m_mqttClient = new QMQTT::Client(m_hostAddress, m_connectParam.hostPort, ssl_config, true, this); // qDebug()<<"\n###SSL PrivateKey="<<ssl_config.privateKey(); // qDebug()<<"###SSL Certificate="<<ssl_config.localCertificate(); // qDebug()<<"###SSL rootCA="<<ssl_config.caCertificates(); // qDebug()<<"hostName="<<m_hostAddress<<", hostPort="<<m_connectParam.hostPort; // qDebug()<<"userName="<<m_connectParam.userName<<", passWord="<<m_connectParam.passWord<<", clientID="<<m_connectParam.clientID; } else//普通连接 { QHostAddress host(m_hostAddress); m_mqttClient = new QMQTT::Client(host, m_connectParam.hostPort, this); } signalSlotInit(); m_mqttClient->setUsername(m_connectParam.userName); m_mqttClient->setPassword(m_connectParam.passWord); m_mqttClient->setClientId(m_connectParam.clientID); m_mqttClient->setAutoReconnect(true); m_mqttClient->setCleanSession(true); m_mqttClient->setKeepAlive(30); m_mqttClient->connectToHost(); } else if(this->mqttIsConnected()) { for(auto iter : m_subTopicList)//订阅话题 { if(iter.isSubed==false) { mqttSubscribeMessage(iter.subTopic, iter.qos); break; } } if(m_heartTime 0==0)//保持连接 { // qDebug()<<"mqtt send keep"; mqttPublishMessage("dev/sub/data1", QByteArray("heart")); } } else { // qDebug()<<"BaseMqtt no connected!"; } } void BaseMqtt::mqttConnect(QString hostName, u16 hostPort, QString userName, QByteArray passWord, QString clientID, QString certPath) { clientID=clientID+QString("_")+takeHostMac().remove(":"); m_connectParam.hostName=hostName; m_connectParam.hostPort=hostPort; m_connectParam.userName=userName; m_connectParam.passWord=passWord; m_connectParam.clientID=clientID; m_connectParam.certPath=certPath; u8 *pData=(u8*)hostName.toUtf8().data(); if(pData[0]>='0' && pData[0]<='9')//判断是否为域名,使用域名时 域名的第一个字符不能是数字 { m_hostAddress=hostName; } } bool BaseMqtt::mqttIsConnected(void) { // if(m_mqttClient!=nullptr) // return m_mqttClient->isConnectedToHost(); return isConnected; } void BaseMqtt::mqttPublishMessage(QString topicFilter, QByteArray msgBa) { if(m_mqttClient==nullptr || m_mqttClient->isConnectedToHost()==false) return; QMQTT::Message message; message.setTopic(topicFilter); message.setPayload(msgBa); m_mqttClient->publish(message); } void BaseMqtt::mqttPingresp(void) { // m_mqttClient->pingresp(); } void BaseMqtt::mqttSubscribeMessage(QString topicFilter, quint8 qos) { if(m_mqttClient==nullptr) return; m_mqttClient->subscribe(topicFilter, qos); } void BaseMqtt::mqttUnsubscribeMessage(QString topicFilter) { if(m_mqttClient==nullptr) return; m_mqttClient->unsubscribe(topicFilter); } void BaseMqtt::signalSlotInit(void) { connect(m_mqttClient, SIGNAL(connected()), this, SLOT(slotMqttConnected())); connect(m_mqttClient, SIGNAL(disconnected()), this, SLOT(slotMqttDisconnected())); connect(m_mqttClient, SIGNAL(error(QMQTT::ClientError)), this, SLOT(slotMqttError(QMQTT::ClientError))); connect(m_mqttClient, SIGNAL(pingresp()), this, SLOT(slotMqttPingresp())); connect(m_mqttClient, SIGNAL(published(QMQTT::Message,quint16)), this, SLOT(slotMqttPuslished(QMQTT::Message,quint16))); connect(m_mqttClient, SIGNAL(received(QMQTT::Message)), this, SLOT(slotMqttReceived(QMQTT::Message))); connect(m_mqttClient, SIGNAL(subscribed(QString,quint8)), this, SLOT(slotMqttSubscribed(QString,quint8))); connect(m_mqttClient, SIGNAL(unsubscribed(QString)), this, SLOT(slotMqttUnsubscribed(QString))); } void BaseMqtt::mqttAddTopic(QString topic, u8 qos) { for(auto iter : m_subTopicList) { if(iter.subTopic==topic) { qDebug()<<"have the same topic="<<topic; return; } } SubTopicStruct tag_subTopic; tag_subTopic.subTopic=topic; tag_subTopic.isSubed=false; tag_subTopic.qos=qos; m_subTopicList.append(tag_subTopic); qDebug()<<"mqttAddTopic="<<topic; } void BaseMqtt::mqttDelTopic(QString topic) { int i=0; for(auto iter : m_subTopicList) { if(topic==iter.subTopic) { if(iter.isSubed==true) { this->mqttUnsubscribeMessage(topic); } m_subTopicList.removeAt(i); qDebug()<<"remove topic="<<topic; return; } i++; } } void BaseMqtt::slotMqttConnected(void) { isConnected=true; emit sigMqttConnected(); qDebug()<<"clientId="<<m_mqttClient->clientId()<<"connected"; } void BaseMqtt::slotMqttDisconnected(void) { isConnected=false; int nSize=m_subTopicList.size(); for(int i=0; i<nSize; i++) { m_subTopicList[i].isSubed=false; } qDebug()<<"clientId="<<m_mqttClient->clientId()<<"disconnected"; emit sigMqttDisconnected(); } void BaseMqtt::slotMqttError(const QMQTT::ClientError error) { qDebug()<<"clientId="<<m_mqttClient->clientId()<<"slotMqttError="<<error; } void BaseMqtt::slotMqttPingresp(void) { // qDebug("BaseMqtt::slotMqttPingresp"); } void BaseMqtt::slotMqttPuslished(const QMQTT::Message &message, quint16 msgid) { msgid=message.id(); msgid=msgid; // qDebug("BaseMqtt::slotMqttPuslished, msgid=%d ", msgid); // qDebug()<<"msg="<<message.payload().toHex(); } void BaseMqtt::slotMqttReceived(const QMQTT::Message &message) { emit sigtMqttReceived(message); } void BaseMqtt::slotMqttSubscribed(const QString &topic, const quint8 qos) { int i=0; // qDebug()<<"slotMqttSubscribed, topic="<<topic<<", qos="<<qos; for(auto iter : m_subTopicList) { if(iter.subTopic==topic) { m_subTopicList[i].isSubed=true; break; } i++; } emit sigMqttSubscribed(topic, qos); } void BaseMqtt::slotMqttUnsubscribed(const QString &topic) { int i=0; for(auto iter : m_subTopicList) { if(iter.subTopic==topic) { m_subTopicList[i].isSubed=false; break; } i++; } emit sigMqttUnsubscribed(topic); } QString BaseMqtt::takeHostMac(void) { DrvCommon drv_com; return drv_com.takeRandMac(); }

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

三、数据解析

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

void MainInterface::slotMqttReceived(const QMQTT::Message &message) { QByteArray msg_ba=message.payload(); u8 *pData=(u8*)msg_ba.data(); // qDebug()<<"mqtt recv="<<msg_ba.toHex(':'); u8 head[2]={0xAA, 0x55}; pData=drv_com.memstr(pData, msg_ba.size(), head, 2); if(pData!=nullptr) { u16 total_len=pData[2]<<8 | pData[3]; u16 crcValue=pData[total_len]<<8 | pData[total_len+1]; if(crcValue==drv_com.crc16(pData, total_len)) { pData+=4; u32 device_sn=pData[0]<<24|pData[1]<<16|pData[2]<<8|pData[3]; pData+=4; m_currDevSn=device_sn; u8 cmd_type=pData[0]; pData+=1; qDebug("recv device_sn=X, cmd_type=%d", device_sn, cmd_type); m_keepTime=m_secCounts; switch(cmd_type) { case AIR_CMD_HEART: { break; } case AIR_CMD_DATA: { int temp=pData[0]<<8|pData[1];//温度 原始数据 float temp_f=(temp-1000)/10.f;//温度浮点数据 pData+=2; int humi=pData[0]<<8|pData[1]; float humi_f=humi/10.f; pData+=2; int pm25=pData[0]<<8|pData[1]; pData+=2; u8 speed=pData[0]; pData+=1; u8 state=pData[0]; pData+=1; qDebug("temp_f=%.1f C, humi_f=%.1f%%, pm25=%d ug/m3, speed=%d, state=%d", temp_f, humi_f, pm25, speed,state); QString dev_sn_str=QString::asprintf("X", device_sn); QString temp_str=QString::asprintf("%.0f", temp_f); QString humi_str=QString::asprintf("%.0f", humi_f); QString pm25_str=QString::asprintf("d", pm25); int alarm_level=0; if(pm25<20)alarm_level=0; else if(pm25<30)alarm_level=1; else alarm_level=2; emit siqUpdateSensorValues(dev_sn_str, temp_str, humi_str, pm25_str); emit siqUpdateAlarmLevel(alarm_level); emit siqUpdateSwitchState(state); break; } case AIR_CMD_SET_SPEED: { break; } } } } }

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

四、数据更新

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

五、数据发送

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

void MainInterface::airSendLevel(u32 dev_sn, int cmd_type, u8 *cmd_buff, u16 cmd_len) { u8 make_buff[500]={0}; u16 make_len=0; make_buff[make_len++]=0xAA; make_buff[make_len++]=0x55; make_buff[make_len++]=0; make_buff[make_len++]=0; make_buff[make_len++]=dev_sn>>24; make_buff[make_len++]=dev_sn>>16; make_buff[make_len++]=dev_sn>>8; make_buff[make_len++]=dev_sn; make_buff[make_len++]=cmd_type; memcpy(&make_buff[make_len], cmd_buff, cmd_len); make_len+=cmd_len; make_buff[2]=make_len>>8; make_buff[3]=make_len; u16 crcValue=drv_com.crc16(make_buff, make_len); make_buff[make_len++]=crcValue>>8; make_buff[make_len++]=crcValue; QByteArray msg_ba((char*)make_buff, make_len); QString topic=QString::asprintf("air/dev/sub/X", dev_sn); if(m_mqttClient) { m_mqttClient->mqttPublishMessage(topic, msg_ba); } }

六、指令下发

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

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

本项目的交流QQ群:701889554