@@ -402,6 +402,7 @@ void HmiButton::setOn(bool on) | |||||
setColor(m_onColor); | setColor(m_onColor); | ||||
else | else | ||||
setColor(m_offColor); | setColor(m_offColor); | ||||
emit stateChanged(m_isOn); // 通知 HMIModule 更新 | |||||
update(); | update(); | ||||
} | } | ||||
} | } | ||||
@@ -414,10 +415,16 @@ QRectF HmiButton::boundingRect() const | |||||
void HmiButton::paintShape(QPainter *painter) | void HmiButton::paintShape(QPainter *painter) | ||||
{ | { | ||||
painter->setPen(Qt::NoPen); | painter->setPen(Qt::NoPen); | ||||
painter->setBrush(m_color); | |||||
painter->setBrush(m_isOn ? m_onColor : m_offColor); | |||||
painter->drawRect(boundingRect()); | painter->drawRect(boundingRect()); | ||||
} | } | ||||
void HmiButton::mousePressEvent(QGraphicsSceneMouseEvent *event) | |||||
{ | |||||
// 让基类处理其他情况(选择、拖动等) | |||||
HmiComponent::mousePressEvent(event); | |||||
} | |||||
HmiIndicator::HmiIndicator(QGraphicsItem *parent) : HmiComponent(parent) | HmiIndicator::HmiIndicator(QGraphicsItem *parent) : HmiComponent(parent) | ||||
{ | { | ||||
// 指示灯初始为圆形 | // 指示灯初始为圆形 | ||||
@@ -87,6 +87,7 @@ protected: | |||||
// 按钮类 | // 按钮类 | ||||
class HmiButton : public HmiComponent | class HmiButton : public HmiComponent | ||||
{ | { | ||||
Q_OBJECT | |||||
public: | public: | ||||
HmiButton(QGraphicsItem *parent = nullptr); | HmiButton(QGraphicsItem *parent = nullptr); | ||||
QRectF boundingRect() const override; | QRectF boundingRect() const override; | ||||
@@ -96,9 +97,12 @@ public: | |||||
bool isOn() const; | bool isOn() const; | ||||
void setOn(bool on); | void setOn(bool on); | ||||
signals: | |||||
void stateChanged(bool on); | |||||
protected: | protected: | ||||
void paintShape(QPainter *painter) override; | void paintShape(QPainter *painter) override; | ||||
void mousePressEvent(QGraphicsSceneMouseEvent *event) override; | |||||
private: | private: | ||||
bool m_isOn = false; // 默认OFF | bool m_isOn = false; // 默认OFF | ||||
}; | }; | ||||
@@ -15,16 +15,7 @@ CustomGraphicsScene::CustomGraphicsScene(QGraphicsView* view, QObject *parent) | |||||
void CustomGraphicsScene::setMode(Mode mode) | void CustomGraphicsScene::setMode(Mode mode) | ||||
{ | { | ||||
if (m_mode == mode) return; | |||||
m_mode = mode; | m_mode = mode; | ||||
// 根据模式设置光标 | |||||
if (m_mode == CreateItem) { | |||||
m_view->setCursor(Qt::CrossCursor); | |||||
} else { | |||||
m_view->setCursor(Qt::ArrowCursor); // 恢复为标准箭头光标 | |||||
} | |||||
} | } | ||||
void CustomGraphicsScene::setComponentTypeToCreate(ComponentType type) | void CustomGraphicsScene::setComponentTypeToCreate(ComponentType type) | ||||
@@ -14,10 +14,18 @@ | |||||
#include <QDebug> | #include <QDebug> | ||||
HMIModule::HMIModule(Ui::MainWindow* ui, QObject *parent) | HMIModule::HMIModule(Ui::MainWindow* ui, QObject *parent) | ||||
: QObject{parent}, ui_(ui), m_scene(nullptr) | |||||
: QObject{parent}, ui_(ui), m_scene(nullptr), m_modbusManager(new ModbusManager(this)) | |||||
{ | { | ||||
// 初始化 m_pageOrder | // 初始化 m_pageOrder | ||||
m_pageOrder.append(1); // 初始页面 | m_pageOrder.append(1); // 初始页面 | ||||
// 接收 Modbus 更新 | |||||
connect(m_modbusManager, &ModbusManager::coilUpdated, this, [this](int address, bool value){ | |||||
updateComponentsByAddress(address, value); | |||||
}); | |||||
// 接收日志 | |||||
connect(m_modbusManager, &ModbusManager::logMessage, this, &HMIModule::logMessageGenerated); | |||||
} | } | ||||
void HMIModule::setButtonIcon(QAbstractButton *button, const QString &iconPath) | void HMIModule::setButtonIcon(QAbstractButton *button, const QString &iconPath) | ||||
@@ -391,6 +399,11 @@ void HMIModule::setModified(bool modified) | |||||
m_isModified = modified; | m_isModified = modified; | ||||
} | } | ||||
ModbusManager *HMIModule::getModbusManager() const | |||||
{ | |||||
return m_modbusManager; | |||||
} | |||||
void HMIModule::setupNewComponent(HmiComponent* item) | void HMIModule::setupNewComponent(HmiComponent* item) | ||||
{ | { | ||||
if (!item) return; | if (!item) return; | ||||
@@ -401,12 +414,44 @@ void HMIModule::setupNewComponent(HmiComponent* item) | |||||
connect(item, &HmiComponent::changeOnColorRequested, this, &HMIModule::onChangeOnColorRequested); | connect(item, &HmiComponent::changeOnColorRequested, this, &HMIModule::onChangeOnColorRequested); | ||||
connect(item, &HmiComponent::changeOffColorRequested, this, &HMIModule::onChangeOffColorRequested); | connect(item, &HmiComponent::changeOffColorRequested, this, &HMIModule::onChangeOffColorRequested); | ||||
if (auto *btn = dynamic_cast<HmiButton*>(item)) { | |||||
connect(btn, &HmiButton::stateChanged, this, [this, btn](bool on){ | |||||
int addr = btn->address(); | |||||
updateComponentsByAddress(addr, on); | |||||
if (m_modbusManager && m_modbusManager->isRunning()) { | |||||
m_modbusManager->writeCoil(addr, on); | |||||
} | |||||
}); | |||||
} | |||||
QString currentTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"); | QString currentTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"); | ||||
QString log = QString("[%1] 创建 %2 组件").arg(currentTime).arg(item->componentName()); | QString log = QString("[%1] 创建 %2 组件").arg(currentTime).arg(item->componentName()); | ||||
emit logMessageGenerated(log); | emit logMessageGenerated(log); | ||||
} | } | ||||
void HMIModule::updateComponentsByAddress(int address, bool on) | |||||
{ | |||||
QTabWidget* tabWidget = ui_->tabWidget_2; | |||||
for (int i = 0; i < tabWidget->count(); ++i) { | |||||
QGraphicsView* view = tabWidget->widget(i)->findChild<QGraphicsView*>(); | |||||
if (!view) continue; | |||||
auto scene = view->scene(); | |||||
for (auto *item : scene->items()) { | |||||
if (auto hmi = dynamic_cast<HmiComponent*>(item)) { | |||||
if (hmi->address() == address) { | |||||
if (auto btn = dynamic_cast<HmiButton*>(hmi)) { | |||||
btn->setOn(on); // 按钮联动 | |||||
} else { | |||||
// 非按钮类就按颜色更新 | |||||
hmi->setColor(on ? hmi->onColor() : hmi->offColor()); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
void HMIModule::prepareToCreateButton() | void HMIModule::prepareToCreateButton() | ||||
{ | { | ||||
// 获取当前 TabWidget 中选中的 GraphicsView | // 获取当前 TabWidget 中选中的 GraphicsView | ||||
@@ -1,9 +1,10 @@ | |||||
#ifndef HMIMODULE_H | #ifndef HMIMODULE_H | ||||
#define HMIMODULE_H | #define HMIMODULE_H | ||||
#include <QObject> | |||||
#include "ui_MainWindow.h" | #include "ui_MainWindow.h" | ||||
#include "customgraphicsscene.h" | #include "customgraphicsscene.h" | ||||
#include "modbusmanager.h" | |||||
#include <QObject> | |||||
#include <QList> | #include <QList> | ||||
#include <QMap> | #include <QMap> | ||||
#include <QVector> | #include <QVector> | ||||
@@ -21,6 +22,7 @@ public: | |||||
void resetPages(); | void resetPages(); | ||||
bool isModified() const; // 判断是否有未保存的修改 | bool isModified() const; // 判断是否有未保存的修改 | ||||
void setModified(bool modified); // 设置修改状态 | void setModified(bool modified); // 设置修改状态 | ||||
ModbusManager* getModbusManager() const; | |||||
signals: | signals: | ||||
void pageAdded(int pageNumber, int index); | void pageAdded(int pageNumber, int index); | ||||
@@ -46,6 +48,7 @@ private slots: | |||||
private: | private: | ||||
void setupNewComponent(HmiComponent* item); | void setupNewComponent(HmiComponent* item); | ||||
void updateComponentsByAddress(int address, bool on); | |||||
private: | private: | ||||
Ui::MainWindow* ui_; | Ui::MainWindow* ui_; | ||||
@@ -66,6 +69,8 @@ private: | |||||
QList<int> m_availablePageNumbers; // 存储可用的页面编号 (有序列表) | QList<int> m_availablePageNumbers; // 存储可用的页面编号 (有序列表) | ||||
QList<int> m_pageOrder; // 维护页面顺序 | QList<int> m_pageOrder; // 维护页面顺序 | ||||
bool m_isModified = false; // 添加一个修改标志变量 | bool m_isModified = false; // 添加一个修改标志变量 | ||||
ModbusManager *m_modbusManager; | |||||
}; | }; | ||||
#endif // HMIMODULE_H | #endif // HMIMODULE_H |
@@ -23,6 +23,9 @@ MainWindow::MainWindow(QWidget *parent) | |||||
connect(ui_->actionNew, &QAction::triggered, this, &MainWindow::onNewFile); | connect(ui_->actionNew, &QAction::triggered, this, &MainWindow::onNewFile); | ||||
connect(ui_->actionOpen, &QAction::triggered, this, &MainWindow::onOpenFile); | connect(ui_->actionOpen, &QAction::triggered, this, &MainWindow::onOpenFile); | ||||
connect(ui_->actionSave, &QAction::triggered, this, &MainWindow::onSaveFile); | connect(ui_->actionSave, &QAction::triggered, this, &MainWindow::onSaveFile); | ||||
connect(ui_->actionStart, &QAction::triggered, this, &MainWindow::startSimulation); | |||||
connect(ui_->actionStop, &QAction::triggered, this, &MainWindow::stopSimulation); | |||||
} | } | ||||
MainWindow::~MainWindow() | MainWindow::~MainWindow() | ||||
@@ -109,3 +112,18 @@ void MainWindow::onSaveFile() | |||||
} | } | ||||
} | } | ||||
void MainWindow::startSimulation() | |||||
{ | |||||
if (hmi_ && hmi_->getModbusManager()) { | |||||
// 自己的串口参数,如 COM2 / 9600 | |||||
hmi_->getModbusManager()->startSimulation("COM2", 9600, QSerialPort::NoParity, QSerialPort::Data8, QSerialPort::OneStop); | |||||
} | |||||
} | |||||
void MainWindow::stopSimulation() | |||||
{ | |||||
if (hmi_ && hmi_->getModbusManager()) { | |||||
hmi_->getModbusManager()->stopSimulation(); | |||||
} | |||||
} | |||||
@@ -27,6 +27,9 @@ private slots: | |||||
void onNewFile(); | void onNewFile(); | ||||
void onOpenFile(); | void onOpenFile(); | ||||
void onSaveFile(); | void onSaveFile(); | ||||
void startSimulation(); | |||||
void stopSimulation(); | |||||
private: | private: | ||||
Ui::MainWindow *ui_; | Ui::MainWindow *ui_; | ||||
@@ -0,0 +1,124 @@ | |||||
#include "ModbusManager.h" | |||||
#include <QModbusDataUnit> | |||||
#include <QModbusReply> | |||||
#include <QSerialPort> | |||||
#include <QVariant> | |||||
ModbusManager::ModbusManager(QObject *parent) | |||||
: QObject(parent) | |||||
{ | |||||
} | |||||
ModbusManager::~ModbusManager() | |||||
{ | |||||
stopSimulation(); | |||||
} | |||||
bool ModbusManager::startSimulation(const QString &serialPortName, | |||||
int baudRate, | |||||
int parity, | |||||
int dataBits, | |||||
int stopBits) | |||||
{ | |||||
if (m_running) { | |||||
emit logMessage("仿真已经在运行"); | |||||
return true; | |||||
} | |||||
m_modbusDevice = new QModbusRtuSerialClient(this); | |||||
if (!m_modbusDevice) | |||||
return false; | |||||
m_modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter, serialPortName); | |||||
m_modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter, parity); | |||||
m_modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, baudRate); | |||||
m_modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, dataBits); | |||||
m_modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, stopBits); | |||||
m_modbusDevice->setTimeout(1000); | |||||
m_modbusDevice->setNumberOfRetries(3); | |||||
if (!m_modbusDevice->connectDevice()) { | |||||
emit logMessage("无法连接到 Modbus RTU 从站"); | |||||
delete m_modbusDevice; | |||||
m_modbusDevice = nullptr; | |||||
return false; | |||||
} | |||||
// 定时轮询 | |||||
m_pollingTimer = new QTimer(this); | |||||
connect(m_pollingTimer, &QTimer::timeout, this, &ModbusManager::pollModbusCoils); | |||||
m_pollingTimer->start(200); // 200毫秒轮询 | |||||
m_running = true; | |||||
emit logMessage(QString("开始仿真:串口 ") + serialPortName); | |||||
return true; | |||||
} | |||||
void ModbusManager::stopSimulation() | |||||
{ | |||||
if (!m_running) return; | |||||
if (m_pollingTimer) { | |||||
m_pollingTimer->stop(); | |||||
m_pollingTimer->deleteLater(); | |||||
m_pollingTimer = nullptr; | |||||
} | |||||
if (m_modbusDevice) { | |||||
m_modbusDevice->disconnectDevice(); | |||||
m_modbusDevice->deleteLater(); | |||||
m_modbusDevice = nullptr; | |||||
} | |||||
m_running = false; | |||||
emit logMessage("停止仿真"); | |||||
} | |||||
void ModbusManager::pollModbusCoils() | |||||
{ | |||||
if (!m_modbusDevice) return; | |||||
int startAddress = 0; | |||||
int numberOfCoils = 9; // 根据需要修改 | |||||
QModbusDataUnit readUnit(QModbusDataUnit::Coils, startAddress, numberOfCoils); | |||||
if (auto *reply = m_modbusDevice->sendReadRequest(readUnit, 1)) { // 1 = 从站ID | |||||
if (!reply->isFinished()) { | |||||
connect(reply, &QModbusReply::finished, this, [this, reply]() { | |||||
if (reply->error() == QModbusDevice::NoError) { | |||||
auto result = reply->result(); | |||||
for (uint i = 0; i < result.valueCount(); ++i) { | |||||
int addr = result.startAddress() + i; | |||||
bool val = result.value(i); | |||||
if (m_coilValues.value(addr) != val) { | |||||
m_coilValues[addr] = val; | |||||
emit coilUpdated(addr, val); | |||||
} | |||||
} | |||||
} else { | |||||
emit logMessage("读取线圈失败: " + reply->errorString()); | |||||
} | |||||
reply->deleteLater(); | |||||
}); | |||||
} else { | |||||
reply->deleteLater(); | |||||
} | |||||
} | |||||
} | |||||
void ModbusManager::writeCoil(int address, bool value) | |||||
{ | |||||
if (!m_running || !m_modbusDevice) return; | |||||
QModbusDataUnit writeUnit(QModbusDataUnit::Coils, address, 1); | |||||
writeUnit.setValue(0, value); | |||||
if (auto *reply = m_modbusDevice->sendWriteRequest(writeUnit, 1)) { | |||||
connect(reply, &QModbusReply::finished, reply, &QObject::deleteLater); | |||||
} | |||||
} | |||||
bool ModbusManager::isRunning() const | |||||
{ | |||||
return m_running; | |||||
} |
@@ -0,0 +1,41 @@ | |||||
#ifndef MODBUSMANAGER_H | |||||
#define MODBUSMANAGER_H | |||||
#include <QObject> | |||||
#include <qmodbusrtuserialclient.h> | |||||
#include <QTimer> | |||||
#include <QMap> | |||||
#include <QSerialPort> | |||||
class ModbusManager : public QObject | |||||
{ | |||||
Q_OBJECT | |||||
public: | |||||
explicit ModbusManager(QObject *parent = nullptr); | |||||
~ModbusManager(); | |||||
bool startSimulation(const QString &serialPortName, | |||||
int baudRate = 9600, | |||||
int parity = QSerialPort::NoParity, | |||||
int dataBits = QSerialPort::Data8, | |||||
int stopBits = QSerialPort::OneStop); | |||||
void stopSimulation(); | |||||
void writeCoil(int address, bool value); | |||||
bool isRunning() const; | |||||
signals: | |||||
void coilUpdated(int address, bool value); // 从站数据更新 | |||||
void logMessage(const QString &msg); // 日志 | |||||
private slots: | |||||
void pollModbusCoils(); | |||||
private: | |||||
QModbusClient *m_modbusDevice = nullptr; | |||||
QTimer *m_pollingTimer = nullptr; | |||||
bool m_running = false; | |||||
QMap<int, bool> m_coilValues; | |||||
}; | |||||
#endif // MODBUSMANAGER_H |