@@ -22,6 +22,7 @@ | |||||
#include <QJsonDocument> | #include <QJsonDocument> | ||||
#include <QJsonObject> | #include <QJsonObject> | ||||
#include<QSpinBox> | #include<QSpinBox> | ||||
#include<QMessageBox> | |||||
HMIDocument::HMIDocument(QWidget *parent) | HMIDocument::HMIDocument(QWidget *parent) | ||||
: BaseDocument(HMI, parent), | : BaseDocument(HMI, parent), | ||||
m_title("未命名HMI"), | m_title("未命名HMI"), | ||||
@@ -66,8 +67,95 @@ HMIDocument::HMIDocument(QWidget *parent) | |||||
HMIDocument::~HMIDocument() | HMIDocument::~HMIDocument() | ||||
{ | { | ||||
stopSimulation(); | |||||
} | |||||
void HMIDocument::startSimulation() | |||||
{ | |||||
qDebug() <<"123"; | |||||
if (m_modbusSimulator && m_modbusSimulator->isRunning()) return; | |||||
// 收集所有线圈地址 | |||||
QSet<int> addresses; | |||||
foreach (QGraphicsItem *item, m_scene->items()) { | |||||
if (auto namedItem = dynamic_cast<NamedItem*>(item)) { | |||||
bool ok; | |||||
int address = namedItem->name().toInt(&ok); | |||||
if (ok && address >= 0 && address <= 4000) { // 限制有效范围 | |||||
// 只收集指示灯(ResizableEllipse)的地址 | |||||
if (auto ellipseItem = dynamic_cast<ResizableEllipse*>(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<NamedItem*>(item)) { | |||||
bool ok; | |||||
int itemAddress = namedItem->name().toInt(&ok); | |||||
if (ok && itemAddress == address) { | |||||
if (auto ellipse = dynamic_cast<ResizableEllipse*>(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) | bool HMIDocument::eventFilter(QObject *obj, QEvent *event) | ||||
{ | { | ||||
@@ -191,6 +279,8 @@ void HMIDocument::showContextMenu(QPoint globalPos) | |||||
QAction *pasteAction = menu.addAction("粘贴"); | QAction *pasteAction = menu.addAction("粘贴"); | ||||
QAction *deleteAction = menu.addAction("删除"); | QAction *deleteAction = menu.addAction("删除"); | ||||
pasteAction->setEnabled(!m_copiedItemsData.isEmpty()); | pasteAction->setEnabled(!m_copiedItemsData.isEmpty()); | ||||
ResizableRectangle *rectItem = dynamic_cast<ResizableRectangle*>(clickedItem); | |||||
connect(propAction, &QAction::triggered, this, &HMIDocument::showItemProperties); | connect(propAction, &QAction::triggered, this, &HMIDocument::showItemProperties); | ||||
connect(copyAction, &QAction::triggered, this, &HMIDocument::copySelectedItems); | connect(copyAction, &QAction::triggered, this, &HMIDocument::copySelectedItems); | ||||
connect(pasteAction, &QAction::triggered, this, &HMIDocument::pasteItems); | connect(pasteAction, &QAction::triggered, this, &HMIDocument::pasteItems); | ||||
@@ -202,15 +292,24 @@ void HMIDocument::showContextMenu(QPoint globalPos) | |||||
QAction *offAction = menu.addAction("置为OFF"); | QAction *offAction = menu.addAction("置为OFF"); | ||||
connect(onAction, &QAction::triggered, [=]() { | connect(onAction, &QAction::triggered, [=]() { | ||||
if (auto rect = dynamic_cast<ResizableRectangle*>(clickedItem)) { | |||||
rect->setBrush(rect->pressedColor()); | |||||
} | |||||
}); | |||||
connect(offAction, &QAction::triggered, [=]() { | |||||
if (auto rect = dynamic_cast<ResizableRectangle*>(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)); | menu.exec(globalPos + QPoint(10, 10)); | ||||
@@ -5,7 +5,7 @@ | |||||
#include <QGraphicsScene> | #include <QGraphicsScene> | ||||
#include <QGraphicsView> | #include <QGraphicsView> | ||||
#include <QJsonObject> | #include <QJsonObject> | ||||
#include"modbussimulator.h" | |||||
// 前向声明 | // 前向声明 | ||||
class ResizableRectangle; | class ResizableRectangle; | ||||
class ResizableEllipse; | class ResizableEllipse; | ||||
@@ -40,12 +40,25 @@ public: | |||||
bool loadFromFile(const QString &filePath) override; | bool loadFromFile(const QString &filePath) override; | ||||
void markModified(); | void markModified(); | ||||
// 仿真控制 | |||||
void startSimulation(); | |||||
void stopSimulation(); | |||||
bool isSimulationRunning() const; | |||||
// 写线圈方法 | |||||
void writeCoil(int address, bool state); | |||||
signals: | signals: | ||||
void titleChanged(const QString &title); | void titleChanged(const QString &title); | ||||
protected: | protected: | ||||
bool eventFilter(QObject *obj, QEvent *event) override; | bool eventFilter(QObject *obj, QEvent *event) override; | ||||
private slots: | |||||
void onCoilStatusRead(int address, bool state); | |||||
void onSimulationError(const QString &message); | |||||
private: | private: | ||||
ModbusSimulator *m_modbusSimulator; | |||||
// 辅助方法 | // 辅助方法 | ||||
void createShape(const QString& type, const QPointF &pos); | void createShape(const QString& type, const QPointF &pos); | ||||
void resetDrawFlags(); | void resetDrawFlags(); | ||||
@@ -205,10 +205,43 @@ void MainWindow::createMenus() | |||||
simulationMenu->setFont(itemFont); | 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<HMIDocument*>(m_tabWidget->currentWidget())) { | |||||
if (hmiDoc->isSimulationRunning()) { | |||||
hmiDoc->stopSimulation(); | |||||
} else { | |||||
hmiDoc->startSimulation(); | |||||
} | |||||
onSimulationStatusChanged(); | |||||
} | |||||
} | |||||
void MainWindow::onSimulationStatusChanged() | |||||
{ | |||||
if (auto hmiDoc = dynamic_cast<HMIDocument*>(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() | void MainWindow::createToolbars() | ||||
@@ -5,9 +5,11 @@ | |||||
#include <QTabWidget> | #include <QTabWidget> | ||||
#include <QToolBar> | #include <QToolBar> | ||||
#include <QAction> | #include <QAction> | ||||
#include<QTextEdit> | |||||
#include <QTextEdit> | |||||
#include "basedocument.h" | #include "basedocument.h" | ||||
#include<QPushButton> | |||||
#include <QPushButton> | |||||
#include <QMenu> | |||||
QT_BEGIN_NAMESPACE | QT_BEGIN_NAMESPACE | ||||
namespace Ui { class MainWindow; } | namespace Ui { class MainWindow; } | ||||
QT_END_NAMESPACE | QT_END_NAMESPACE | ||||
@@ -33,6 +35,11 @@ private slots: | |||||
void onCloseTab(int index); // 关闭标签页 | void onCloseTab(int index); // 关闭标签页 | ||||
void onClearLogButtonClicked(); | void onClearLogButtonClicked(); | ||||
void showLogContextMenu(const QPoint &pos); | void showLogContextMenu(const QPoint &pos); | ||||
// 新增的仿真相关槽函数 | |||||
void onRunSimulation(); // 运行/停止仿真 | |||||
void onSimulationStatusChanged(); // 更新仿真状态显示 | |||||
private: | private: | ||||
void createMenus(); // 创建菜单栏 | void createMenus(); // 创建菜单栏 | ||||
QWidget* m_logPanelContainer; | QWidget* m_logPanelContainer; | ||||
@@ -53,6 +60,7 @@ private: | |||||
QAction *m_saveAction; | QAction *m_saveAction; | ||||
QAction *m_saveAsAction; | QAction *m_saveAsAction; | ||||
QAction *m_openAction; | QAction *m_openAction; | ||||
QAction *m_runSimulationAction; // 新增:仿真运行动作 | |||||
}; | }; | ||||
#endif // MAINWINDOW_H | #endif // MAINWINDOW_H |
@@ -0,0 +1,114 @@ | |||||
#include "modbussimulator.h" | |||||
#include <QSerialPort> | |||||
#include <QDebug> | |||||
#include <algorithm> | |||||
#include<QThread> | |||||
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<int> &addresses) | |||||
{ | |||||
qDebug() <<"执行到此"; | |||||
qDebug() << "setCoilAddresses called, this:" << this; // 打印对象地址 | |||||
QMutexLocker locker(&m_mutex); // 必须加锁 | |||||
m_coilAddresses = QSet<int>(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<int> 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<QSet<int>>::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; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,39 @@ | |||||
#ifndef MODBUSSIMULATOR_H | |||||
#define MODBUSSIMULATOR_H | |||||
#include <QObject> | |||||
#include <QModbusRtuSerialMaster> | |||||
#include <QTimer> | |||||
#include <QSet> | |||||
#include <QMutex> // 新增:互斥锁头文件 | |||||
class ModbusSimulator : public QObject | |||||
{ | |||||
Q_OBJECT | |||||
public: | |||||
explicit ModbusSimulator(QObject *parent = nullptr); | |||||
~ModbusSimulator(); | |||||
void startSimulation(); | |||||
void stopSimulation(); | |||||
bool isRunning() const; | |||||
void setCoilAddresses(const QSet<int> &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<int> m_coilAddresses; | |||||
bool m_running; | |||||
QMutex m_mutex; // 新增:保护m_coilAddresses的互斥锁 | |||||
}; | |||||
#endif // MODBUSSIMULATOR_H |
@@ -1,5 +1,6 @@ | |||||
QT += core gui | QT += core gui | ||||
QT += core gui widgets serialport | QT += core gui widgets serialport | ||||
QT+=serialbus | |||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets | ||||
CONFIG += c++11 | CONFIG += c++11 | ||||
@@ -21,6 +22,7 @@ SOURCES += \ | |||||
hmidocument.cpp \ | hmidocument.cpp \ | ||||
main.cpp \ | main.cpp \ | ||||
mainwindow.cpp \ | mainwindow.cpp \ | ||||
modbussimulator.cpp \ | |||||
plcdocument.cpp \ | plcdocument.cpp \ | ||||
plcitems.cpp | plcitems.cpp | ||||
@@ -29,6 +31,7 @@ HEADERS += \ | |||||
graphicsitems.h \ | graphicsitems.h \ | ||||
hmidocument.h \ | hmidocument.h \ | ||||
mainwindow.h \ | mainwindow.h \ | ||||
modbussimulator.h \ | |||||
plcdocument.h \ | plcdocument.h \ | ||||
plcitems.h | plcitems.h | ||||
@@ -1,6 +1,6 @@ | |||||
<?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||
<!DOCTYPE QtCreatorProject> | <!DOCTYPE QtCreatorProject> | ||||
<!-- Written by QtCreator 4.11.1, 2025-08-12T21:46:20. --> | |||||
<!-- Written by QtCreator 4.11.1, 2025-08-14T12:27:31. --> | |||||
<qtcreator> | <qtcreator> | ||||
<data> | <data> | ||||
<variable>EnvironmentId</variable> | <variable>EnvironmentId</variable> | ||||
@@ -287,9 +287,8 @@ | |||||
</valuelist> | </valuelist> | ||||
<value type="int" key="PE.EnvironmentAspect.Base">2</value> | <value type="int" key="PE.EnvironmentAspect.Base">2</value> | ||||
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/> | <valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/> | ||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">untitled2</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4RunConfiguration:C:/Users/admin/Desktop/two/untitled/untitled.pro</value> | |||||
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">C:/Users/admin/Desktop/two/untitled/untitled.pro</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4RunConfiguration:C:/Users/admin/Desktop/seven/untitled/untitled.pro</value> | |||||
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">C:/Users/admin/Desktop/seven/untitled/untitled.pro</value> | |||||
<value type="QString" key="RunConfiguration.Arguments"></value> | <value type="QString" key="RunConfiguration.Arguments"></value> | ||||
<value type="bool" key="RunConfiguration.Arguments.multi">false</value> | <value type="bool" key="RunConfiguration.Arguments.multi">false</value> | ||||
<value type="QString" key="RunConfiguration.OverrideDebuggerStartup"></value> | <value type="QString" key="RunConfiguration.OverrideDebuggerStartup"></value> | ||||