最近项目中用到了ModbusSlave,也就是Modbus从设备的功能,之前用的基本都是master设备,所以读取数据啥的用单线程就行了,用
void WaitHelper::WaitImplByEventloop(int msec)
{
QEventLoop loop;
QTimer::singleShot(msec, &loop, &QEventLoop::quit); // 设置定时器,在超时时退出事件循环
loop.exec(); // 进入事件循
}
这种方法进行流程等待也没啥问题。但是在slave模式下,使用这种模式就会造成事件无法正常回调,在等待过程中没法回复请求,导致master设备卡死,最终使用多线程解决的该问题。
这里遵循Qt多线程最佳实践,使用worker->moveToThread(workerThread);
实现多线程功能。核心方法就是创建ModbusWorker
实例并且移动到QThread中。
bool ModbusTcpSlave::connectToHost()
{
worker = new ModbusWorker();
workerThread = new QThread(this);
worker->moveToThread(workerThread);
connect(workerThread, &QThread::started, [=]() {
worker->startMonitoring(_obj["address"].toString());
});
connect(worker, &ModbusWorker::dataWritten, this, &ModbusTcpSlave::onDataWritten);
connect(workerThread, &QThread::finished, worker, &QObject::deleteLater);
connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater);
workerThread->start();
return true;
}
然后就是调用方法时使用QMetaObject::invokeMethod
异步调用
void ModbusTcpSlave::WriteRegister(int address, int value)
{
// 异步跨线程调用
QMetaObject::invokeMethod(worker,"setData",Qt::QueuedConnection,
Q_ARG(QModbusDataUnit::RegisterType, QModbusDataUnit::HoldingRegisters),
Q_ARG(int, address),
Q_ARG(quint16, value));
}
以下是源码:
#ifndef MODBUSWORKER_H
#define MODBUSWORKER_H
#include <QObject>
#include <QModbusTcpServer>
#include <QModbusDataUnit>
class ModbusWorker : public QObject
{
Q_OBJECT
public:
explicit ModbusWorker(QObject *parent = nullptr);
~ModbusWorker();
bool connectToHost(const QString &address);
void startMonitoring(const QString &address);
void stopMonitoring();
public slots:
bool setData(QModbusDataUnit::RegisterType table, int address, quint16 value);
signals:
void dataWritten(QModbusDataUnit::RegisterType table, int address, quint16 value);
private slots:
void onDataWritten(QModbusDataUnit::RegisterType table, int address, int size);
void onStateChanged(QModbusDevice::State state);
private:
QModbusTcpServer *_modbusServer;
};
#endif // MODBUSWORKER_H
#include <QUrl>
#include <QDebug>
#include "modbusworker.h"
ModbusWorker::ModbusWorker(QObject *parent) : QObject(parent), _modbusServer(new QModbusTcpServer(this))
{
qRegisterMetaType<QModbusDataUnit::RegisterType>("QModbusDataUnit::RegisterType");
QObject::connect(_modbusServer, &QModbusTcpServer::stateChanged, this, &ModbusWorker::onStateChanged);
QObject::connect(_modbusServer, &QModbusTcpServer::dataWritten, this, &ModbusWorker::onDataWritten);
}
ModbusWorker::~ModbusWorker()
{
_modbusServer->disconnectDevice();
}
bool ModbusWorker::setData(QModbusDataUnit::RegisterType table, int address, quint16 value)
{
return _modbusServer->setData(table,address,value);
}
void ModbusWorker::onDataWritten(QModbusDataUnit::RegisterType table, int address, int size)
{
if(size==1)
{
quint16 value;
_modbusServer->data(table, address, &value);
emit dataWritten(table,address,value);
}
}
void ModbusWorker::onStateChanged(QModbusDevice::State state)
{
qDebug()<<__FUNCTION__<<__LINE__<<state;
}
bool ModbusWorker::connectToHost(const QString &address)
{
const QUrl url = QUrl::fromUserInput(address);
_modbusServer->setConnectionParameter(QModbusDevice::NetworkPortParameter, url.port());
_modbusServer->setConnectionParameter(QModbusDevice::NetworkAddressParameter, url.host());
// 设置Modbus数据单元,类型为保持寄存器
QModbusDataUnitMap reg;
reg.insert(QModbusDataUnit::HoldingRegisters, {QModbusDataUnit::HoldingRegisters, 0, 30});
reg.insert(QModbusDataUnit::InputRegisters, {QModbusDataUnit::InputRegisters, 0, 10});
_modbusServer->setMap(reg);
_modbusServer->setServerAddress(1);
// 初始化保持寄存器的值
for (int i = 0; i < 10; ++i) {
_modbusServer->setData(QModbusDataUnit::HoldingRegisters, i, 0);
}
return _modbusServer->connectDevice();
}
void ModbusWorker::startMonitoring(const QString &address)
{
if(connectToHost(address))
{
return;
}
}
void ModbusWorker::stopMonitoring()
{
_modbusServer->disconnectDevice();
}
```cpp
#ifndef MODBUSTCPSLAVE_H
#define MODBUSTCPSLAVE_H
#include <QObject>
#include <QModbusTcpServer>
#include "basecommunication.h"
#include "modbusworker.h"
class ModbusTcpSlave : public BaseCommunication
{
Q_OBJECT
public:
Q_INVOKABLE explicit ModbusTcpSlave(QObject *parent = nullptr);
signals:
public slots:
// BaseCommunication interface
public:
virtual bool connectToHost() override;
virtual bool isConnected() override;
virtual bool disConnectFromHost() override;
void WriteRegister(int address, int value);
void WriteInputRegister(int address, int value);
signals:
void dataWritten(QModbusDataUnit::RegisterType,int address, quint16 val);
private slots:
void onDataWritten(QModbusDataUnit::RegisterType table, int address, int value);
private:
QThread *workerThread;
ModbusWorker *worker;
};
#endif // TCPMODBUSSERVER_H
#include <QUrl>
#include "modbustcpslave.h"
ModbusTcpSlave::ModbusTcpSlave(QObject *parent) : BaseCommunication(parent)
{
}
bool ModbusTcpSlave::connectToHost()
{
worker = new ModbusWorker();
workerThread = new QThread(this);
worker->moveToThread(workerThread);
connect(workerThread, &QThread::started, [=]() {
worker->startMonitoring(_obj["address"].toString());
});
connect(worker, &ModbusWorker::dataWritten, this, &ModbusTcpSlave::onDataWritten);
connect(workerThread, &QThread::finished, worker, &QObject::deleteLater);
connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater);
workerThread->start();
return true;
}
bool ModbusTcpSlave::isConnected()
{
return true;
}
bool ModbusTcpSlave::disConnectFromHost()
{
worker->stopMonitoring();
return true;
}
void ModbusTcpSlave::WriteRegister(int address, int value)
{
// 异步跨线程调用
QMetaObject::invokeMethod(worker,"setData",Qt::QueuedConnection,
Q_ARG(QModbusDataUnit::RegisterType, QModbusDataUnit::HoldingRegisters),
Q_ARG(int, address),
Q_ARG(quint16, value));
}
void ModbusTcpSlave::WriteInputRegister(int address, int value)
{
QMetaObject::invokeMethod(worker,"setData",Qt::QueuedConnection,
Q_ARG(QModbusDataUnit::RegisterType, QModbusDataUnit::InputRegisters),
Q_ARG(int, address),
Q_ARG(quint16, value));
}
void ModbusTcpSlave::onDataWritten(QModbusDataUnit::RegisterType table, int address, int value)
{
emit dataWritten(table,address,value);
}