From ead0b485ac23d431f045345233a22908733bebd9 Mon Sep 17 00:00:00 2001 From: lipengpeng Date: Fri, 8 Aug 2025 11:10:33 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=AF=84=E5=AD=98=E5=99=A8?= =?UTF-8?q?=E6=98=A0=E5=B0=84=E7=B1=BB=E5=92=8Cmodbus=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- coil.cpp | 16 ++++- coil.h | 1 + contact.cpp | 19 +++++- contact.h | 1 + editor.pro | 10 ++- item.cpp | 6 ++ item.h | 6 ++ mainwindow.cpp | 1 + mainwindow.h | 6 +- modbusmanager.cpp | 146 ++++++++++++++++++++++++++++++++++++++++++++ modbusmanager.h | 55 +++++++++++++++++ mygraphicsview.cpp | 45 +++++++++++--- mygraphicsview.h | 3 + plc.cpp | 10 +++ plc.h | 3 + registermanager.cpp | 44 +++++++++++++ registermanager.h | 28 +++++++++ 17 files changed, 385 insertions(+), 15 deletions(-) create mode 100644 modbusmanager.cpp create mode 100644 modbusmanager.h create mode 100644 registermanager.cpp create mode 100644 registermanager.h diff --git a/coil.cpp b/coil.cpp index 30b0c69..a1f46bc 100644 --- a/coil.cpp +++ b/coil.cpp @@ -11,6 +11,11 @@ void Coil::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWid { painter->setRenderHint(QPainter::Antialiasing); + if (registerValue_ > 0) { + painter->setBrush(Qt::green); // 激活状态 + } else { + painter->setBrush(Qt::black); // 未激活状态 + } if (type_ == "线圈") { // 绘制线圈样式: 两边线段+中间椭圆 painter->drawLine(-12, 0, -5, 0); @@ -28,8 +33,10 @@ void Coil::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWid painter->setFont(QFont("Arial", 8)); painter->setPen(Qt::black); // 在元件底部居中绘制寄存器ID - painter->drawText(boundingRect().adjusted(0, 20, 0, 0), - Qt::AlignCenter, registerId_); +// painter->drawText(boundingRect().adjusted(0, 20, 0, 0), +// Qt::AlignCenter, registerId_); + QString text = QString("%1: %2").arg(registerId()).arg(registerValue_); + painter->drawText(boundingRect(), Qt::AlignBottom | Qt::AlignHCenter, text); painter->restore(); } if (option->state & QStyle::State_Selected) { @@ -41,3 +48,8 @@ void Coil::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWid painter->drawRect(boundingRect()); } } + +bool Coil::state() const +{ + return registerValue_ > 0; +} diff --git a/coil.h b/coil.h index 58db3b7..f6f7c36 100644 --- a/coil.h +++ b/coil.h @@ -9,6 +9,7 @@ public: void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) override; + bool state() const override; }; #endif // COIL_H diff --git a/contact.cpp b/contact.cpp index 68a821d..da1472d 100644 --- a/contact.cpp +++ b/contact.cpp @@ -11,6 +11,11 @@ void Contact::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, Q { painter->setRenderHint(QPainter::Antialiasing); if (type_ == "常开") { + if (state()) { + painter->setPen(QPen(Qt::green, 2)); // 激活状态 + } else { + painter->setPen(QPen(Qt::black, 1)); // 未激活状态 + } painter->drawLine(-12, 0, -4, 0); painter->drawLine(-4, -8, -4, 8); painter->drawLine(4, -8, 4, 8); @@ -23,6 +28,11 @@ void Contact::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, Q painter->drawEllipse(QPointF(18, 0), 4, 4); // 右 } else if (type_ == "常闭") { + if (!state()) { + painter->setPen(QPen(Qt::green, 2)); // 激活状态 + } else { + painter->setPen(QPen(Qt::black, 1)); // 未激活状态 + } painter->drawLine(-15, -10, 15, 10); // 对角线 painter->drawLine(-12, 0, -4, 0); painter->drawLine(-4, -8, -4, 8); @@ -41,8 +51,8 @@ void Contact::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, Q painter->setFont(QFont("Arial", 8)); painter->setPen(Qt::black); // 在元件底部居中绘制寄存器ID - painter->drawText(boundingRect().adjusted(0, 20, 0, 0), - Qt::AlignCenter, registerId_); + QString text = QString("%1: %2").arg(registerId()).arg(registerValue_); + painter->drawText(boundingRect(), Qt::AlignBottom | Qt::AlignHCenter, text); painter->restore(); } if (option->state & QStyle::State_Selected) { @@ -54,3 +64,8 @@ void Contact::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, Q painter->drawRect(boundingRect()); } } + +bool Contact::state() const +{ + return registerValue_ > 0; +} diff --git a/contact.h b/contact.h index 0cb77a6..625f8cb 100644 --- a/contact.h +++ b/contact.h @@ -9,6 +9,7 @@ public: void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) override; + bool state() const override; }; #endif // CONTACT_H diff --git a/editor.pro b/editor.pro index 0a766e2..ab4e248 100644 --- a/editor.pro +++ b/editor.pro @@ -1,4 +1,6 @@ QT += core gui +QT += serialport +QT += serialbus greaterThan(QT_MAJOR_VERSION, 4): QT += widgets @@ -27,9 +29,11 @@ SOURCES += \ light.cpp \ main.cpp \ mainwindow.cpp \ + modbusmanager.cpp \ mygraphicsview.cpp \ plc.cpp \ - project.cpp + project.cpp \ + registermanager.cpp HEADERS += \ button.h \ @@ -42,9 +46,11 @@ HEADERS += \ item.h \ light.h \ mainwindow.h \ + modbusmanager.h \ mygraphicsview.h \ plc.h \ - project.h + project.h \ + registermanager.h FORMS += \ hmi.ui \ diff --git a/item.cpp b/item.cpp index 6f00f58..6d46328 100644 --- a/item.cpp +++ b/item.cpp @@ -58,6 +58,12 @@ QString Item::itemType() return type_; } +void Item::setRegisterValue(quint16 value) +{ + registerValue_ = value; + update(); // 触发重绘 +} + void Item::MenuActions(QMenu *menu) { menu->addAction("复制"); diff --git a/item.h b/item.h index 1f5d3bc..0e2a88c 100644 --- a/item.h +++ b/item.h @@ -23,6 +23,11 @@ public: QString itemType(); void setRegisterId(const QString& id) { registerId_ = id; } QString registerId() const { return registerId_; } + void setRegisterValue(quint16 value); + quint16 registerValue() const { return registerValue_; } + + // 添加状态属性(用于线圈、触点等) + virtual bool state() const { return false; } signals: @@ -39,6 +44,7 @@ protected: QString type_; QList connections_; QString registerId_; + quint16 registerValue_ = 0; }; #endif // ITEM_H diff --git a/mainwindow.cpp b/mainwindow.cpp index 8123370..290aaa1 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -30,6 +30,7 @@ MainWindow::MainWindow(QWidget *parent) connect(ui->action_save, &QAction::triggered, this, &MainWindow::saveProject); connect(ui->action_open, &QAction::triggered, this, &MainWindow::openProject); + plc_->applyProjectToScene(plcProject_); // 初始空 hmi_->applyProjectToScene(hmiProject_); } diff --git a/mainwindow.h b/mainwindow.h index ac8540a..e46b3b7 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -2,8 +2,10 @@ #define MAINWINDOW_H #include -#include -#include +#include "plc.h" +#include "hmi.h" +#include "modbusmanager.h" +#include "registermanager.h" QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } diff --git a/modbusmanager.cpp b/modbusmanager.cpp new file mode 100644 index 0000000..9306dce --- /dev/null +++ b/modbusmanager.cpp @@ -0,0 +1,146 @@ +#include "modbusmanager.h" +#include +#include + +ModbusManager::ModbusManager(RegisterManager* regManager, QObject *parent) + : QObject(parent), registerManager(regManager) +{ + modbusDevice = new QModbusRtuSerialMaster(this); + pollTimer = new QTimer(this); + + connect(modbusDevice, &QModbusClient::errorOccurred, this, + [this](QModbusDevice::Error) { + emit errorOccurred(modbusDevice->errorString()); + }); + + connect(pollTimer, &QTimer::timeout, this, &ModbusManager::readRegisters); +} + +ModbusManager::~ModbusManager() +{ + stopSimulation(); + disconnectDevice(); +} + +bool ModbusManager::connectToDevice(const QString& portName, + QSerialPort::BaudRate baudRate, + QSerialPort::DataBits dataBits, + QSerialPort::Parity parity, + QSerialPort::StopBits stopBits) +{ + if (modbusDevice->state() != QModbusDevice::UnconnectedState) { + disconnectDevice(); + } + + modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter, portName); + modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, baudRate); + modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, dataBits); + modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter, parity); + modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, stopBits); + + if (!modbusDevice->connectDevice()) { + emit errorOccurred(tr("连接失败: %1").arg(modbusDevice->errorString())); + return false; + } + + emit connectionStatusChanged(true); + return true; +} + +void ModbusManager::disconnectDevice() +{ + if (modbusDevice->state() != QModbusDevice::UnconnectedState) { + modbusDevice->disconnectDevice(); + emit connectionStatusChanged(false); + } +} + +void ModbusManager::startSimulation(int interval) +{ + pollTimer->start(interval); +} + +void ModbusManager::stopSimulation() +{ + pollTimer->stop(); +} + +void ModbusManager::readRegisters() +{ + if (!modbusDevice || modbusDevice->state() != QModbusDevice::ConnectedState) + return; + + // 读取所有D寄存器 (保持寄存器) + readRegisterGroup(QModbusDataUnit::HoldingRegisters, 0, 4000); + + // 读取所有M寄存器 (线圈) + readRegisterGroup(QModbusDataUnit::Coils, 0, 4000); +} + +void ModbusManager::readRegisterGroup(QModbusDataUnit::RegisterType type, int startAddr, int count) +{ + const int MAX_REG_PER_REQUEST = 125; // Modbus RTU限制 + + for (int addr = startAddr; addr < count; addr += MAX_REG_PER_REQUEST) { + int readCount = qMin(MAX_REG_PER_REQUEST, count - addr); + + QModbusDataUnit unit(type, addr, readCount); + + if (auto *reply = modbusDevice->sendReadRequest(unit, slaveAddress)) { + QString regType = (type == QModbusDataUnit::HoldingRegisters) ? "D" : "M"; + pendingRequests[reply] = regType; + connect(reply, &QModbusReply::finished, this, &ModbusManager::processModbusReply); + } + } +} + +void ModbusManager::processModbusReply() +{ + auto *reply = qobject_cast(sender()); + if (!reply) return; + + if (reply->error() == QModbusDevice::NoError) { + const QModbusDataUnit unit = reply->result(); + QString regType = pendingRequests.value(reply); + + int startAddr = unit.startAddress(); + + for (uint i = 0; i < unit.valueCount(); i++) { + int regAddr = startAddr + i; + quint16 value = unit.value(i); + + QString regId = QString("%1%2").arg(regType).arg(regAddr); + + // 更新图元状态 + registerManager->updateRegisterValue(regId, value); + } + } else { + emit errorOccurred(tr("Modbus错误: %1").arg(reply->errorString())); + } + + pendingRequests.remove(reply); + reply->deleteLater(); +} + +void ModbusManager::writeRegister(const QString& registerId, quint16 value) +{ + if (registerId.isEmpty()) return; + + QModbusDataUnit::RegisterType regType = registerId.startsWith("D") + ? QModbusDataUnit::HoldingRegisters + : QModbusDataUnit::Coils; + + int regAddr = registerId.mid(1).toInt(); + + QModbusDataUnit writeUnit(regType, regAddr, 1); + writeUnit.setValue(0, value); + + if (auto *reply = modbusDevice->sendWriteRequest(writeUnit, slaveAddress)) { + connect(reply, &QModbusReply::finished, this, [this, reply]() { + if (reply->error() != QModbusDevice::NoError) { + emit errorOccurred(tr("写入失败: %1").arg(reply->errorString())); + } + reply->deleteLater(); + }); + } +} diff --git a/modbusmanager.h b/modbusmanager.h new file mode 100644 index 0000000..000870b --- /dev/null +++ b/modbusmanager.h @@ -0,0 +1,55 @@ +#ifndef MODBUSMANAGER_H +#define MODBUSMANAGER_H + +#include +#include +#include +#include +#include +#include "registermanager.h" + +class ModbusManager : public QObject +{ + Q_OBJECT +public: + explicit ModbusManager(RegisterManager* regManager, QObject *parent = nullptr); + ~ModbusManager(); + + // 串口连接管理 + bool connectToDevice(const QString& portName, + QSerialPort::BaudRate baudRate = QSerialPort::Baud9600, + QSerialPort::DataBits dataBits = QSerialPort::Data8, + QSerialPort::Parity parity = QSerialPort::NoParity, + QSerialPort::StopBits stopBits = QSerialPort::OneStop); + + void disconnectDevice(); + + // 仿真控制 + void startSimulation(int interval = 500); // 毫秒 + void stopSimulation(); + + // 寄存器读写 + void writeRegister(const QString& registerId, quint16 value); + + // 设置从站地址 + void setSlaveAddress(int address) { slaveAddress = address; } + +signals: + void connectionStatusChanged(bool connected); + void errorOccurred(const QString& error); + +private slots: + void readRegisters(); + void processModbusReply(); + +private: + QModbusRtuSerialMaster *modbusDevice; + RegisterManager* registerManager; + QTimer* pollTimer; + int slaveAddress = 1; // 默认从站地址 + QMap pendingRequests; + + void readRegisterGroup(QModbusDataUnit::RegisterType type, int startAddr, int count); +}; + +#endif // MODBUSMANAGER_H diff --git a/mygraphicsview.cpp b/mygraphicsview.cpp index 64fa633..1c59a10 100644 --- a/mygraphicsview.cpp +++ b/mygraphicsview.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include MyGraphicsView::ClipInfo MyGraphicsView::clipboard_ = {}; @@ -165,16 +167,45 @@ void MyGraphicsView::onItemRequestDelete(Item *item) void MyGraphicsView::onItemRequestBindRegister(Item *item) { + if (!item) return; + bool ok = false; - QString reg = QInputDialog::getText(this, - "寄存器", - "编号:", - QLineEdit::Normal, - item->registerId(), - &ok); + QString reg = QInputDialog::getText( + this, + "绑定寄存器", + "格式: <类型><地址>\n" + "类型: D(保持寄存器), M(线圈寄存器)\n" + "示例: D100, M200\n" + "地址范围: 0-4000", + QLineEdit::Normal, + item->registerId(), + &ok + ); + if (ok && !reg.isEmpty()) { - item->setRegisterId(reg); + // 验证寄存器格式 + QRegularExpression regex("^[DMdm]\\d{1,4}$"); + QRegularExpressionMatch match = regex.match(reg); + + if (!match.hasMatch()) { + QMessageBox::warning(this, "格式错误", "请输入有效的寄存器格式\n示例: D100, M200"); + return; + } + + int address = reg.mid(1).toInt(); + if (address > 4000) { + QMessageBox::warning(this, "范围错误", "寄存器地址必须在0-4000范围内"); + return; + } + + // 标准化格式 (D100) + QString newReg = QString("%1%2").arg(reg[0].toUpper()).arg(address); + + item->setRegisterId(newReg); item->update(); + + // 发出绑定信号 + emit itemBoundToRegister(item, newReg); } } diff --git a/mygraphicsview.h b/mygraphicsview.h index 193fd61..d51e227 100644 --- a/mygraphicsview.h +++ b/mygraphicsview.h @@ -28,6 +28,9 @@ protected: void mouseMoveEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; +signals: + void itemBoundToRegister(Item*, QString); + private: Item* anchorItemAt(const QPoint& viewPos, Item::AnchorType& anchorType); diff --git a/plc.cpp b/plc.cpp index 10303f7..1818534 100644 --- a/plc.cpp +++ b/plc.cpp @@ -17,6 +17,8 @@ PLC::PLC(QWidget *parent) : ui(new Ui::PLC) { ui->setupUi(this); + registerManager = new RegisterManager(this); + modbusManager = new ModbusManager(registerManager, this); /* 1. 场景 */ plc_scene_ = new QGraphicsScene(this); ui->graphicsView->setScene(plc_scene_); @@ -33,6 +35,14 @@ PLC::PLC(QWidget *parent) : createComponents(); connect(ui->listWidget,&QListWidget::currentTextChanged,this,&PLC::onListwidgetCurrenttextchanged); connect(ui->btn_insert,&QPushButton::clicked,this,&PLC::btnInsertClicked); +// connect(modbusManager, &ModbusManager::connectionStatusChanged, +// this, &PLC::updateConnectionStatus); +// connect(modbusManager, &ModbusManager::errorOccurred, +// this, &PLC::handleModbusError); + connect(ui->graphicsView, &MyGraphicsView::itemBoundToRegister, + registerManager, &RegisterManager::bindItem); +// connect(this, &PLC::requestWriteRegister, +// modbusManager, &ModbusManager::writeRegister); } PLC::~PLC() diff --git a/plc.h b/plc.h index 95fd8ef..906f8c0 100644 --- a/plc.h +++ b/plc.h @@ -3,6 +3,7 @@ #include #include +#include "modbusmanager.h" #include "project.h" namespace Ui { @@ -32,6 +33,8 @@ private slots: private: Ui::PLC *ui; QGraphicsScene *plc_scene_; + ModbusManager* modbusManager; + RegisterManager* registerManager; QString selectedComponentType; }; diff --git a/registermanager.cpp b/registermanager.cpp new file mode 100644 index 0000000..6781fb9 --- /dev/null +++ b/registermanager.cpp @@ -0,0 +1,44 @@ +// registermanager.cpp +#include "registermanager.h" +#include "item.h" + +RegisterManager::RegisterManager(QObject *parent) : QObject(parent) {} + +void RegisterManager::bindItem(Item* item, const QString& registerId) +{ + if (registerId.isEmpty() || !item) return; + + // 添加到映射表 + registerMap[registerId].append(item); + + // 连接图元销毁信号 + connect(item, &Item::destroyed, this, [this, item, registerId](){ + if (registerMap.contains(registerId)) { + registerMap[registerId].removeAll(item); + if (registerMap[registerId].isEmpty()) { + registerMap.remove(registerId); + } + } + }); +} + +void RegisterManager::updateRegisterValue(const QString& registerId, quint16 value) +{ + if (!registerMap.contains(registerId)) return; + + // 更新所有绑定该寄存器的图元 + for (Item* item : registerMap[registerId]) { + item->setRegisterValue(value); + item->update(); // 刷新显示 + } +} + +QStringList RegisterManager::getAllRegisteredRegisters() const +{ + return registerMap.keys(); +} + +QList RegisterManager::getItemsForRegister(const QString& registerId) const +{ + return registerMap.value(registerId, QList()); +} diff --git a/registermanager.h b/registermanager.h new file mode 100644 index 0000000..333ff2d --- /dev/null +++ b/registermanager.h @@ -0,0 +1,28 @@ +#ifndef REGISTERMANAGER_H +#define REGISTERMANAGER_H + +#include +#include "item.h" + +class RegisterManager : public QObject +{ + Q_OBJECT +public: + explicit RegisterManager(QObject *parent = nullptr); + + // 注册图元与寄存器绑定关系 + void bindItem(Item* item, const QString& registerId); + + // 更新寄存器值 + void updateRegisterValue(const QString& registerId, quint16 value); + QStringList getAllRegisteredRegisters() const; + + // 获取绑定特定寄存器的所有图元 + QList getItemsForRegister(const QString& registerId) const; + +private: + // 寄存器到图元的映射 (寄存器ID -> 图元列表) + QMap> registerMap; +}; + +#endif // REGISTERMANAGER_H