目录
一、Qt工程创建
二、数据库知识
三、通信协议
四、名词定义
本项目的交流QQ群:701889554
物联网实战--入门篇https://blog.csdn.net/ypp240124016/category_12609773.html
物联网实战--驱动篇https://blog.csdn.net/ypp240124016/category_12631333.html
一、Qt工程创建
Qt方面有三个工程需要创建,分别是应用服务器程序、数据服务器程序和客户端APP,他们各自的作用在上一篇已经说过了,这一篇主要创建工程的基础结构,主要是MQTT通讯和数据库相关等基础文件。对于应用服务器和数据服务器程序是不需要界面的,用控制台程序即可,所以它们的.pro文件都有一个:
QT -= gui
对于MQTT之前已经有大概讲解过了,可以去参考之前的文章,我们这里主要还是对开源库进行封装,把域名解析、话题订阅、证书添加等内容进行内部处理。在这里我们主要介绍下数据库相关的内容。
物联网实战--入门篇之(七)嵌入式-MQTT_mqtt嵌入式-CSDN博客
项目工程集合 https://download.csdn.net/download/ypp240124016/89248704
二、数据库知识
QT对各类数据库进行封装,可以驱动很多类型的数据,包括ODBC、SQLite、MySQL、PostgreSQL、SQL Server、Oracle等,不同类型的数据库驱动类型略有区别,一般数据库都需要另外安装软件,比如MySQL,可以参考这篇文章Mysql的安装配置教程(非常详细)从零基础入门到精通,看完这一篇就够了_mysql安装教程-CSDN博客
对于没接触过数据库的同学会觉得比较麻烦,所以我们这个项目为了方便,直接使用SQLite作为数据库了,它无需额外安装软件,生成的数据库是独立文件,就像普通配置文件一样。数据库的操作,核心就是增删改查,利用数据库语句进行操作,这里可以大概看下教程SQLite 教程 | 菜鸟教程
以下是数据库驱动SQLite的封装文件和代码:
#include "BaseSqlite.h"BaseSqlite::BaseSqlite(QObject *parent) : QObject(parent)
{
}BaseSqlite::~BaseSqlite()
{
// closeDataBase();
}void BaseSqlite::closeDataBase(void)
{
// qDebug()<<m_dbName<<" ## "<<m_connName<<"closeDataBase";
// QSqlDatabase::removeDatabase(m_connName);m_sqlDataBase.close();
}//打开数据库
bool BaseSqlite::openDataBase(QString db_name, QString conn_name)
{if(m_sqlDataBase.isOpen()){qDebug()<<m_dbName<<" "<<m_connName<< " is opened.";return false;}m_dbName=db_name;m_connName=conn_name;if(QSqlDatabase::contains(m_connName))m_sqlDataBase = QSqlDatabase::database(m_connName);elsem_sqlDataBase = QSqlDatabase::addDatabase("QSQLITE", m_connName);// m_sqlDataBase = QSqlDatabase::addDatabase("QSQLITE", conn_name);m_sqlDataBase.setDatabaseName(db_name);if(!m_sqlDataBase.open()){qDebug()<<m_dbName<<" "<<m_connName<< "Error: Failed to connect database." << m_sqlDataBase.lastError();return false;}else{m_sqlQuery = QSqlQuery(m_sqlDataBase);
// qDebug()<<m_dbName<<" "<<m_connName<< "Succeed to open database." ;}return true;
}bool BaseSqlite::isOpened(void)
{return m_sqlDataBase.isOpen();
}//执行语句
bool BaseSqlite::runSqlQuery(QString str_query)
{
// QSqlQuery query(m_sqlDataBase);if(!m_sqlQuery.exec(str_query)){qDebug()<<m_sqlQuery.lastError();}else{return true;}return false;
}//启动事务
bool BaseSqlite::beginTransaction(void)
{QString str_query = "BEGIN TRANSACTION";if(runSqlQuery(str_query)==false){return false;}return true;
}//停止事务
bool BaseSqlite::endTransaction(void)
{QString str_query = "COMMIT";if(runSqlQuery(str_query)==false){return false;}return true;
}
看起来也很简单,主要就是打开、关闭数据库,执行数据库语句;另外还有启动和停止事务,主要是用于数据流比较大的场景,比如数据服务器的数据保存,用数据库的事务功能可以避免频繁打开、关闭数据库,浪费时间,可以累积数据或者定时写入数据,提高效率。
三、通信协议
这里的通讯协议指的是应用层的协议,理论上各个公司都是自定义的,没有标准。在这里,我们从实际需求出发,也自定义了一种协议,具体如下:
协议看着比较复杂,分成两部分来看,一个是整体定义,一个是数据包内容。首先协议采用二进制传输,整体上包含帧头,便于检索,校验码、数据长度该有的也都有了;1~16字节内容都是明文,都有相应解释,其中app_id和dev_sn稍后详细说明;17~N的数据区需要加密,我们整个系统采用一型多密的方案,即一种型号配套多组密码,根据自己需要使用,密码在设备生产定义时确定,需要注意保护,协议中的索引就是密码索引,这样可以明确数据包内用了哪一组的密码。
协议的核心还是数据区里的内容,这里使用的是一种嵌套的思想,因为在实际项目中我们经常会用到无线组网的方式,一个网关加多个节点,对于节点数据是需要通过网关转发的,那就相当于把节点数据嵌套在网关数据内部,这时候网关的顶层命令固定为100,意思就是本次数据包是转发的节点数据,要根据数据包的内容进一步解析;下行的时候也是一样,节点的控制指令需要经过网关进行转发,转发命令固定为200,网关解析后会把数据提取出来,至于怎么转发是网关跟节点之间的事了,后面在做LoRa组网的时候会具体演示。
理论上可以一直嵌套下去,不过一般就一层,网关+节点,节点后面再挂载节点的情况比较少见了。
对于不同的设备会包含不同的命令类型,需要从设备的实际需求出发,一般来讲,针对每一种类型的设备或者新产品,都要有规范的协议文档,这样在开发和维护过程中比较清晰,出现问题容易定位。
协议内容都是字节序的形式传输,避免大小端的问题,我们约定数据高位先传输,比如协议里的app_id是四个字节,以AABB1122为例,那传输顺序就是AA、BB、11、22;对于数据的定义可以参考之前的净化器数值,简单讲就是我们只传输正整数,产品定义的时候就要把某个数据的范围和精度确定好,这样就可以把这个数据转为正整数了,然后传输依然是高位在前,低位在后的原则。这样,整个协议的统一性就很高了,对于具体的代码在后续合适的章节会体现,不会很复杂。
四、名词定义
整个体系会有一些专有名词,这里统一做一下解释。
账户:这个不用过多解释了,就像微信号一样,在整个系统里是唯一的,一般采用字符和数字的形式,我们这个系统以手机号为核心,注册的时候需要跟手机绑定,用验证码的方式注册,属于比较常规的。
子账户:有时候手机号就一个,账户又想要多个,那么就可以用主账户去新建子账户,子账户的使用上跟主账户类似,但是子账户不能再创建子账户了。
应用ID:就是协议里的app_id,长度是4个字节,每个账户下可以创建多个应用,每个应用下包含多个设备,举例说明,一般来讲,智能家居中一套房子创建一个应用,这个应用下有空调、冰箱、净化器等设备,这时候假设你有多套房子,每套房子都要整一套智能家居产品,显然,如果把所有设备都放在一个应用下就不太好管理了,这时候就可以为每套房子新建一个应用,各自的设备放在对应的应用下,彼此就很好管理了,这就是应用ID存在的意义。从技术角度来讲,设备发布消息的话题形式是这样的dev/pub/data/123001,其中123001就是应用ID,这样对于用户端APP来讲,只要根据app_id来订阅,该应用下的设备数据就会发到正确的用户端。所以说,app_id在整个系统的管理上起到了一个很关键的作用。
设备序列号:就是协议里的dev_sn,是设备的身份标识,长度是4个字节,对于dev_sn是需要规范的,不能随便定义,在这里我们定义,高2字节代表设备类型,低2字节代表地址码,例如16进制A3010001,其中A301代表设备类型,比如四路主机,这个在产品定义时就要确定,那么0001就是地址码,这是在生产时确定的,于是四路主机所有的序列号就是A3010001~A301FFFF,理论上可以生产6万多个。实际上,再加上app_id的区分,其实不要在同一个应用里使用相同的dev_sn就行了,这样一来,在实际项目中都是够用的了。我们的密码体系是一型多密原则,这里的型就是指dev_sn的高2字节,理论上可以创建6万多种型号,够用了。总体来讲,app_id和dev_sn是我们整个系统设计的核心,一切都是围绕这两个概念展开的。
解析插件:每一种型号的设备对应一个解析插件,主要就是负责对设备数据进行解析和操作,后期的产品开发主要就是设备端和对应解析插件的开发,这也是端到端开发模式的由来。解析插件主要是对用户端APP的不断扩充,包含两个内容,一个是后端C++对数据进行解析,一个是前端QML做该型号设备的展示界面,就像入门篇里所展示的净化器界面一样。这样,平台端无需增加开发,一个简单的物联网开发模式就形成了。
网关/主机:这一类设备是直接跟服务器连接的,它们一般带有网口、WIFI或者4G等互联网接口,设备端的加密/解密也是在这类设备中完成的;它们有时候是独立的个体,比如WiFi空调,有时候是作为其它设备的数据中转站,比如LoRa网关,主要作用就是对LoRa节点设备的数据进行转发。
节点:与上面所述的网关配合使用,节点的特点一般是数量较多,每个都有联网功能的话成本较高,所以选择用网关作为统一的联网媒介,比如485温湿度、LoRa门磁等等。