建筑监控与管理系统及Qt框架开发应用解析
1. 管理工具与空调服务
- 管理工具 :借助C&C服务器实现的API,利用Qt5框架和Mosquitto MQTT客户端库开发了基于GUI的管理工具,可对节点进行基本管理,并将其覆盖在建筑物布局图上。不过,图形工具开发较为复杂,且通常局限于建筑物的单层,除非使用包含所有楼层及节点映射的超大地图,而这显然不够便捷。
- 空调服务 :为控制空调机组,开发了类似C&C的服务,其核心代码如下:
#include <string>
#include <vector>
using namespace std;
#include <Poco/Data/Session.h>
#include <Poco/Data/SQLite/Connector.h>
#include <Poco/Net/HTTPClientSession.h>
#include <Poco/Net/HTTPSClientSession.h>
#include <Poco/Timer.h>
using namespace Poco;
using namespace Poco::Net;
class Listener;
struct NodeInfo {
string uid;
float posx;
float posy;
float current;
float target;
bool ch0_state;
UInt8 ch0_duty;
bool ch0_valid;
bool ch1_state;
UInt8 ch1_duty;
bool ch1_valid;
bool ch2_state;
UInt8 ch2_duty;
bool ch2_valid;
bool ch3_state;
UInt8 ch3_duty;
bool ch3_valid;
UInt8 validate;
};
struct ValveInfo {
string uid;
UInt8 ch0_valve;
UInt8 ch1_valve;
UInt8 ch2_valve;
UInt8 ch3_valve;
};
struct SwitchInfo {
string uid;
bool state;
};
#include "listener.h"
class Nodes {
static Data::Session* session;
static bool initialized;
static HTTPClientSession* influxClient;
static string influxDb;
static bool secure;
static Listener* listener;
static Timer* tempTimer;
static Timer* nodesTimer;
static Timer* switchTimer;
static Nodes* selfRef;
public:
static void init(string influxHost, int influxPort, string influxDb, string influx_sec, Listener* listener);
static void stop();
static bool getNodeInfo(string uid, NodeInfo &info);
static bool getValveInfo(string uid, ValveInfo &info);
static bool getSwitchInfo(string uid, SwitchInfo &info);
static bool setTargetTemperature(string uid, float temp);
static bool setCurrentTemperature(string uid, float temp);
static bool setDuty(string uid, UInt8 ch0, UInt8 ch1, UInt8 ch2, UInt8 ch3);
static bool setValves(string uid, bool ch0, bool ch1, bool ch2, bool ch3);
static bool setSwitch(string uid, bool state);
void updateCurrentTemperatures(Timer& timer);
void checkNodes(Timer& timer);
void checkSwitch(Timer& timer);
static bool getUIDs(vector<string> &uids);
static bool getSwitchUIDs(vector<string> &uids);
};
Nodes类本质上是SQLite数据库的包装器,包含节点、阀门和制冷/制热开关的信息,还设有定时器,用于不断触发应用程序检查系统状态,与目标状态进行比较,并在必要时进行调整。
Listener类则广泛使用Nodes类来跟踪节点和连接的空调机组的状态,以及控制水流的开关和阀门的状态,代码如下:
#include <mosquittopp.h>
#include <string>
#include <map>
using namespace std;
#include <Poco/Mutex.h>
using namespace Poco;
struct NodeInfo;
struct ValveInfo;
struct SwitchInfo;
#include "nodes.h"
class Listener : public mosqpp::mosquittopp {
map<string, NodeInfo> nodes;
map<string, ValveInfo> valves;
map<string, SwitchInfo> switches;
Mutex nodesLock;
Mutex valvesLock;
Mutex switchesLock;
bool heating;
Mutex heatingLock;
public:
Listener(string clientId, string host, int port);
~Listener();
void on_connect(int rc);
void on_message(const struct mosquitto_message* message);
void on_subscribe(int mid, int qos_count, const int* granted_qos);
bool checkNodes();
bool checkSwitch();
};
该应用程序的工作方式是,Nodes类的定时器会使Listener类在PWM、IO和开关模块的主题上发布消息,查询应处于活动状态的设备的状态。这种主动循环系统在工业应用中很常见,可持续验证系统,快速检测是否有异常。
2. InfluxDB记录传感器读数
从一开始,记录传感器读数和咖啡机统计信息就是重点。对于这类数据,理想的数据库是时间序列数据库,Influx是常见的选择。但它不支持MQTT,仅提供HTTP和本地接口。为解决此问题,编写了一个简单的MQTT到Influx HTTP行协议的桥接器,代码如下:
#include "mth.h"
#include <iostream>
using namespace std;
#include <Poco/Net/HTTPRequest.h>
#include <Poco/Net/HTTPResponse.h>
#include <Poco/StringTokenizer.h>
#include <Poco/String.h>
using namespace Poco;
MtH::MtH(string clientId, string host, int port, string topics, string influxHost,
int influxPort, string influxDb, string influx_sec) : mosquittopp(clientId.c_str()) {
this->topics = topics;
this->influxDb = influxDb;
if (influx_sec == "true") {
cout << "Connecting with HTTPS..." << std::endl;
influxClient = new Net::HTTPSClientSession(influxHost, influxPort);
secure = true;
}
else {
cout << "Connecting with HTTP..." << std::endl;
influxClient = new Net::HTTPClientSession(influxHost, influxPort);
secure = false;
}
int keepalive = 60;
connect(host.c_str(), port, keepalive);
}
MtH::~MtH() {
delete influxClient;
}
void MtH::on_connect(int rc) {
cout << "Connected. Subscribing to topics...\n";
if (rc == 0) {
StringTokenizer st(topics, ",", StringTokenizer::TOK_TRIM | StringTokenizer::TOK_IGNORE_EMPTY);
for (StringTokenizer::Iterator it = st.begin(); it != st.end(); ++it) {
string topic = string(*it);
cout << "Subscribing to: " << topic << "\n";
subscribe(0, topic.c_str());
// Add name of the series to the 'series' map.
StringTokenizer st1(topic, "/", StringTokenizer::TOK_TRIM | StringTokenizer::TOK_IGNORE_EMPTY);
string s = st1[st1.count() - 1]; // Get last item.
series.insert(std::pair<string, string>(topic, s));
}
}
else {
cerr << "Connection failed. Aborting subscribing.\n";
}
}
void MtH::on_message(const struct mosquitto_message* message) {
string topic = message->topic;
map<string, string>::iterator it = series.find(topic);
if (it == series.end()) {
cerr << "Topic not found: " << topic << "\n";
return;
}
if (message->payloadlen < 1) {
cerr << "No payload found. Returning...\n";
return;
}
string payload = string((const char*) message->payload, message->payloadlen);
size_t pos = payload.find(";");
if (pos == string::npos || pos == 0) {
cerr << "Invalid payload: " << payload << ". Reject.\n";
return;
}
string uid = payload.substr(0, pos);
string value = payload.substr(pos + 1);
string influxMsg;
influxMsg = series[topic];
influxMsg += ",location=" + uid;
influxMsg += " value=" + value;
try {
Net::HTTPRequest request(Net::HTTPRequest::HTTP_POST,
"/write?db=" + influxDb, Net::HTTPMessage::HTTP_1_1);
request.setContentLength(influxMsg.length());
request.setContentType("application/x-www-form-urlencoded");
influxClient->sendRequest(request) << influxMsg;
Net::HTTPResponse response;
influxClient->receiveResponse(response);
}
catch (Exception& exc) {
cout << "Exception caught while attempting to connect." <<
std::endl;
cerr << exc.displayText() << std::endl;
return;
}
}
当收到新的MQTT消息时,会查找对应的Influx时间序列名称,创建要发送到InfluxDB服务器的字符串。假设消息负载由发送消息的节点的MAC地址后跟分号组成,取分号后的部分作为值,MAC作为位置,然后将其发送到数据库服务器。
3. 安全方面
在系统开发过程中,安全至关重要。因此考虑添加传输层安全(TLS)加密,利用Sming框架中的集成axTLS加密库和AES证书(主机和客户端),不仅可验证主机(服务器)和客户端(节点)的身份,还能提供安全的加密链接。
然而,ESP8266内存不足以分配默认的TLS握手缓冲区,需要服务器(主机)端使用SSL片段大小扩展。但常用的MQTT代理Mosquitto不支持此扩展,客户端需使用默认的双16 kB缓冲区。一种解决方案是修改Mosquitto代理的源代码并重新编译,更好的方案是安装代理软件HAProxy,它作为TLS端点,处理证书并将解密后的流量通过本地回环(localhost)接口重定向到MQTT代理。将SSL片段大小选项设置为1 - 2 kB后,系统可正常工作,实现了建筑物范围内的无线监控和控制系统,确保敏感信息和精细控制命令的安全通信。
4. Qt框架开发嵌入式系统
- 框架的力量 :框架是为简化特定应用程序开发而设计的代码集合,为开发者提供一系列类,使其无需担心底层硬件接口或操作系统API,即可实现应用逻辑。之前使用过多种框架,如No date框架、CMSIS、Arduino、POCO框架和Qt框架等。不同框架有不同的目标系统,No date、CMSIS和Arduino针对微控制器(MCUs),实时操作系统框架(RTOS)包含完整的操作系统,而POCO和Qt则针对操作系统,作为操作系统特定API的抽象层,同时提供额外功能,便于快速构建功能齐全的应用程序。
- Qt用于命令行应用 :虽然Qt框架以图形用户界面(GUI)为卖点,但也可用于开发仅命令行的应用程序。示例代码如下:
#include <QCoreApplication>
#include <core.h>
int main(int argc, char *argv[]) {
QCoreApplication app(argc, argv);
Core core;
connect(&core, &Core::done, &app, &app::quit, Qt::QueuedConnection);
core.start();
return app.exec();
}
#include <QObject>
class Core : public QObject {
Q_OBJECT
public:
explicit Core(QObject *parent = 0);
signals:
void done();
public slots:
void start();
};
#include "core.h"
#include <iostream>
Core::Core(QObject *parent) : QObject(parent) {
//
}
void Core::start() {
std::cout << "Start emitting done()" << std::endl;
emit done();
}
在这个例子中,使用QCoreApplication类创建输入和事件循环处理程序。每个想使用Qt信号 - 槽架构的类都需从QObject类派生,并在类声明中包含Q_OBJECT宏。
5. GUI-based Qt applications
- 代码对比与变化 :将基于Qt的示例项目(来自之前的Linux - 基于信息娱乐系统示例)的主函数与前面的仅命令行版本进行对比,当添加GUI时,有明显变化。
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
最明显的变化是使用
QApplication
而不是
QCoreApplication
,并且使用从
QMainWindow
派生的类。
-
MainWindow类的定义与功能
#include <QMainWindow>
#include <QAudioRecorder>
#include <QAudioProbe>
#include <QMediaPlayer>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
public slots:
void playBluetooth();
void stopBluetooth();
void playOnlineStream();
void stopOnlineStream();
void playLocalFile();
void stopLocalFile();
void recordMessage();
void playMessage();
void errorString(QString err);
void quit();
private:
Ui::MainWindow *ui;
QMediaPlayer* player;
QAudioRecorder* audioRecorder;
QAudioProbe* audioProbe;
qint64 silence;
private slots:
void processBuffer(QAudioBuffer);
};
MainWindow
类从
QMainWindow
派生,拥有
show()
方法。
MainWindow
实例在
UI
命名空间中声明,这与运行
qmake
工具生成的自动代码相关。
-
构造函数的操作与功能
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent),
ui(new Ui::MainWindow) {
ui->setupUi(this);
connect(ui->actionQuit, SIGNAL(triggered()), this, SLOT(quit()));
connect(ui->playBluetoothButton, SIGNAL(pressed), this, SLOT(playBluetooth));
connect(ui->stopBluetoothButton, SIGNAL(pressed), this, SLOT(stopBluetooth));
connect(ui->playLocalAudioButton, SIGNAL(pressed), this, SLOT(playLocalFile));
connect(ui->stopLocalAudioButton, SIGNAL(pressed), this, SLOT(stopLocalFile));
connect(ui->playOnlineStreamButton, SIGNAL(pressed), this, SLOT(playOnlineStream));
connect(ui->stopOnlineStreamButton, SIGNAL(pressed), this, SLOT(stopOnlineStream));
connect(ui->recordMessageButton, SIGNAL(pressed), this, SLOT(recordMessage));
connect(ui->playBackMessage, SIGNAL(pressed), this, SLOT(playMessage));
silence = 0;
// Create the audio interface instances.
player = new QMediaPlayer(this);
audioRecorder = new QAudioRecorder(this);
audioProbe = new QAudioProbe(this);
// Configure the audio recorder.
QAudioEncoderSettings audioSettings;
audioSettings.setCodec("audio/amr");
audioSettings.setQuality(QMultimedia::HighQuality);
audioRecorder->setEncodingSettings(audioSettings);
audioRecorder->setOutputLocation(QUrl::fromLocalFile("message/last_message.amr"));
// Configure audio probe.
connect(audioProbe, SIGNAL(audioBufferProbed(QAudioBuffer)), this, SLOT(processBuffer(QAudioBuffer)));
audioProbe->setSource(audioRecorder);
QThread* thread = new QThread;
VoiceInput* vi = new VoiceInput();
vi->moveToThread(thread);
connect(thread, SIGNAL(started()), vi, SLOT(run()));
connect(vi, SIGNAL(finished()), thread, SLOT(quit()));
connect(vi, SIGNAL(finished()), vi, SLOT(deleteLater()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
connect(vi, SIGNAL(error(QString)), this, SLOT(errorString(QString)));
connect(vi, SIGNAL(playBluetooth), this, SLOT(playBluetooth));
connect(vi, SIGNAL(stopBluetooth), this, SLOT(stopBluetooth));
connect(vi, SIGNAL(playLocal), this, SLOT(playLocalFile));
connect(vi, SIGNAL(stopLocal), this, SLOT(stopLocalFile));
connect(vi, SIGNAL(playRemote), this, SLOT(playOnlineStream));
connect(vi, SIGNAL(stopRemote), this, SLOT(stopOnlineStream));
connect(vi, SIGNAL(recordMessage), this, SLOT(recordMessage));
connect(vi, SIGNAL(playMessage), this, SLOT(playMessage));
thread->start();
}
构造函数首先从UI描述文件中加载GUI,该文件通常使用Qt Designer工具创建。然后,将GUI中的菜单动作和按钮小部件连接到内部槽。还进行了音频接口实例的创建和配置,以及线程的创建和连接,将一些耗时操作移到单独的线程中执行,避免阻塞UI线程。
-
析构函数的作用
MainWindow::~MainWindow() {
delete ui;
}
析构函数用于删除UI和所有关联元素。
6. 未来发展展望
该系统仍有许多可改进之处,例如增加支持的传感器数量、使用更多的GPIO扩展芯片、优化空调系统配置、实现与日历后端关联的房间占用检测,以及清理无人参加的办公室会议安排等。此外,还可以考虑将MCU从ESP8266更换为基于ARM的MCU,以获得有线以太网选项和更好的调试与开发工具。虽然带有Wi - Fi的MCU使用方便,但ESP8266的开发工具不够完善,且缺乏有线通信选项(不使用外部芯片时),系统的可靠性依赖于Wi - Fi网络质量。对于建筑自动化系统,可靠性很重要,因此未来可能会采用有线和无线相结合的混合网络。
建筑监控与管理系统及Qt框架开发应用解析
7. 系统架构与功能总结
为了更清晰地理解整个系统的架构和功能,我们可以通过以下表格进行总结:
| 系统组件 | 功能描述 | 关键代码类 |
| — | — | — |
| 管理工具 | 基于GUI对节点进行基本管理,覆盖在建筑物布局图上 | 无(使用Qt5和Mosquitto MQTT库开发) |
| 空调服务 | 控制空调机组,管理节点、阀门和开关信息,检查系统状态 | Nodes类、Listener类 |
| InfluxDB桥接器 | 将MQTT消息转换为InfluxDB可接受的格式并记录传感器数据 | MtH类 |
| 安全机制 | 提供传输层安全(TLS)加密,确保通信安全 | 使用HAProxy作为TLS端点 |
| Qt命令行应用 | 使用QCoreApplication创建命令行应用程序,利用信号 - 槽机制 | Core类、QCoreApplication |
| Qt GUI应用 | 创建具有图形用户界面的应用程序,处理音频、播放等功能 | MainWindow类、QApplication |
下面是系统的整体工作流程的mermaid流程图:
graph LR;
A[管理工具] -->|管理节点| B[空调服务];
B -->|记录数据| C[InfluxDB桥接器];
C -->|存储数据| D[InfluxDB];
E[安全机制] -->|加密通信| A;
E -->|加密通信| B;
E -->|加密通信| C;
F[Qt命令行应用] -->|独立运行| G[系统功能];
H[Qt GUI应用] -->|用户交互| G;
8. 各组件详细分析
- 管理工具 :虽然提供了直观的节点管理方式,但受限于图形工具开发的复杂性和地图展示的局限性,在多层建筑管理方面存在不足。未来可以考虑开发更灵活的地图展示方式,如分层切换或3D地图,以提高管理效率。
- 空调服务 :Nodes类和Listener类的设计使得系统能够有效地管理空调机组的各种参数和状态。通过定时器的使用,实现了对系统状态的实时监测和调整。然而,随着系统规模的扩大,定时器的管理可能会变得复杂,需要考虑更高效的调度算法。
- InfluxDB桥接器 :解决了InfluxDB不支持MQTT的问题,实现了传感器数据的有效记录。但在处理大量消息时,可能会出现性能瓶颈,需要优化消息处理和数据库写入的逻辑。
- 安全机制 :采用HAProxy作为TLS端点是一个有效的解决方案,确保了系统通信的安全性。但在证书管理和更新方面,需要建立完善的机制,以应对证书过期等问题。
- Qt命令行应用 :展示了Qt框架在命令行开发中的灵活性,通过信号 - 槽机制实现了程序的简洁和高效。但对于复杂的命令行交互,可能需要进一步扩展和优化。
- Qt GUI应用 :利用Qt的强大功能创建了具有丰富交互性的图形用户界面。但在多线程处理方面,需要注意线程安全问题,避免出现数据竞争和死锁等情况。
9. 开发建议与实践技巧
- 框架选择 :根据项目需求选择合适的框架是关键。对于微控制器开发,可以优先考虑No date、CMSIS和Arduino等框架;对于操作系统相关的开发,POCO和Qt框架能提供更强大的功能和更好的抽象层。
- 代码结构 :保持良好的代码结构和注释,有助于提高代码的可维护性和可读性。例如,在Nodes类和Listener类中,将不同的功能模块分开实现,便于后续的扩展和修改。
- 安全意识 :在开发过程中始终保持安全意识,特别是在涉及敏感信息和控制命令的通信中,要采用加密和认证机制,确保系统的安全性。
- 性能优化 :对于可能出现性能瓶颈的部分,如InfluxDB桥接器和多线程处理,要进行性能测试和优化,采用合适的算法和数据结构提高系统的响应速度。
10. 总结与启示
通过对建筑监控与管理系统及Qt框架开发应用的分析,我们可以得到以下启示:
- 一个复杂的系统通常由多个组件组成,每个组件都有其特定的功能和作用,需要合理设计和集成,以实现系统的整体目标。
- 框架的使用可以大大简化开发过程,但也需要根据具体需求进行选择和定制,充分发挥框架的优势。
- 安全是系统开发中不可忽视的重要因素,要采取有效的安全措施,保障系统的稳定运行和数据安全。
- 随着技术的不断发展,系统需要不断进行改进和升级,以适应新的需求和挑战,未来的系统可能会采用更先进的技术和架构。
总之,建筑监控与管理系统及Qt框架开发应用是一个综合性的领域,涉及到硬件、软件、网络和安全等多个方面。通过深入学习和实践,我们可以不断提高自己的开发能力,开发出更加高效、安全和可靠的系统。
超级会员免费看

被折叠的 条评论
为什么被折叠?



