diff --git a/untitled/hmidocument.cpp b/untitled/hmidocument.cpp index c185354..4cde5d0 100644 --- a/untitled/hmidocument.cpp +++ b/untitled/hmidocument.cpp @@ -22,6 +22,7 @@ #include #include #include +#include HMIDocument::HMIDocument(QWidget *parent) : BaseDocument(HMI, parent), m_title("未命名HMI"), @@ -66,8 +67,95 @@ HMIDocument::HMIDocument(QWidget *parent) HMIDocument::~HMIDocument() { + stopSimulation(); +} +void HMIDocument::startSimulation() +{ + qDebug() <<"123"; + if (m_modbusSimulator && m_modbusSimulator->isRunning()) return; + // 收集所有线圈地址 + QSet addresses; + + foreach (QGraphicsItem *item, m_scene->items()) { + if (auto namedItem = dynamic_cast(item)) { + bool ok; + int address = namedItem->name().toInt(&ok); + + if (ok && address >= 0 && address <= 4000) { // 限制有效范围 + // 只收集指示灯(ResizableEllipse)的地址 + if (auto ellipseItem = dynamic_cast(item)) { + addresses.insert(address); + // 遍历代码结束后 + qDebug() << "收集到的指示灯地址:" << addresses; // 检查是否正确包含地址(如123) + } else { + // 对按钮(ResizableRectangle)进行处理,但不收集地址 + qDebug() << "按钮不收集地址: " << item->type(); + } + } else { + // 记录无效地址,方便调试 + qDebug() << "无效的线圈地址:" << namedItem->name() + << "(图形项类型:" << item->type() << ")"; + } + } + } + + if (addresses.isEmpty()) { + QMessageBox::warning(nullptr, "仿真启动", "没有找到有效的线圈地址绑定"); + return; + } + + if (!m_modbusSimulator) { + m_modbusSimulator = new ModbusSimulator(this); + connect(m_modbusSimulator, &ModbusSimulator::coilStatusRead, + this, &HMIDocument::onCoilStatusRead); + connect(m_modbusSimulator, &ModbusSimulator::errorOccurred, + this, &HMIDocument::onSimulationError); + } + // 遍历代码结束后 + qDebug() << "zhxing"; + m_modbusSimulator->setCoilAddresses(addresses); + m_modbusSimulator->startSimulation(); +} + +void HMIDocument::stopSimulation() +{ + if (m_modbusSimulator) { + m_modbusSimulator->stopSimulation(); + } +} + +bool HMIDocument::isSimulationRunning() const +{ + return m_modbusSimulator && m_modbusSimulator->isRunning(); } +void HMIDocument::onCoilStatusRead(int address, bool state) +{ + foreach (QGraphicsItem *item, m_scene->items()) { + if (auto namedItem = dynamic_cast(item)) { + bool ok; + int itemAddress = namedItem->name().toInt(&ok); + if (ok && itemAddress == address) { + if (auto ellipse = dynamic_cast(item)) { + ellipse->setBrush(state ? ellipse->onColor() : ellipse->offColor()); + ellipse->update(); + } + } + } + } +} + +void HMIDocument::onSimulationError(const QString &message) +{ + QMessageBox::critical(nullptr, "仿真错误", message); +} + +void HMIDocument::writeCoil(int address, bool state) +{ + if (m_modbusSimulator) { + m_modbusSimulator->writeCoil(address, state); + } +} // 事件过滤器(处理绘图、拖拽等事件) bool HMIDocument::eventFilter(QObject *obj, QEvent *event) { @@ -191,6 +279,8 @@ void HMIDocument::showContextMenu(QPoint globalPos) QAction *pasteAction = menu.addAction("粘贴"); QAction *deleteAction = menu.addAction("删除"); pasteAction->setEnabled(!m_copiedItemsData.isEmpty()); + + ResizableRectangle *rectItem = dynamic_cast(clickedItem); connect(propAction, &QAction::triggered, this, &HMIDocument::showItemProperties); connect(copyAction, &QAction::triggered, this, &HMIDocument::copySelectedItems); connect(pasteAction, &QAction::triggered, this, &HMIDocument::pasteItems); @@ -202,15 +292,24 @@ void HMIDocument::showContextMenu(QPoint globalPos) QAction *offAction = menu.addAction("置为OFF"); connect(onAction, &QAction::triggered, [=]() { - if (auto rect = dynamic_cast(clickedItem)) { - rect->setBrush(rect->pressedColor()); - } - }); - connect(offAction, &QAction::triggered, [=]() { - if (auto rect = dynamic_cast(clickedItem)) { - rect->setBrush(rect->releasedColor()); - } - }); + rectItem->setBrush(rectItem->pressedColor()); // 使用 rectItem + // 获取线圈地址并写入 + bool ok; + int address = rectItem->name().toInt(&ok); // 使用 rectItem + if (ok && address > 0) { + writeCoil(address, true); // 写入ON状态 + } + }); + + connect(offAction, &QAction::triggered, [=]() { + rectItem->setBrush(rectItem->releasedColor()); // 使用 rectItem + // 获取线圈地址并写入 + bool ok; + int address = rectItem->name().toInt(&ok); // 使用 rectItem + if (ok && address > 0) { + writeCoil(address, false); // 写入OFF状态 + } + }); } } menu.exec(globalPos + QPoint(10, 10)); diff --git a/untitled/hmidocument.h b/untitled/hmidocument.h index 10f9ea8..19026c9 100644 --- a/untitled/hmidocument.h +++ b/untitled/hmidocument.h @@ -5,7 +5,7 @@ #include #include #include - +#include"modbussimulator.h" // 前向声明 class ResizableRectangle; class ResizableEllipse; @@ -40,12 +40,25 @@ public: bool loadFromFile(const QString &filePath) override; void markModified(); + + + // 仿真控制 + void startSimulation(); + void stopSimulation(); + bool isSimulationRunning() const; + + // 写线圈方法 + void writeCoil(int address, bool state); signals: void titleChanged(const QString &title); protected: bool eventFilter(QObject *obj, QEvent *event) override; +private slots: + void onCoilStatusRead(int address, bool state); + void onSimulationError(const QString &message); private: + ModbusSimulator *m_modbusSimulator; // 辅助方法 void createShape(const QString& type, const QPointF &pos); void resetDrawFlags(); diff --git a/untitled/mainwindow.cpp b/untitled/mainwindow.cpp index 016f759..6c1073d 100644 --- a/untitled/mainwindow.cpp +++ b/untitled/mainwindow.cpp @@ -205,10 +205,43 @@ void MainWindow::createMenus() simulationMenu->setFont(itemFont); // 运行动作 (暂时留空) - QAction *runAction = new QAction("运行", this); - runAction->setFont(itemFont); - runAction->setEnabled(false); - simulationMenu->addAction(runAction); +// QAction *runAction = new QAction("运行", this); +// runAction->setFont(itemFont); +// simulationMenu->addAction(runAction); + // 创建仿真运行动作并连接到槽函数 + m_runSimulationAction = new QAction("运行", this); // 使用成员变量 + m_runSimulationAction->setFont(itemFont); + simulationMenu->addAction(m_runSimulationAction); + + // 添加连接:将仿真运行动作连接到 onRunSimulation 槽 + connect(m_runSimulationAction, &QAction::triggered, this, &MainWindow::onRunSimulation); +} + +void MainWindow::onRunSimulation() +{ + if (auto hmiDoc = dynamic_cast(m_tabWidget->currentWidget())) { + if (hmiDoc->isSimulationRunning()) { + hmiDoc->stopSimulation(); + } else { + hmiDoc->startSimulation(); + } + onSimulationStatusChanged(); + } +} + +void MainWindow::onSimulationStatusChanged() +{ + if (auto hmiDoc = dynamic_cast(m_tabWidget->currentWidget())) { + if (hmiDoc->isSimulationRunning()) { + m_runSimulationAction->setText("停止"); + m_runSimulationAction->setIcon(QIcon(":/icons/stop.png")); + statusBar()->showMessage("仿真运行中...", 2000); + } else { + m_runSimulationAction->setText("运行"); + m_runSimulationAction->setIcon(QIcon(":/icons/run.png")); + statusBar()->showMessage("仿真已停止", 2000); + } + } } // 创建左侧工具栏 void MainWindow::createToolbars() diff --git a/untitled/mainwindow.h b/untitled/mainwindow.h index 64422dc..264afe8 100644 --- a/untitled/mainwindow.h +++ b/untitled/mainwindow.h @@ -5,9 +5,11 @@ #include #include #include -#include +#include #include "basedocument.h" -#include +#include +#include + QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE @@ -33,6 +35,11 @@ private slots: void onCloseTab(int index); // 关闭标签页 void onClearLogButtonClicked(); void showLogContextMenu(const QPoint &pos); + + // 新增的仿真相关槽函数 + void onRunSimulation(); // 运行/停止仿真 + void onSimulationStatusChanged(); // 更新仿真状态显示 + private: void createMenus(); // 创建菜单栏 QWidget* m_logPanelContainer; @@ -53,6 +60,7 @@ private: QAction *m_saveAction; QAction *m_saveAsAction; QAction *m_openAction; + QAction *m_runSimulationAction; // 新增:仿真运行动作 }; #endif // MAINWINDOW_H diff --git a/untitled/modbussimulator.cpp b/untitled/modbussimulator.cpp new file mode 100644 index 0000000..b5dfc39 --- /dev/null +++ b/untitled/modbussimulator.cpp @@ -0,0 +1,114 @@ +#include "modbussimulator.h" +#include +#include +#include +#include +ModbusSimulator::ModbusSimulator(QObject *parent) + : QObject(parent), m_coilAddresses(),m_running(false), m_mutex() +{ + m_modbusClient = new QModbusRtuSerialMaster(this); + m_readTimer = new QTimer(this); + connect(m_readTimer, &QTimer::timeout, this, &ModbusSimulator::readCoils); +} + +ModbusSimulator::~ModbusSimulator() +{ + stopSimulation(); +} + +bool ModbusSimulator::isRunning() const +{ + return m_running; +} + +void ModbusSimulator::startSimulation() +{ + qDebug() << "进入 startSimulation() 函数"; + if (m_running) return; + qDebug() << "启动Modbus仿真555..."; + // 配置Modbus连接 + m_modbusClient->setConnectionParameter(QModbusDevice::SerialPortNameParameter, "COM1"); + m_modbusClient->setConnectionParameter(QModbusDevice::SerialParityParameter, QSerialPort::NoParity); + m_modbusClient->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, QSerialPort::Baud9600); + m_modbusClient->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, QSerialPort::Data8); + m_modbusClient->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, QSerialPort::OneStop); + + if (!m_modbusClient->connectDevice()) + { + emit errorOccurred("无法连接Modbus设备"); + return; + } + qDebug() << "Modbus设备连接成功"; + m_running = true; + m_readTimer->start(1000); // 每秒读取一次 +} + +void ModbusSimulator::stopSimulation() +{ + if (!m_running) return; + + m_readTimer->stop(); + m_modbusClient->disconnectDevice(); + m_running = false; +} + +void ModbusSimulator::setCoilAddresses(const QSet &addresses) +{ + qDebug() <<"执行到此"; + qDebug() << "setCoilAddresses called, this:" << this; // 打印对象地址 + QMutexLocker locker(&m_mutex); // 必须加锁 + m_coilAddresses = QSet(addresses); +} + +void ModbusSimulator::writeCoil(int address, bool state) +{ + if (!m_running) return; + + if (auto reply = m_modbusClient->sendWriteRequest( + QModbusDataUnit(QModbusDataUnit::Coils, address, {state}), 1)) { + + connect(reply, &QModbusReply::finished, [reply, address]() { + if (reply->error() != QModbusDevice::NoError) { + qWarning() << "写线圈失败:" << reply->errorString() << "地址:" << address; + } + reply->deleteLater(); + }); + } +} + +void ModbusSimulator::readCoils() +{ + QSet addressesCopy; + { + QMutexLocker locker(&m_mutex); + if (!m_running || m_coilAddresses.isEmpty()) return; + addressesCopy = m_coilAddresses; // 在锁保护下复制 + } + int minAddress = *std::min_element(addressesCopy.begin(), addressesCopy.end()); + int maxAddress = *std::max_element(addressesCopy.begin(), addressesCopy.end()); + int count = maxAddress - minAddress + 1; + + // 创建读请求 + QModbusDataUnit readUnit(QModbusDataUnit::Coils, minAddress, count); + if (auto *reply = m_modbusClient->sendReadRequest(readUnit, 1)) { + if (!reply->isFinished()) { + // 使用智能指针确保地址集合在回调中有效 + auto safeAddresses = QSharedPointer>::create(addressesCopy); + connect(reply, &QModbusReply::finished, this, [this, reply, safeAddresses]() { + if (reply->error() == QModbusDevice::NoError) { + const QModbusDataUnit unit = reply->result(); + for (int i = 0; i < unit.valueCount(); ++i) { + int address = unit.startAddress() + i; + bool state = unit.value(i); + if (safeAddresses->contains(address)) { + emit coilStatusRead(address, state); + } + } + } + reply->deleteLater(); + }); + } else { + delete reply; + } + } +} diff --git a/untitled/modbussimulator.h b/untitled/modbussimulator.h new file mode 100644 index 0000000..d0b3a7f --- /dev/null +++ b/untitled/modbussimulator.h @@ -0,0 +1,39 @@ +#ifndef MODBUSSIMULATOR_H +#define MODBUSSIMULATOR_H + +#include +#include +#include +#include +#include // 新增:互斥锁头文件 +class ModbusSimulator : public QObject +{ + Q_OBJECT + +public: + explicit ModbusSimulator(QObject *parent = nullptr); + ~ModbusSimulator(); + + void startSimulation(); + void stopSimulation(); + bool isRunning() const; + + void setCoilAddresses(const QSet &addresses); + void writeCoil(int address, bool state); + +signals: + void coilStatusRead(int address, bool state); + void errorOccurred(const QString &message); + +private slots: + void readCoils(); + +private: + QModbusRtuSerialMaster *m_modbusClient; + QTimer *m_readTimer; + QSet m_coilAddresses; + bool m_running; + QMutex m_mutex; // 新增:保护m_coilAddresses的互斥锁 +}; + +#endif // MODBUSSIMULATOR_H diff --git a/untitled/untitled.pro b/untitled/untitled.pro index 71ea9db..360b677 100644 --- a/untitled/untitled.pro +++ b/untitled/untitled.pro @@ -1,5 +1,6 @@ QT += core gui QT += core gui widgets serialport +QT+=serialbus greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += c++11 @@ -21,6 +22,7 @@ SOURCES += \ hmidocument.cpp \ main.cpp \ mainwindow.cpp \ + modbussimulator.cpp \ plcdocument.cpp \ plcitems.cpp @@ -29,6 +31,7 @@ HEADERS += \ graphicsitems.h \ hmidocument.h \ mainwindow.h \ + modbussimulator.h \ plcdocument.h \ plcitems.h diff --git a/untitled/untitled.pro.user b/untitled/untitled.pro.user index b6607b4..c515fcd 100644 --- a/untitled/untitled.pro.user +++ b/untitled/untitled.pro.user @@ -1,6 +1,6 @@ - + EnvironmentId @@ -287,9 +287,8 @@ 2 - untitled2 - Qt4ProjectManager.Qt4RunConfiguration:C:/Users/admin/Desktop/two/untitled/untitled.pro - C:/Users/admin/Desktop/two/untitled/untitled.pro + Qt4ProjectManager.Qt4RunConfiguration:C:/Users/admin/Desktop/seven/untitled/untitled.pro + C:/Users/admin/Desktop/seven/untitled/untitled.pro false