@@ -22,6 +22,7 @@ | |||
#include <QJsonDocument> | |||
#include <QJsonObject> | |||
#include<QSpinBox> | |||
#include<QMessageBox> | |||
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<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) | |||
{ | |||
@@ -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<ResizableRectangle*>(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<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)); | |||
@@ -5,7 +5,7 @@ | |||
#include <QGraphicsScene> | |||
#include <QGraphicsView> | |||
#include <QJsonObject> | |||
#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(); | |||
@@ -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<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() | |||
@@ -5,9 +5,11 @@ | |||
#include <QTabWidget> | |||
#include <QToolBar> | |||
#include <QAction> | |||
#include<QTextEdit> | |||
#include <QTextEdit> | |||
#include "basedocument.h" | |||
#include<QPushButton> | |||
#include <QPushButton> | |||
#include <QMenu> | |||
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 |
@@ -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 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 | |||
@@ -1,6 +1,6 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<!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> | |||
<data> | |||
<variable>EnvironmentId</variable> | |||
@@ -287,9 +287,8 @@ | |||
</valuelist> | |||
<value type="int" key="PE.EnvironmentAspect.Base">2</value> | |||
<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="bool" key="RunConfiguration.Arguments.multi">false</value> | |||
<value type="QString" key="RunConfiguration.OverrideDebuggerStartup"></value> | |||