@@ -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; | |||
} |
@@ -9,6 +9,7 @@ public: | |||
void paint(QPainter *painter, | |||
const QStyleOptionGraphicsItem *option, | |||
QWidget *) override; | |||
bool state() const override; | |||
}; | |||
#endif // COIL_H |
@@ -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; | |||
} |
@@ -9,6 +9,7 @@ public: | |||
void paint(QPainter *painter, | |||
const QStyleOptionGraphicsItem *option, | |||
QWidget *) override; | |||
bool state() const override; | |||
}; | |||
#endif // CONTACT_H |
@@ -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 \ | |||
@@ -58,6 +58,12 @@ QString Item::itemType() | |||
return type_; | |||
} | |||
void Item::setRegisterValue(quint16 value) | |||
{ | |||
registerValue_ = value; | |||
update(); // 触发重绘 | |||
} | |||
void Item::MenuActions(QMenu *menu) | |||
{ | |||
menu->addAction("复制"); | |||
@@ -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<Connection*> connections_; | |||
QString registerId_; | |||
quint16 registerValue_ = 0; | |||
}; | |||
#endif // ITEM_H |
@@ -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_); | |||
} | |||
@@ -2,8 +2,10 @@ | |||
#define MAINWINDOW_H | |||
#include <QMainWindow> | |||
#include <plc.h> | |||
#include <hmi.h> | |||
#include "plc.h" | |||
#include "hmi.h" | |||
#include "modbusmanager.h" | |||
#include "registermanager.h" | |||
QT_BEGIN_NAMESPACE | |||
namespace Ui { class MainWindow; } | |||
@@ -0,0 +1,146 @@ | |||
#include "modbusmanager.h" | |||
#include <QModbusDataUnit> | |||
#include <QDebug> | |||
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<QModbusReply*>(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(); | |||
}); | |||
} | |||
} |
@@ -0,0 +1,55 @@ | |||
#ifndef MODBUSMANAGER_H | |||
#define MODBUSMANAGER_H | |||
#include <QObject> | |||
#include <QModbusRtuSerialMaster> | |||
#include <QSerialPort> | |||
#include <QTimer> | |||
#include <QMap> | |||
#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<QModbusReply*, QString> pendingRequests; | |||
void readRegisterGroup(QModbusDataUnit::RegisterType type, int startAddr, int count); | |||
}; | |||
#endif // MODBUSMANAGER_H |
@@ -5,6 +5,8 @@ | |||
#include <QDebug> | |||
#include <QMenu> | |||
#include <QInputDialog> | |||
#include <QMessageBox> | |||
#include <QRegularExpression> | |||
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); | |||
} | |||
} | |||
@@ -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); | |||
@@ -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() | |||
@@ -3,6 +3,7 @@ | |||
#include <QGraphicsScene> | |||
#include <QWidget> | |||
#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; | |||
}; | |||
@@ -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<Item*> RegisterManager::getItemsForRegister(const QString& registerId) const | |||
{ | |||
return registerMap.value(registerId, QList<Item*>()); | |||
} |
@@ -0,0 +1,28 @@ | |||
#ifndef REGISTERMANAGER_H | |||
#define REGISTERMANAGER_H | |||
#include <QObject> | |||
#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<Item*> getItemsForRegister(const QString& registerId) const; | |||
private: | |||
// 寄存器到图元的映射 (寄存器ID -> 图元列表) | |||
QMap<QString, QList<Item*>> registerMap; | |||
}; | |||
#endif // REGISTERMANAGER_H |