我将展示如何将 Qt 数据库(QtSql 模块)集成到前文的调试工具中,基于 ReferenceClass、PointerClass、SerialPort、ModbusTCPDebugger 和 ModbusConverter,并扩展 Qt GUI 以支持数据库操作。数据库将用于存储串口和 Modbus 数据(包括功能码 0x01、0x03、0x05、0x06),取代部分 CSV 日志功能,同时保留 QTreeView 和图表。代码将使用 SQLite 数据库(轻量、跨平台),支持中文字符,确保与异步 I/O、线程池和 TCP-to-RTU 转换兼容。GUI 将显示数据库查询结果,并允许用户管理数据。代码基于 Windows,提供 Linux 适配说明,Demo 通过 main 函数启动 GUI。
1. 设计目标
Qt 数据库集成:
使用 QtSql 和 SQLite 存储串口数据(值、时间戳)和 Modbus 数据(功能码、地址、值、时间戳)。
支持插入、查询、删除操作,确保中文字符正确存储和显示。
替换部分 CSV 日志功能,提供更强大的数据管理。
GUI 增强:
QTreeView:显示设备数据层次结构(串口和 Modbus 数据)。
QTableView:显示数据库查询结果(替代前文的 QTableWidget)。
控件:添加查询输入框、删除按钮,支持按时间或功能码筛选。
图表:实时更新寄存器值曲线。
功能:
存储:串口和 Modbus 数据自动存入数据库。
查询:支持按时间范围、功能码或地址查询。
删除:允许删除指定记录。
中文支持:确保数据库和 GUI 正确处理中文(如标签、日志)。
Modbus 功能码:
保持 0x01(读线圈)、0x03(读寄存器)、0x05(写线圈)、0x06(写寄存器)。
场景:调试工具记录设备数据到数据库,实时查询和分析,支持中文界面。
优化:异步 I/O(QTcpSocket)、线程池(QThreadPool)、数据库事务提升性能。
2. 前置类调整
以下调整 SerialPort(保持不变)、ModbusConverter(不变)、ReferenceClass、PointerClass 和 ModbusTCPDebugger,以支持数据库存储。
ReferenceClass(添加数据库存储)
cpp
#include
#include
#include
#include
#include
#include
#include
#include
#include "SerialPort.h"
class ReferenceClass : public QObject {
Q_OBJECT
private:
std::vector& data;
std::string& buffer;
SerialPort& serial;
QSqlDatabase db;
public:
ReferenceClass(std::vector& d, std::string& b, SerialPort& s, QObject* parent = nullptr)
: QObject(parent), data(d), buffer(b), serial(s) {
if (data.empty()) {
throw std::runtime_error("数据向量不能为空");
}
db = QSqlDatabase::addDatabase("QSQLITE", "serial_connection");
db.setDatabaseName("debug_data.db");
if (!db.open()) {
throw std::runtime_error("数据库打开失败: " + db.lastError().text().toStdString());
}
QSqlQuery query(db);
query.exec("CREATE TABLE IF NOT EXISTS serial_data ("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"timestamp TEXT, "
"type TEXT, "
"data TEXT)");
}
~ReferenceClass() {
db.close();
}
void asyncReadFromSerial(std::function&)> callback) {
serial.asyncRead(data, [this, callback](const std::vector& serialData) {
std::string str(serialData.begin(), serialData.end());
buffer += str;
std::string num;
for (char c : str) {
if (c == ' ') {
if (!num.empty()) data.push_back(std::stoi(num));
num.clear();
} else {
num += c;
}
}
if (!num.empty()) data.push_back(std::stoi(num));
saveToDatabase("接收", serialData);
callback(serialData);
emit dataUpdated();
});
}
void writeToSerial(const std::string& command) {
std::vector data(command.begin(), command.end());
serial.write(data);
buffer += "发送: " + command;
saveToDatabase("发送", data);
emit dataUpdated();
}
const std::vector& getData() const { return data; }
const std::string& getBuffer() const { return buffer; }
signals:
void dataUpdated();
private:
void saveToDatabase(const std::string& type, const std::vector& data) {
QSqlQuery query(db);
query.prepare("INSERT INTO serial_data (timestamp, type, data) VALUES (:timestamp, :type, :data)");
query.bindValue(":timestamp", QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"));
query.bindValue(":type", QString::fromStdString(type));
QString hexData;
for (auto byte : data) {
hexData += QString("%1 ").arg(byte, 2, 16, QChar('0')).toUpper();
}
query.bindValue(":data", hexData);
if (!query.exec()) {
qWarning() << "数据库插入失败:" << query.lastError().text();
}
}
};
调整:
初始化 SQLite 数据库(debug_data.db),创建 serial_data 表。
saveToDatabase:存储时间戳、类型(发送/接收)、HEX 数据。
支持中文(接收、发送)。
PointerClass(支持数据库存储)
cpp
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
class PointerClass : public QObject {
Q_OBJECT
private:
std::vector* data;
std::string* buffer;
std::unique_ptr dynamicArray;
size_t arraySize;
QTcpSocket* tcpSocket;
uint16_t transactionId;
QSqlDatabase db;
public:
PointerClass(std::vector* d, std::string* b, size_t size, const std::string& host, int port, QObject* parent = nullptr)
: QObject(parent), data(d), buffer(b), arraySize(size), tcpSocket(new QTcpSocket(this)), transactionId(0) {
if (!data || !buffer) {
throw std::runtime_error("指针不能为空");
}
dynamicArray = std::unique_ptr(new int[size]);
for (size_t i = 0; i < size; ++i) {
dynamicArray[i] = static_cast(i);
}
db = QSqlDatabase::addDatabase("QSQLITE", "modbus_connection");
db.setDatabaseName("debug_data.db");
if (!db.open()) {
throw std::runtime_error("数据库打开失败: " + db.lastError().text().toStdString());
}
QSqlQuery query(db);
query.exec("CREATE TABLE IF NOT EXISTS modbus_data ("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"timestamp TEXT, "
"type TEXT, "
"function_code TEXT, "
"data TEXT)");
tcpSocket->connectToHost(QString::fromStdString(host), port);
connect(tcpSocket, &QTcpSocket::connected, this, &PointerClass::onConnected);
connect(tcpSocket, &QTcpSocket::readyRead, this, &PointerClass::onReadyRead);
connect(tcpSocket, &QTcpSocket::disconnected, this, &PointerClass::onDisconnected);
}
~PointerClass() {
db.close();
}
void asyncModbusRequest(uint16_t functionCode, uint16_t startAddr, uint16_t countOrValue,
std::function&)> callback) {
if (tcpSocket->state() != QAbstractSocket::ConnectedState) {
emit errorOccurred("未连接");
return;
}
std::vector frame = createModbusFrame(functionCode, startAddr, countOrValue);
tcpSocket->write(QByteArray(reinterpret_cast(frame.data()), frame.size()));
*buffer += "发送 Modbus: ";
for (auto it = frame.begin(); it != frame.end(); ++it) {
*buffer += std::to_string(static_cast(*it) & 0xFF) + " ";
}
saveToDatabase("发送", functionCode, frame);
connect(this, &PointerClass::responseReceived, this, [=](const std::vector& data) {
saveToDatabase("接收", functionCode, data);
callback(data);
emit dataUpdated();
}, Qt::SingleShotConnection);
}
void updateDynamicArray(size_t index, int value) {
if (index >= arraySize) {
throw std::out_of_range("动态数组索引越界");
}
dynamicArray[index] = value;
}
std::vector parseModbusResponse(const std::vector& response) {
std::vector values;
if (response.size() >= 9) {
uint8_t functionCode = response[7];
if (functionCode == 0x03) {
for (size_t i = 0; i < response[8] / 2; ++i) {
values.push_back((response[9 + 2 * i] << 8) | response[9 + 2 * i + 1]);
data->push_back(values.back());
}
} else if (functionCode == 0x01) {
for (size_t i = 0; i < response[8]; ++i) {
uint8_t byte = response[9 + i];
for (size_t j = 0; j < 8; ++j) {
values.push_back((byte >> j) & 1);
data->push_back(values.back());
}
}
} else if (functionCode == 0x05 || functionCode == 0x06) {
values.push_back((response[10] << 8) | response[11]);
data->push_back(values.back());
}
}
return values;
}
const std::vector& getData() const { return *data; }
const std::string& getBuffer() const { return *buffer; }
signals:
void dataUpdated();
void responseReceived(const std::vector& data);
void errorOccurred(const QString& error);
void connectionStateChanged(bool connected);
private slots:
void onConnected() { emit connectionStateChanged(true); }
void onDisconnected() { emit connectionStateChanged(false); }
void onReadyRead() {
QByteArray response = tcpSocket->readAll();
std::vector data(response.begin(), response.end());
emit responseReceived(data);
}
private:
std::vector createModbusFrame(uint16_t functionCode, uint16_t startAddr, uint16_t countOrValue) {
std::vector frame(12);
transactionId++;
frame[0] = (transactionId >> 8) & 0xFF;
frame[1] = transactionId & 0xFF;
frame[2] = 0x00;
frame[3] = 0x00;
frame[4] = 0x00;
frame[5] = 0x06;
frame[6] = 0x01;
frame[7] = functionCode;
frame[8] = (startAddr >> 8) & 0xFF;
frame[9] = startAddr & 0xFF;
frame[10] = (countOrValue >> 8) & 0xFF;
frame[11] = countOrValue & 0xFF;
return frame;
}
void saveToDatabase(const std::string& type, uint16_t functionCode, const std::vector& data) {
QSqlQuery query(db);
query.prepare("INSERT INTO modbus_data (timestamp, type, function_code, data) VALUES (:timestamp, :type, :function_code, :data)");
query.bindValue(":timestamp", QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"));
query.bindValue(":type", QString::fromStdString(type));
query.bindValue(":function_code", QString("0x%1").arg(functionCode, 2, 16, QChar('0')).toUpper());
QString hexData;
for (auto byte : data) {
hexData += QString("%1 ").arg(byte, 2, 16, QChar('0')).toUpper();
}
query.bindValue(":data", hexData);
if (!query.exec()) {
qWarning() << "数据库插入失败:" << query.lastError().text();
}
}
};
调整:
初始化 SQLite 数据库,创建 modbus_data 表。
saveToDatabase:存储时间戳、类型、功能码、HEX 数据。
支持中文(发送、接收、未连接)。
ModbusTCPDebugger(保持不变)
使用前文的实现,支持 0x01、0x03、0x05、0x06,无需调整。
3. 扩展的 Qt GUI(添加数据库查询)
cpp
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "ReferenceClass.h"
#include "PointerClass.h"
#include "ModbusTCPDebugger.h"
#include "ModbusConverter.h"
class DebugWindow : public QMainWindow {
Q_OBJECT
private:
ReferenceClass* refClass;
PointerClass* ptrClass;
QTextEdit* logText;
QLineEdit* commandInput;
QComboBox* commandType;
QTableView* dataTable;
QSqlTableModel* tableModel;
QTreeView* deviceTree;
QStandardItemModel* treeModel;
QChart* chart;
QChartView* chartView;
QLineSeries* regSeries;
QTimer* updateTimer;
QLabel* statusLabel;
QDateTimeEdit* startTimeEdit;
QLineEdit* filterInput;
std::vector lastRegValues;
QSqlDatabase db;
public:
DebugWindow(ReferenceClass* ref, PointerClass* ptr, QWidget* parent = nullptr)
: QMainWindow(parent), refClass(ref), ptrClass(ptr) {
setWindowTitle("调试工具");
resize(1200, 800);
QWidget* centralWidget = new QWidget(this);
setCentralWidget(centralWidget);
QVBoxLayout* mainLayout = new QVBoxLayout(centralWidget);
// 数据库
db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName("debug_data.db");
if (!db.open()) {
logText->append("数据库打开失败: " + db.lastError().text());
}
// 状态标签
statusLabel = new QLabel("未连接", this);
mainLayout->addWidget(statusLabel);
// 日志区域
logText = new QTextEdit(this);
logText->setReadOnly(true);
mainLayout->addWidget(logText);
// 设备树和表格布局
QHBoxLayout* dataLayout = new QHBoxLayout;
deviceTree = new QTreeView(this);
treeModel = new QStandardItemModel(this);
treeModel->setHorizontalHeaderLabels({"设备数据"});
deviceTree->setModel(treeModel);
dataLayout->addWidget(deviceTree);
dataTable = new QTableView(this);
tableModel = new QSqlTableModel(this, db);
tableModel->setTable("modbus_data");
tableModel->setHeaderData(1, Qt::Horizontal, "时间");
tableModel->setHeaderData(2, Qt::Horizontal, "类型");
tableModel->setHeaderData(3, Qt::Horizontal, "功能码");
tableModel->setHeaderData(4, Qt::Horizontal, "数据");
tableModel->select();
dataTable->setModel(tableModel);
dataTable->setColumnHidden(0, true); // 隐藏 ID
dataLayout->addWidget(dataTable);
mainLayout->addLayout(dataLayout);
// 图表
chart = new QtCharts::QChart;
regSeries = new QtCharts::QLineSeries;
chart->addSeries(regSeries);
chart->createDefaultAxes();
chart->setTitle("寄存器值");
chartView = new QtCharts::QChartView(chart);
chartView->setRenderHint(QPainter::Antialiasing);
mainLayout->addWidget(chartView);
// 查询和删除
QHBoxLayout* queryLayout = new QHBoxLayout;
startTimeEdit = new QDateTimeEdit(this);
startTimeEdit->setDateTime(QDateTime::currentDateTime().addDays(-1));
filterInput = new QLineEdit(this);
filterInput->setPlaceholderText("输入功能码 (如 0x03)");
QPushButton* queryButton = new QPushButton("查询", this);
QPushButton* deleteButton = new QPushButton("删除选中", this);
queryLayout->addWidget(new QLabel("开始时间:"));
queryLayout->addWidget(startTimeEdit);
queryLayout->addWidget(new QLabel("功能码:"));
queryLayout->addWidget(filterInput);
queryLayout->addWidget(queryButton);
queryLayout->addWidget(deleteButton);
mainLayout->addLayout(queryLayout);
// 命令输入
QHBoxLayout* inputLayout = new QHBoxLayout;
commandType = new QComboBox(this);
commandType->addItems({"串口", "Modbus 读线圈 (0x01)", "Modbus 读寄存器 (0x03)", "Modbus 写线圈 (0x05)", "Modbus 写寄存器 (0x06)"});
commandInput = new QLineEdit(this);
QPushButton* sendButton = new QPushButton("发送", this);
inputLayout->addWidget(commandType);
inputLayout->addWidget(commandInput);
inputLayout->addWidget(sendButton);
mainLayout->addLayout(inputLayout);
// 定时器
updateTimer = new QTimer(this);
updateTimer->start(500);
// 信号与槽
connect(sendButton, &QPushButton::clicked, this, &DebugWindow::sendCommand);
connect(queryButton, &QPushButton::clicked, this, &DebugWindow::queryData);
connect(deleteButton, &QPushButton::clicked, this, &DebugWindow::deleteSelected);
connect(updateTimer, &QTimer::timeout, this, &DebugWindow::updateUI);
connect(refClass, &ReferenceClass::dataUpdated, this, &DebugWindow::updateUI);
connect(ptrClass, &PointerClass::dataUpdated, this, &DebugWindow::updateUI);
connect(ptrClass, &PointerClass::connectionStateChanged, this, &DebugWindow::updateStatus);
connect(ptrClass, &PointerClass::errorOccurred, this, &DebugWindow::showError);
// 初始请求
refClass->asyncReadFromSerial([this](const std::vector& data) {
logText->append("串口: " + QString::fromStdString(formatData(data)));
});
ptrClass->asyncModbusRequest(0x03, 0, 4, [this](const std::vector& data) {
logText->append("Modbus: " + QString::fromStdString(formatData(data)));
});
updateTree();
}
private slots:
void sendCommand() {
std::string cmd = commandInput->text().toStdString();
std::stringstream ss(cmd);
std::string type = commandType->currentText().toStdString();
if (type == "串口") {
refClass->writeToSerial(cmd + "\n");
logText->append("发送串口: " + QString::fromStdString(cmd));
} else if (type == "Modbus 读线圈 (0x01)") {
int start, count;
ss >> start >> count;
ptrClass->asyncModbusRequest(0x01, start, count, [this](const std::vector& data) {
logText->append("Modbus 线圈: " + QString::fromStdString(formatData(data)));
});
} else if (type == "Modbus 读寄存器 (0x03)") {
int start, count;
ss >> start >> count;
ptrClass->asyncModbusRequest(0x03, start, count, [this](const std::vector& data) {
logText->append("Modbus 寄存器: " + QString::fromStdString(formatData(data)));
});
} else if (type == "Modbus 写线圈 (0x05)") {
int addr, value;
ss >> addr >> value;
ptrClass->asyncModbusRequest(0x05, addr, value ? 0xFF00 : 0x0000, [this](const std::vector& data) {
logText->append("Modbus 写线圈: " + QString::fromStdString(formatData(data)));
});
std::vector tcpFrame = ptrClass->createModbusFrame(0x05, addr, value ? 0xFF00 : 0x0000);
std::vector rtuFrame = ModbusConverter::tcpToRtu(tcpFrame);
refClass->writeToSerial(std::string(rtuFrame.begin(), rtuFrame.end()));
} else if (type == "Modbus 写寄存器 (0x06)") {
int addr, value;
ss >> addr >> value;
ptrClass->asyncModbusRequest(0x06, addr, value, [this](const std::vector& data) {
logText->append("Modbus 写寄存器: " + QString::fromStdString(formatData(data)));
});
std::vector tcpFrame = ptrClass->createModbusFrame(0x06, addr, value);
std::vector rtuFrame = ModbusConverter::tcpToRtu(tcpFrame);
refClass->writeToSerial(std::string(rtuFrame.begin(), rtuFrame.end()));
}
commandInput->clear();
updateTree();
}
void queryData() {
QString filter = QString("timestamp >= '%1'").arg(startTimeEdit->dateTime().toString("yyyy-MM-dd hh:mm:ss"));
if (!filterInput->text().isEmpty()) {
filter += QString(" AND function_code = '%1'").arg(filterInput->text());
}
tableModel->setFilter(filter);
tableModel->select();
}
void deleteSelected() {
QItemSelectionModel* selection = dataTable->selectionModel();
QModelIndexList indices = selection->selectedRows();
for (const auto& index : indices) {
tableModel->removeRow(index.row());
}
tableModel->submitAll();
tableModel->select();
}
void updateUI() {
tableModel->select();
updateChart(ptrClass->getData());
updateTree();
}
void updateStatus(bool connected) {
statusLabel->setText(connected ? "已连接" : "未连接");
statusLabel->setStyleSheet(connected ? "color: green" : "color: red");
}
void showError(const QString& error) {
logText->append("错误: " + error);
}
private:
void updateTree() {
treeModel->clear();
treeModel->setHorizontalHeaderLabels({"设备数据"});
QStandardItem* root = new QStandardItem("设备");
QStandardItem* serialNode = new QStandardItem("串口数据");
QStandardItem* modbusNode = new QStandardItem("Modbus 数据");
root->appendRow(serialNode);
root->appendRow(modbusNode);
// 串口数据
const auto& serialData = refClass->getData();
for (size_t i = 0; i < serialData.size(); ++i) {
serialNode->appendRow(new QStandardItem(QString("值 %1: %2").arg(i).arg(serialData[i])));
}
// Modbus 数据
QStandardItem* regNode = new QStandardItem("寄存器");
QStandardItem* coilNode = new QStandardItem("线圈");
modbusNode->appendRow(regNode);
modbusNode->appendRow(coilNode);
const auto& modbusData = ptrClass->getData();
for (size_t i = 0; i < std::min(modbusData.size(), 4); ++i) {
regNode->appendRow(new QStandardItem(QString("寄存器 %1: %2").arg(i).arg(modbusData[i])));
}
ptrClass->asyncModbusRequest(0x01, 0, 8, [this](const std::vector& data) {
auto values = ptrClass->parseModbusResponse(data);
QStandardItem* coilNode = treeModel->item(0)->child(1)->child(1);
for (size_t i = 0; i < values.size(); ++i) {
coilNode->appendRow(new QStandardItem(QString("线圈 %1: %2").arg(i).arg(values[i])));
}
});
treeModel->appendRow(root);
deviceTree->expandAll();
}
void updateChart(const std::vector& values) {
if (values == lastRegValues) return;
lastRegValues = values;
regSeries->clear();
for (size_t i = 0; i < values.size(); ++i) {
regSeries->append(i, values[i]);
}
chart->axes(Qt::Horizontal)->first()->setRange(0, values.size());
chart->axes(Qt::Vertical)->first()->setRange(0, *std::max_element(values.begin(), values.end()) + 100);
}
std::string formatData(const std::vector& data) {
std::stringstream ss;
for (auto byte : data) {
ss << std::hex << std::setw(2) << std::setfill('0') << (static_cast(byte) & 0xFF) << " ";
}
return ss.str();
}
};
4. Main 函数
cpp
#include
#include "ReferenceClass.h"
#include "PointerClass.h"
#include "ModbusTCPDebugger.h"
#include "ModbusConverter.h"
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
try {
std::vector data = {10, 20, 30};
std::string buffer = "初始";
SerialPort serial("COM3", CBR_9600);
ModbusTCPDebugger server("0.0.0.0", 502);
ReferenceClass refClass(data, buffer, serial);
PointerClass ptrClass(&data, &buffer, 3, "127.0.0.1", 502);
DebugWindow window(&refClass, &ptrClass);
window.show();
return app.exec();
} catch (const std::exception& e) {
std::cerr << "错误: " << e.what() << std::endl;
return 1;
}
}
5. 功能说明
数据库集成:
表结构:
serial_data:id(主键)、timestamp(时间戳)、type(发送/接收)、data(HEX)。
modbus_data:id、timestamp、type、function_code(如 0x03)、data。
操作:
插入:saveToDatabase 自动记录数据。
查询:通过 QSqlTableModel 按时间或功能码筛选。
删除:选中 QTableView 行后删除记录。
中文支持:使用 QString::fromStdString 确保 UTF-8 编码。
GUI:
QTreeView:显示设备 > 串口数据/Modbus 数据 > 值/寄存器/线圈。
QTableView:显示 modbus_data 表,支持筛选和删除。
查询控件:QDateTimeEdit 设置开始时间,QLineEdit 输入功能码。
图表:实时更新寄存器曲线。
命令输入:支持串口命令和 Modbus 请求(0x01、0x03、0x05、0x06)。
TCP-to-RTU:
0x05 和 0x06 请求生成 RTU 帧,通过串口发送。
数据结构:
std::vector
std::string 存储缓冲区。
std::unique_ptr 管理动态数组。
6. 输出示例
假设串口返回 "1 2 3",Modbus 返回寄存器 {0x1234, 0x5678},线圈 {0, 1}:
日志区域:
串口: 01 02 03
Modbus: 00 01 00 00 00 07 01 03 04 12 34 56 78
Modbus 线圈: 00 02 00 00 00 04 01 01 01 02
树形视图:
设备
├── 串口数据
│ ├── 值 0: 10
│ ├── 值 1: 20
│ ├── 值 2: 30
│ ├── 值 3: 1
│ ├── 值 4: 2
│ └── 值 5: 3
└── Modbus 数据
├── 寄存器
│ ├── 寄存器 0: 4660
│ └── 寄存器 1: 22136
└── 线圈
├── 线圈 0: 0
└── 线圈 1: 1
表格(QTableView):
时间 类型 功能码 数据
2025-06-03 19:31:00 发送 0x03 00 01 00 00 00 06 01 03 00 00 00 04
2025-06-03 19:31:00 接收 0x03 00 01 00 00 00 07 01 03 04 12 34 56 78
图表:显示曲线(4660, 22136)。
数据库(debug_data.db):
serial_data:存储 "01 02 03" 等。
modbus_data:存储功能码和 HEX 数据。
7. Linux 适配
QtSql:确保安装 libqt5sql5-sqlite。
SerialPort:替换 Windows API 为 POSIX(open, read, write)。
QtNetwork:跨平台,无需修改。
编译:
g++ -o app main.cpp ReferenceClass.cpp PointerClass.cpp SerialPort.cpp ModbusTCPDebugger.cpp ModbusConverter.cpp -std=c++11 -pthread $(pkg-config --cflags --libs Qt5Widgets Qt5Charts Qt5Network Qt5Sql)
桥接:使用 socat:
socat TCP-LISTEN:12345,reuseaddr /dev/ttyUSB0,raw,echo=0,b9600
8. 扩展建议
其他数据库:支持 MySQL 或 PostgreSQL(替换 QSQLITE 为 QMYSQL/QPSQL)。
QTreeView 增强:支持编辑寄存器/线圈,触发数据库更新。
查询增强:添加结束时间筛选、数据导出为 CSV。
更多功能码:
0x0F:写多个线圈。
0x10:写多个寄存器。
9. 注意事项
中文编码:确保源文件为 UTF-8,数据库使用 UTF-8 编码。
线程安全:QSqlDatabase 需为每个连接分配独立实例(通过 addDatabase 的连接名)。
性能:大数据量时,使用数据库索引或限制查询范围。
依赖:安装 libqt5sql5-sqlite。
如果你需要 MySQL 支持、QTreeView 编辑功能、CSV 导出,或更多功能码(如 0x0F),请提供细节,我可以进一步定制!