扩展前文的 Qt GUI 调试工具,添加 QTreeView 控件以显示设备数据的层次结构,支持更多 Modbus 功能码

扩展前文的 Qt GUI 调试工具,添加 QTreeView 控件以显示设备数据的层次结构,支持更多 Modbus 功能码(新增 0x05 写单个线圈),并实现详细的 CSV 日志保存功能。代码将基于 Qt 网络编程(QtNetwork),集成 ReferenceClass、PointerClass、SerialPort、ModbusTCPDebugger 和 ModbusConverter,使用 std::string、std::vector、C 风格数组和迭代器,保持异步 I/O 和线程池优化。GUI 将显示串口和 Modbus 数据(功能码 0x01、0x03、0x05、0x06),支持 TCP-to-RTU 转换。代码基于 Windows,提供 Linux 适配说明,Demo 通过 main 函数启动 GUI。

1. 设计目标

  • QTreeView 控件:

    • 显示设备数据层次结构(如设备 > 寄存器/线圈 > 值)。

    • 支持动态更新,反映串口和 Modbus 数据。

  • Modbus 功能码:

    • 新增 0x05:写单个线圈。

    • 保持 0x01(读线圈)、0x03(读寄存器)、0x06(写寄存器)。

  • CSV 日志保存:

    • 将串口和 Modbus 数据(时间戳、HEX、解析值)保存为 CSV 文件。

    • 支持手动触发保存或自动记录。

  • 集成:

    • ReferenceClass:处理串口数据。

    • PointerClass:处理 Modbus TCP 数据。

    • ModbusTCPDebugger:支持新功能码。

    • ModbusConverter:TCP-to-RTU 转换。

  • 场景:调试工具监控设备状态,发送控制命令,保存日志。

  • 优化:异步 I/O(QTcpSocket)、线程池(QThreadPool)、GUI 实时更新。

2. 前置类调整

以下调整 SerialPort(保持不变)、ModbusConverter(不变)、ReferenceClass、PointerClass 和 ModbusTCPDebugger,支持新功能。

ReferenceClass(添加 CSV 日志)

cpp

#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;
    QFile logFile;

public:
    ReferenceClass(std::vector& d, std::string& b, SerialPort& s, QObject* parent = nullptr)
        : QObject(parent), data(d), buffer(b), serial(s), logFile("serial_log.csv") {
        if (data.empty()) {
            throw std::runtime_error("ReferenceClass: Data vector cannot be empty");
        }
        if (!logFile.open(QIODevice::WriteOnly | QIODevice::Append)) {
            throw std::runtime_error("Failed to open serial log file");
        }
        if (logFile.size() == 0) {
            QTextStream out(&logFile);
            out << "Timestamp,Type,Data\n";
        }
    }

    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));
            logToCSV("Received", serialData);
            callback(serialData);
            emit dataUpdated();
        });
    }

    void writeToSerial(const std::string& command) {
        std::vector data(command.begin(), command.end());
        serial.write(data);
        buffer += "Sent: " + command;
        logToCSV("Sent", data);
        emit dataUpdated();
    }

    const std::vector& getData() const { return data; }
    const std::string& getBuffer() const { return buffer; }

signals:
    void dataUpdated();

private:
    void logToCSV(const std::string& type, const std::vector& data) {
        QTextStream out(&logFile);
        QString hexData;
        for (auto byte : data) {
            hexData += QString("%1 ").arg(byte, 2, 16, QChar('0')).toUpper();
        }
        out << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << ","
            << QString::fromStdString(type) << ","
            << hexData << "\n";
        logFile.flush();
    }
};

调整:

  • 添加 CSV 日志文件(serial_log.csv)。

  • logToCSV 记录时间戳、类型(发送/接收)、HEX 数据。

PointerClass(支持 0x05)

cpp

#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;
    QFile logFile;

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), logFile("modbus_log.csv") {
        if (!data || !buffer) {
            throw std::runtime_error("PointerClass: Null pointer provided");
        }
        dynamicArray = std::unique_ptr(new int[size]);
        for (size_t i = 0; i < size; ++i) {
            dynamicArray[i] = static_cast(i);
        }
        if (!logFile.open(QIODevice::WriteOnly | QIODevice::Append)) {
            throw std::runtime_error("Failed to open modbus log file");
        }
        if (logFile.size() == 0) {
            QTextStream out(&logFile);
            out << "Timestamp,Type,FunctionCode,Data\n";
        }
        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);
    }

    void asyncModbusRequest(uint16_t functionCode, uint16_t startAddr, uint16_t countOrValue,
                           std::function&)> callback) {
        if (tcpSocket->state() != QAbstractSocket::ConnectedState) {
            emit errorOccurred("Not connected");
            return;
        }
        std::vector frame = createModbusFrame(functionCode, startAddr, countOrValue);
        tcpSocket->write(QByteArray(reinterpret_cast(frame.data()), frame.size()));
        *buffer += "Sent Modbus: ";
        for (auto it = frame.begin(); it != frame.end(); ++it) {
            *buffer += std::to_string(static_cast(*it) & 0xFF) + " ";
        }
        logToCSV("Sent", functionCode, frame);
        connect(this, &PointerClass::responseReceived, this, [=](const std::vector& data) {
            logToCSV("Received", functionCode, data);
            callback(data);
            emit dataUpdated();
        }, Qt::SingleShotConnection);
    }

    void updateDynamicArray(size_t index, int value) {
        if (index >= arraySize) {
            throw std::out_of_range("Dynamic array index 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 logToCSV(const std::string& type, uint16_t functionCode, const std::vector& data) {
        QTextStream out(&logFile);
        QString hexData;
        for (auto byte : data) {
            hexData += QString("%1 ").arg(byte, 2, 16, QChar('0')).toUpper();
        }
        out << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << ","
            << QString::fromStdString(type) << ","
            << QString("0x%1").arg(functionCode, 2, 16, QChar('0')).toUpper() << ","
            << hexData << "\n";
        logFile.flush();
    }
};

调整:

  • 支持 0x05 响应解析。

  • 添加 CSV 日志(modbus_log.csv),记录功能码和数据。

ModbusTCPDebugger(支持 0x05)

cpp

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

class ModbusTCPDebugger : public QObject {
    Q_OBJECT
private:
    QTcpServer* server;
    std::vector coils = std::vector(100, false);
    std::vector registers = {0x1234, 0x5678, 0x9ABC, 0xDEF0};
    std::mutex dataMutex;
    std::ofstream logFile;
    QThreadPool* threadPool;

public:
    ModbusTCPDebugger(const std::string& host, int port, QObject* parent = nullptr)
        : QObject(parent), server(new QTcpServer(this)), logFile("modbus_server_log.txt", std::ios::app), threadPool(new QThreadPool(this)) {
        threadPool->setMaxThreadCount(4);
        if (!server->listen(QHostAddress(QString::fromStdString(host)), port)) {
            throw std::runtime_error("Server failed to listen");
        }
        connect(server, &QTcpServer::newConnection, this, &ModbusTCPDebugger::handleNewConnection);
    }

private slots:
    void handleNewConnection() {
        QTcpSocket* clientSocket = server->nextPendingConnection();
        connect(clientSocket, &QTcpSocket::readyRead, this, [=]() {
            class ClientTask : public QRunnable {
                QTcpSocket* socket;
                ModbusTCPDebugger* debugger;
            public:
                ClientTask(QTcpSocket* s, ModbusTCPDebugger* d) : socket(s), debugger(d) {}
                void run() override {
                    QByteArray data = socket->readAll();
                    std::vector buffer(data.begin(), data.end());
                    debugger->logData("Received: ", buffer);
                    std::vector response;
                    {
                        std::lock_guard lock(debugger->dataMutex);
                        response = debugger->processModbusFrame(buffer);
                    }
                    if (!response.empty()) {
                        socket->write(QByteArray(reinterpret_cast(response.data()), response.size()));
                        debugger->logData("Sent: ", response);
                    }
                }
            };
            threadPool->start(new ClientTask(clientSocket, this));
        });
        connect(clientSocket, &QTcpSocket::disconnected, clientSocket, &QTcpSocket::deleteLater);
    }

private:
    std::vector processModbusFrame(const std::vector& frame) {
        if (frame.size() < 8) return {};
        uint16_t transactionId = (frame[0] << 8) | frame[1];
        uint16_t protocolId = (frame[2] << 8) | frame[3];
        uint16_t length = (frame[4] << 8) | frame[5];
        uint8_t unitId = frame[6];
        uint8_t functionCode = frame[7];

        if (protocolId != 0x0000 || length != frame.size() - 6) return {};

        if (functionCode == 0x01 && frame.size() >= 12) { // 读线圈
            uint16_t startAddr = (frame[8] << 8) | frame[9];
            uint16_t count = (frame[10] << 8) | frame[11];
            if (startAddr + count <= coils.size()) {
                size_t byteCount = (count + 7) / 8;
                std::vector response(9 + byteCount);
                response[0] = frame[0]; response[1] = frame[1];
                response[2] = 0x00; response[3] = 0x00;
                response[4] = 0x00; response[5] = 3 + byteCount;
                response[6] = unitId; response[7] = functionCode;
                response[8] = byteCount;
                for (size_t i = 0; i < byteCount; ++i) {
                    uint8_t byte = 0;
                    for (size_t j = 0; j < 8 && (i * 8 + j) < count; ++j) {
                        byte |= (coils[startAddr + i * 8 + j] ? 1 : 0) << j;
                    }
                    response[9 + i] = byte;
                }
                return response;
            }
        } else if (functionCode == 0x03 && frame.size() >= 12) { // 读寄存器
            uint16_t startAddr = (frame[8] << 8) | frame[9];
            uint16_t count = (frame[10] << 8) | frame[11];
            if (startAddr + count <= registers.size()) {
                std::vector response(9 + 2 * count);
                response[0] = frame[0]; response[1] = frame[1];
                response[2] = 0x00; response[3] = 0x00;
                response[4] = 0x00; response[5] = 3 + 2 * count;
                response[6] = unitId; response[7] = functionCode;
                response[8] = 2 * count;
                for (size_t i = 0; i < count; ++i) {
                    response[9 + 2 * i] = (registers[startAddr + i] >> 8) & 0xFF;
                    response[9 + 2 * i + 1] = registers[startAddr + i] & 0xFF;
                }
                return response;
            }
        } else if (functionCode == 0x05 && frame.size() >= 12) { // 写单个线圈
            uint16_t addr = (frame[8] << 8) | frame[9];
            uint16_t value = (frame[10] << 8) | frame[11];
            if (addr < coils.size() && (value == 0x0000 || value == 0xFF00)) {
                coils[addr] = (value == 0xFF00);
                return frame;
            }
        } else if (functionCode == 0x06 && frame.size() >= 12) { // 写寄存器
            uint16_t addr = (frame[8] << 8) | frame[9];
            uint16_t value = (frame[10] << 8) | frame[11];
            if (addr < registers.size()) {
                registers[addr] = value;
                return frame;
            }
        }
        std::vector response(9);
        response[0] = frame[0]; response[1] = frame[1];
        response[2] = 0x00; response[3] = 0x00;
        response[4] = 0x00; response[5] = 0x03;
        response[6] = unitId; response[7] = functionCode | 0x80;
        response[8] = 0x02;
        return response;
    }

    void logData(const std::string& prefix, const std::vector& data) {
        std::lock_guard lock(dataMutex);
        logFile << prefix;
        for (auto it = data.begin(); it != data.end(); ++it) {
            logFile << std::hex << std::setw(2) << std::setfill('0') << (static_cast(*it) & 0xFF) << " ";
        }
        logFile << std::dec << "\n";
        logFile.flush();
    }
};

调整:

  • 支持 0x05:写单个线圈,设置 coils[addr] 为 true(0xFF00)或 false(0x0000)。

3. 扩展的 Qt GUI(添加 QTreeView)

cpp

#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;
    QTableWidget* dataTable;
    QTreeView* deviceTree;
    QStandardItemModel* treeModel;
    QChart* chart;
    QChartView* chartView;
    QLineSeries* regSeries;
    QTimer* updateTimer;
    QLabel* statusLabel;
    std::vector lastRegValues;

public:
    DebugWindow(ReferenceClass* ref, PointerClass* ptr, QWidget* parent = nullptr)
        : QMainWindow(parent), refClass(ref), ptrClass(ptr) {
        setWindowTitle("Qt Network Debug Tool");
        resize(1000, 800);
        QWidget* centralWidget = new QWidget(this);
        setCentralWidget(centralWidget);
        QVBoxLayout* mainLayout = new QVBoxLayout(centralWidget);

        // 状态标签
        statusLabel = new QLabel("Disconnected", 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({"Device Data"});
        deviceTree->setModel(treeModel);
        dataLayout->addWidget(deviceTree);
        dataTable = new QTableWidget(10, 3, this);
        dataTable->setHorizontalHeaderLabels({"Address", "Register", "Coil"});
        dataLayout->addWidget(dataTable);
        mainLayout->addLayout(dataLayout);

        // 图表
        chart = new QtCharts::QChart;
        regSeries = new QtCharts::QLineSeries;
        chart->addSeries(regSeries);
        chart->createDefaultAxes();
        chart->setTitle("Register Values");
        chartView = new QtCharts::QChartView(chart);
        chartView->setRenderHint(QPainter::Antialiasing);
        mainLayout->addWidget(chartView);

        // 命令输入
        QHBoxLayout* inputLayout = new QHBoxLayout;
        commandType = new QComboBox(this);
        commandType->addItems({"Serial", "Modbus Read Coils (0x01)", "Modbus Read Registers (0x03)", "Modbus Write Coil (0x05)", "Modbus Write Register (0x06)"});
        commandInput = new QLineEdit(this);
        QPushButton* sendButton = new QPushButton("Send", 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(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("Serial: " + 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 == "Serial") {
            refClass->writeToSerial(cmd + "\n");
            logText->append("Sent Serial: " + QString::fromStdString(cmd));
        } else if (type == "Modbus Read Coils (0x01)") {
            int start, count;
            ss >> start >> count;
            ptrClass->asyncModbusRequest(0x01, start, count, [this](const std::vector& data) {
                logText->append("Modbus Coils: " + QString::fromStdString(formatData(data)));
            });
        } else if (type == "Modbus Read Registers (0x03)") {
            int start, count;
            ss >> start >> count;
            ptrClass->asyncModbusRequest(0x03, start, count, [this](const std::vector& data) {
                logText->append("Modbus Registers: " + QString::fromStdString(formatData(data)));
            });
        } else if (type == "Modbus Write Coil (0x05)") {
            int addr, value;
            ss >> addr >> value;
            ptrClass->asyncModbusRequest(0x05, addr, value ? 0xFF00 : 0x0000, [this](const std::vector& data) {
                logText->append("Modbus Write Coil: " + QString::fromStdString(formatData(data)));
            });
            // TCP-to-RTU
            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 Write Register (0x06)") {
            int addr, value;
            ss >> addr >> value;
            ptrClass->asyncModbusRequest(0x06, addr, value, [this](const std::vector& data) {
                logText->append("Modbus Write Register: " + QString::fromStdString(formatData(data)));
            });
            // TCP-to-RTU
            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 updateUI() {
        updateTable();
        updateChart(ptrClass->getData());
        updateTree();
    }

    void updateStatus(bool connected) {
        statusLabel->setText(connected ? "Connected" : "Disconnected");
        statusLabel->setStyleSheet(connected ? "color: green" : "color: red");
    }

    void showError(const QString& error) {
        logText->append("Error: " + error);
    }

private:
    void updateTable() {
        const auto& data = ptrClass->getData();
        for (size_t i = 0; i < std::min(data.size(), 10); ++i) {
            dataTable->setItem(i, 0, new QTableWidgetItem(QString::number(i)));
            dataTable->setItem(i, 1, new QTableWidgetItem(QString::number(data[i])));
            ptrClass->asyncModbusRequest(0x01, i, 1, [this, i](const std::vector& data) {
                auto values = ptrClass->parseModbusResponse(data);
                if (!values.empty()) {
                    dataTable->setItem(i, 2, new QTableWidgetItem(QString::number(values[0])));
                }
            });
        }
    }

    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);
    }

    void updateTree() {
        treeModel->clear();
        treeModel->setHorizontalHeaderLabels({"Device Data"});
        QStandardItem* root = new QStandardItem("Device");
        QStandardItem* serialNode = new QStandardItem("Serial Data");
        QStandardItem* modbusNode = new QStandardItem("Modbus Data");
        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("Value %1: %2").arg(i).arg(serialData[i])));
        }

        // Modbus 数据
        QStandardItem* regNode = new QStandardItem("Registers");
        QStandardItem* coilNode = new QStandardItem("Coils");
        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("Reg %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("Coil %1: %2").arg(i).arg(values[i])));
            }
        });

        treeModel->appendRow(root);
        deviceTree->expandAll();
    }

    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 = "Initial";
        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 << "Error: " << e.what() << std::endl;
        return 1;
    }
}

5. 功能说明

  • QTreeView:

    • 结构:Device > Serial Data > Value X 和 Device > Modbus Data > Registers/Coils > Reg X/Coil X。

    • 动态更新:通过 updateTree 反映串口和 Modbus 数据。

    • 使用 QStandardItemModel 管理树形数据。

  • Modbus 功能码:

    • 0x05:输入 addr value(如 0 1 置线圈为 true),生成 0xFF00 或 0x0000。

    • 支持 TCP-to-RTU 转换,RTU 帧通过串口发送。

    • 0x01、0x03、0x06 保持不变。

  • CSV 日志:

    • serial_log.csv:记录串口数据(时间戳、类型、HEX)。

      Timestamp,Type,Data
      2025-06-03 19:19:00,Received,01 02 03
      2025-06-03 19:19:01,Sent,53 54 41 52 54 0A
    • modbus_log.csv:记录 Modbus 数据(时间戳、类型、功能码、HEX)。

      Timestamp,Type,FunctionCode,Data
      2025-06-03 19:19:00,Sent,0x03,00 01 00 00 00 06 01 03 00 00 00 04
      2025-06-03 19:19:00,Received,0x03,00 01 00 00 00 0B 01 03 08 12 34 56 78 9A BC DE F0
  • GUI:

    • 布局:状态标签、日志区域、树形视图/表格并列、图表、命令输入。

    • 交互:选择命令类型,输入参数(如 0 4 读 4 个寄存器),点击发送。

    • 实时更新:QTimer 和信号触发 updateUI。

6. 输出示例

假设串口返回 "1 2 3",Modbus 返回寄存器 {0x1234, 0x5678, 0x9ABC, 0xDEF0},线圈 {0, 1, 0, 0}:

  • 日志区域:

    Serial: 01 02 03
    Modbus Registers: 00 01 00 00 00 0B 01 03 08 12 34 56 78 9A BC DE F0
    Modbus Coils: 00 02 00 00 00 04 01 01 01 02
  • 树形视图:

    Device
    ├── Serial Data
    │   ├── Value 0: 10
    │   ├── Value 1: 20
    │   ├── Value 2: 30
    │   ├── Value 3: 1
    │   ├── Value 4: 2
    │   └── Value 5: 3
    └── Modbus Data
        ├── Registers
        │   ├── Reg 0: 4660
        │   ├── Reg 1: 22136
        │   ├── Reg 2: 39612
        │   └── Reg 3: 57088
        └── Coils
            ├── Coil 0: 0
            ├── Coil 1: 1
            ├── Coil 2: 0
            └── Coil 3: 0
  • 表格:

    Address | Register | Coil
    0       | 4660     | 0
    1       | 22136    | 1
    2       | 39612    | 0
    3       | 57088    | 0
  • 图表:显示寄存器曲线(4660, 22136, 39612, 57088)。

7. Linux 适配

  • Qt:确保安装 libqt5widgets5-dev, libqt5charts5-dev, libqt5network5-dev.

  • 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)
  • 桥接:使用 socat:

    socat TCP-LISTEN:12345,reuseaddr /dev/ttyUSB0,raw,echo=0,b9600

8. 扩展建议

  • QTreeView 增强:

    • 支持双击编辑寄存器/线圈值,触发 0x05/0x06。

    • 添加上下文菜单(右键保存节点数据)。

  • 更多功能码:

    • 0x0F:写多个线圈。

    • 0x10:写多个寄存器。

  • CSV 增强:

    • 支持按时间范围导出(使用 QDateTimeEdit)。

    • 添加解析值列(如寄存器值)。

  • 其他控件:

    • QProgressBar:显示数据传输进度。

    • QTabWidget:分页显示串口/Modbus 数据。

9. 注意事项

  • 线程安全:Qt 信号与槽确保 UI 主线程更新,std::mutex 保护服务器数据。

  • 性能:QTreeView 大数据时可使用自定义模型优化。

  • CSV 文件:定期关闭/重新打开避免占用。

如果你需要 QTreeView 编辑功能、更多功能码(如 0x0F)、CSV 按时间导出,或其他控件(如 QProgressBar),请提供细节,我可以进一步定制!

你可能感兴趣的:(C++,qt,开发语言)