This commit is contained in:
2021-05-26 04:15:57 -04:00
parent 0ae56e062c
commit 75f1a4d1c3
74 changed files with 5086 additions and 42 deletions
+756
View File
@@ -0,0 +1,756 @@
/*******************************************************************************
* *
* Société de Transports de Montréal. *
* 2012 - 2013 *
* *
* Projet Zones Tests *
* *
* *
* *
*******************************************************************************/
/*
Description:
Classe qui implémente le protocole Modbus.
*/
/* ************************************************************************** */
/* Revision:
### YYYMMDD JFM
Verision d'origine.
### YYYYMMDD Description du besoin ou du bug
Description du changement.
*/
/* ************************************************************************** */
#include "ModbusBackend.h"
#include "QBuffer"
#include <QDataStream>
//#include "EngLog.h"
//#include "ZTLog.h"
CModbusBackend::CModbusBackend(CModbusRepository *Repo)
{
mModbusTCPSocketHandle = 0;
mDataLinkValid = false;
mModbusRepo = Repo;
mModbusMode = MODBUS_INVALID_MODE;
mTransactionIDCounter = 0;
mDeviceID = 9;//0xFF;
mModbusMaxRetry = MODBUS_RETRY_MAX_COUNT;
mModbusRequestTimeout = MODBUS_RETRY_DELAY;
}
CModbusBackend::~CModbusBackend()
{
for(int i= 0; i < mRequestsList.size(); i++)
{
delete mRequestsList[i];
}
mRequestsList.clear();
}
void CModbusBackend::ModbusDataReady()
{
CModbusTransaction Transaction;
QByteArray InData = mModbusTCPSocketHandle->readAll();
QBuffer FileBuffer(&InData);
FileBuffer.open(QIODevice::ReadOnly);
FileBuffer.seek(0);
QDataStream *TransactionDataStrm = new QDataStream(&FileBuffer);
*TransactionDataStrm >> Transaction.mHeader;
*TransactionDataStrm >> Transaction.mPDU.mFunctionCode;
Transaction.mPDU.mData = InData.right(Transaction.mHeader.mMessageLength - 2); //-2 to remove Device ID and Function Code.
//
// qDebug("---------------------------------------");
// qDebug("modbus data received %s",InData.toHex().data());
// qDebug("Transaction ID 0x%X",Transaction.mHeader.mTransactionID);
// qDebug("Message Length %d",Transaction.mHeader.mMessageLength);
// qDebug("Protocol ID 0x%X",Transaction.mHeader.mProtocolID);
// qDebug("Unit ID 0x%X",Transaction.mHeader.mUnitID);
// qDebug("Function Code 0x%X",Transaction.mPDU.mFunctionCode);
// qDebug("Data %s",Transaction.mPDU.mData.toHex().data());
// qDebug("---------------------------------------");
if(mModbusMode == MODBUS_MASTER_MODE)
{
AnalyzeModbusResponse(Transaction); //Le maître recoit des réponses
}
else if( mModbusMode == MODBUS_SLAVE_MODE)
{
AnalyzeModbusRequest(Transaction); //L'esclave recoit des requêtes
}
else
{
// CEngLog::instance()->AddLogString("Erreur Modbus: <Illegal modbus backend mode...>"); //Erreur de logique interne. Ne doit pas survenir normalement
}
delete TransactionDataStrm;
}
void CModbusBackend::ModbusLinkDisconnected()
{
// qDebug("Modbus link disconnected");
mDataLinkValid = false;
mModbusTCPSocketHandle->flush();
}
//In client mode. This is the request from the master.
int CModbusBackend::AnalyzeModbusRequest(CModbusTransaction Transaction)
{
if(Transaction.mHeader.mProtocolID != 0)
{
//Invalid protocol... what can we do?
return 0;
}
switch(Transaction.mPDU.mFunctionCode)
{
case MODBUS_FCT_READ_HOLDING_REGISTERS:
{
bool ok = true;
unsigned short StartAdress = 0;
StartAdress = Transaction.mPDU.mData[0]&0xFF;
StartAdress <<= 8;
StartAdress += Transaction.mPDU.mData[1]&0xFF;
unsigned short NbRegisters = 0;
NbRegisters = Transaction.mPDU.mData[2]&0xFF;
NbRegisters <<= 8;
NbRegisters += Transaction.mPDU.mData[3]&0xFF;
//Validate nb of registers
if(NbRegisters < 1 || NbRegisters > MODBUS_MAX_NB_REGISTERS)
{
SendErrorResponse(Transaction,MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE);
ModbusRequestException(MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,MODBUS_FCT_READ_HOLDING_REGISTERS); //Fonction à être implémentée par le parent
return 0;
}
//Validate data range
if(!mModbusRepo->IsHRValid(StartAdress,NbRegisters))
{
qDebug("Reg invalid");
//Send negative response
SendErrorResponse(Transaction,MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
ModbusRequestException(MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS,MODBUS_FCT_READ_HOLDING_REGISTERS);
return 0;
}
QByteArray data = mModbusRepo->GetHRData(StartAdress,NbRegisters,&ok);
// qDebug("Slave Rx Read Holding Registers. Address: %d, Nb Reg: %d",StartAdress, NbRegisters);
// qDebug("Data: %s",data.toHex().data());
//The response to a HR reading needs the byte count before the data.
quint8 ByteCount = data.size();
data.prepend(ByteCount);
SendModbusResponse(Transaction, data);
//All OK
break;
}
case MODBUS_WRITE_SINGLE_REGISTER:
{
unsigned short StartAdress = 0;
StartAdress = Transaction.mPDU.mData[0]&0xFF;
StartAdress <<= 8;
StartAdress += Transaction.mPDU.mData[1]&0xFF;
//Validate data range
if(!mModbusRepo->IsHRValid(StartAdress,1))
{
qDebug("Reg invalid");
//Send negative response
SendErrorResponse(Transaction,MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
ModbusRequestException(MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS,MODBUS_WRITE_SINGLE_REGISTER);
return 0;
}
//Extract data.
QByteArray data = Transaction.mPDU.mData.right(2);
//Write register data
mModbusRepo->WriteHRData(StartAdress,1,data);
// if(StartAdress == 2000)
// {
// qDebug("debug... 2000");
// }
// qDebug("Slave Rx Write Single Register. Address: %d, Value: 0x%s",StartAdress, data.toHex().data());
// qDebug("Data: %s",data.toHex().data());
data = Transaction.mPDU.mData.left(4); //The response corresponds to the Reg. Address & the value. Which is the first 4 bytes of the initial request.
SendModbusResponse(Transaction, data);
RegistersDatabaseUpdated(StartAdress,1);
break;
}
case MODBUS_FCT_WRITE_MULTIPLE_REGISTERS:
{
unsigned short StartAdress = 0;
StartAdress = Transaction.mPDU.mData[0]&0xFF;
StartAdress <<= 8;
StartAdress += Transaction.mPDU.mData[1]&0xFF;
unsigned short NbRegisters = 0;
NbRegisters = Transaction.mPDU.mData[2]&0xFF;
NbRegisters <<= 8;
NbRegisters += Transaction.mPDU.mData[3]&0xFF;
quint8 ByteCount = Transaction.mPDU.mData[4];
//Validate nb of registers
if(NbRegisters < 1 || NbRegisters > 0x7D || ByteCount != (NbRegisters*2))
{
qDebug("Invalid register number or byte count ");
SendErrorResponse(Transaction,MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE);
ModbusRequestException(MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,MODBUS_FCT_WRITE_MULTIPLE_REGISTERS);
return 0;
}
//Validate data range
if(!mModbusRepo->IsHRValid(StartAdress,NbRegisters))
{
qDebug("Reg invalid");
//Send negative response
SendErrorResponse(Transaction,MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
ModbusRequestException(MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS,MODBUS_FCT_WRITE_MULTIPLE_REGISTERS);
return 0;
}
//Extract data.
QByteArray data = Transaction.mPDU.mData.right(ByteCount);
//Write register data
mModbusRepo->WriteHRData(StartAdress,NbRegisters,data);
// qDebug("\nSlave Rx Write Multiple Registers. Address: %d, Nb Reg: %d",StartAdress, NbRegisters);
// qDebug("Data: %s\n",data.toHex().data());
data = Transaction.mPDU.mData.left(4); //The response corresponds to the Start Adress and Nb of Regs. Which is the first 4 bytes of the initial request.
SendModbusResponse(Transaction, data);
RegistersDatabaseUpdated(StartAdress,NbRegisters);
break;
}
default:
{
//Received "Illegal function code". Send the exception code to master
//TODO: Log this.
qDebug("Slave received illegal function code from master: 0x%x",Transaction.mPDU.mFunctionCode);
SendErrorResponse(Transaction,MODBUS_EXCEPTION_ILLEGAL_FCT);
ModbusRequestException(MODBUS_EXCEPTION_ILLEGAL_FCT,Transaction.mPDU.mFunctionCode);
break;
}
}
return 1;
}
int CModbusBackend::SendModbusResponse(CModbusTransaction RequestTransaction, QByteArray Data)
{
QByteArray ModbusPacket;
QBuffer Buffer(&ModbusPacket);
Buffer.open(QIODevice::WriteOnly|QIODevice::Unbuffered);
Buffer.seek(0);
QDataStream *PacketDataStrm = new QDataStream(&Buffer);
//For a response, the header will be the same as the original request, except for the msg. length.
//Set the appropriate msg length.
RequestTransaction.mHeader.mMessageLength = Data.size() + 2; //+2 to add function code & Unit ID.
RequestTransaction.mPDU.mData = Data;
*PacketDataStrm << RequestTransaction.mHeader;
*PacketDataStrm << RequestTransaction.mPDU.mFunctionCode;
Buffer.close();
ModbusPacket.append(Data);
// qDebug("Response packet: %s",ModbusPacket.toHex().data());
mModbusTCPSocketHandle->write(ModbusPacket);
delete PacketDataStrm;
return RET_OK;
}
//In Master mode. This is the response from slave to a previously sent request.
int CModbusBackend::AnalyzeModbusResponse(CModbusTransaction Transaction)
{
if(Transaction.mHeader.mProtocolID != 0)
{
//Invalid protocol... what can we do?
return RET_ERROR;
}
//Find matching request and remove it from the queue...
CModbusRequest *Request;
bool Found = false;
for(int i = 0; i < mRequestsList.size(); i++)
{
if(mRequestsList.at(i)->mHeader.mTransactionID == Transaction.mHeader.mTransactionID)
{
Request = mRequestsList.takeAt(i); //Remove from queue and keep a copy
Request->mRequestTimer->stop(); //Stop the resend timer
Found = true;
}
}
if(Found == false)
{
//Invalid request number. This should happen only if a very long delay exists in the comm.
//TODO: Log this...
qDebug("Master received response to a non existent request!!!");
return RET_ERROR;
}
//check if we have an exception response
if((Transaction.mPDU.mFunctionCode & MODBUS_EXCEPTION_FCT_MASK) != 0)
{
//we have an exception response... something went wrong.
quint8 ExceptionCode = Transaction.mPDU.mData[0];
//TODO: Manage this!
qDebug("Master Rx exception code %d to request %d",ExceptionCode,Request->mPDU.mFunctionCode);
emit ModbusResponseException(ExceptionCode,Request->mPDU.mFunctionCode);
delete Request;
return RET_ERROR;
}
switch(Transaction.mPDU.mFunctionCode)
{
case MODBUS_FCT_READ_HOLDING_REGISTERS:
{
quint8 ByteCount = 0;
ByteCount = Transaction.mPDU.mData.at(0);
if((Request->mNbRegisters*2) != ByteCount)
{
//Inconsistency between the data range and the data count.
//TODO: Log the error.
qDebug("Master eceived a wrong data size in response for a MODBUS_FCT_READ_HOLDING_REGISTERS request");
emit ModbusResponseException(MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,MODBUS_FCT_READ_HOLDING_REGISTERS);
delete Request;
return RET_ERROR;
}
QByteArray RegisterValues = Transaction.mPDU.mData.right(ByteCount);
// qDebug("Master Rx Read Holding Registers Response.");
// qDebug("Data: %s",RegisterValues.toHex().data());
mModbusRepo->WriteHRData(Request->mStartAddress,Request->mNbRegisters,RegisterValues);
RegistersDatabaseUpdated(Request->mStartAddress, Request->mNbRegisters);
break;
}
case MODBUS_WRITE_SINGLE_REGISTER:
{
quint16 RegAddress = 0;
RegAddress = Transaction.mPDU.mData[0]&0xFF;
RegAddress <<= 8;
RegAddress += Transaction.mPDU.mData[1]&0xFF;
if(Request->mStartAddress != RegAddress)
{
//Inconsistency between the request Adress and response Adress.
//TODO: Log the error.
qDebug("Master received a wrong Register Adress in response for a MODBUS_WRITE_SINGLE_REGISTER request");
emit ModbusResponseException(MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,MODBUS_WRITE_SINGLE_REGISTER);
delete Request;
return RET_ERROR;
}
// qDebug("Master Rx Write Single Register response. Address: %d,",RegAddress);
// qDebug("Data: %s",Transaction.mPDU.mData.toHex().data());
ModbusWriteSingleRegsSuccess();
//Everything seems good.
break;
}
case MODBUS_FCT_WRITE_MULTIPLE_REGISTERS:
{
unsigned short StartAdress = 0;
StartAdress = Transaction.mPDU.mData[0]&0xFF;
StartAdress <<= 8;
StartAdress += Transaction.mPDU.mData[1]&0xFF;
unsigned short NbRegisters = 0;
NbRegisters = Transaction.mPDU.mData[2]&0xFF;
NbRegisters <<= 8;
NbRegisters += Transaction.mPDU.mData[3]&0xFF;
if(StartAdress != Request->mStartAddress || NbRegisters != Request->mNbRegisters)
{
//Inconsistency between the request Adress or NbRegisters and response.
//TODO: Log the error.
qDebug("Master Received a wrong Register Adress or NbRegisters in response for a MODBUS_FCT_WRITE_MULTIPLE_REGISTERS request");
emit ModbusResponseException(MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,MODBUS_FCT_WRITE_MULTIPLE_REGISTERS);
delete Request;
return RET_ERROR;
}
// qDebug("Master Rx Write Multiple Registers response. Address: %d, Nb Reg: %d",StartAdress, NbRegisters);
// qDebug("Data: %s",Transaction.mPDU.mData.toHex().data());
ModbusWriteMultipleRegsSuccess();
//All is good.
break;
}
default:
{
//Received "Illegal function code" response
//TODO: Log this.
//CEngLog::instance()->AddLogString(QString().sprintf("Modbus Maître, réception d'un code de fonction illégal: 0x%x",Transaction.mPDU.mFunctionCode));
// qDebug("Master received illegal function code 0x%x",Transaction.mPDU.mFunctionCode);
emit ModbusResponseException(MODBUS_EXCEPTION_ILLEGAL_FCT,MODBUS_FCT_WRITE_MULTIPLE_REGISTERS);
break;
}
}
delete Request;
return 1;
}
int CModbusBackend::SendModbusRequest(CModbusRequest *Request)
{
QByteArray ModbusPacket;
QBuffer Buffer(&ModbusPacket);
Buffer.open(QIODevice::WriteOnly|QIODevice::Unbuffered);
Buffer.seek(0);
QDataStream *PacketDataStrm = new QDataStream(&Buffer);
*PacketDataStrm << Request->mHeader;
*PacketDataStrm << Request->mPDU.mFunctionCode;
Buffer.close();
ModbusPacket.append(Request->mPDU.mData);
// qDebug("Request packet: %s",ModbusPacket.toHex().data());
mModbusTCPSocketHandle->write(ModbusPacket);
delete PacketDataStrm;
return RET_OK;
}
int CModbusBackend::SendErrorResponse(CModbusTransaction RequestTransaction, quint8 ErrorCode)
{
QByteArray ModbusPacket;
QBuffer Buffer(&ModbusPacket);
Buffer.open(QIODevice::WriteOnly|QIODevice::Unbuffered);
Buffer.seek(0);
QDataStream *PacketDataStrm = new QDataStream(&Buffer);
//For a response, the header will be the same as the original request, except for the msg. length.
//Set the appropriate msg length.
RequestTransaction.mHeader.mMessageLength = 3; //Unit ID, function code & Exception code.
*PacketDataStrm << RequestTransaction.mHeader;
Buffer.close();
ModbusPacket.append(RequestTransaction.mPDU.mFunctionCode + 0x80);
ModbusPacket.append(ErrorCode);
//CEngLog::instance()->AddLogString(QString("Modbus: Envoi d'un code d'un code d'erreur. Error Code %1, Error Packet: %2").arg(ErrorCode).arg(ModbusPacket.toHex().data()));
// qDebug("Sending error code %d. Error packet: %s",ErrorCode,ModbusPacket.toHex().data());
mModbusTCPSocketHandle->write(ModbusPacket);
delete PacketDataStrm;
return RET_OK;
}
int CModbusBackend::SendReadHoldingRegistersRequest(quint16 StartAddress, quint16 RegisterCount)
{
//First, validate that the reading range is within our repo
if(mModbusRepo->IsHRValid(StartAddress,RegisterCount) == false)
{
//CEngLog::instance()->AddLogString("ModbusBackend: Tentative de lecture d'un registre hors de portée.");
return RET_ERROR;
}
//Create a request.
CModbusRequest *NewRequest = new CModbusRequest;
NewRequest->mStartAddress = StartAddress;
NewRequest->mNbRegisters = RegisterCount;
connect(NewRequest->mRequestTimer,SIGNAL(timeout()),this,SLOT(RequestTimerExpired()));
NewRequest->mPDU.mData.clear();
NewRequest->mPDU.mFunctionCode = MODBUS_FCT_READ_HOLDING_REGISTERS;
quint8 HighByte, LowByte;
LowByte = StartAddress & 0x00FF;
HighByte = (StartAddress >> 8) & 0x00FF;
NewRequest->mPDU.mData.append(HighByte);
NewRequest->mPDU.mData.append(LowByte);
LowByte = RegisterCount & 0x00FF;
HighByte = (RegisterCount >> 8) & 0x00FF;
NewRequest->mPDU.mData.append(HighByte);
NewRequest->mPDU.mData.append(LowByte);
NewRequest->mHeader.mMessageLength = NewRequest->mPDU.mData.size() + 2;
NewRequest->mHeader.mProtocolID = 0;
NewRequest->mHeader.mTransactionID = (qint16)GetNewTransactionID();
NewRequest->mHeader.mUnitID = mDeviceID;
mRequestsList.append(NewRequest);
SendModbusRequest(NewRequest);
NewRequest->mRequestTimer->start(mModbusRequestTimeout);
return RET_OK;
}
int CModbusBackend::SendWriteHoldingRegistersRequest(quint16 StartAddress, quint16 RegisterCount)
{
//First, validate that the reading range is within our repo
if(mModbusRepo->IsHRValid(StartAddress,RegisterCount) == false)
{
//CEngLog::instance()->AddLogString("Trying to send a Write HR in an invalid range SendWriteHoldingRegistersRequest()");
return RET_ERROR;
}
if(RegisterCount > MODBUS_MAX_NB_REGISTERS)
{
return RET_ERROR;
}
//Get data.
bool OK;
QByteArray RegData = mModbusRepo->GetHRData(StartAddress,RegisterCount,&OK);
if(OK == false)
{
return RET_ERROR;
}
//Create a request.
CModbusRequest *NewRequest = new CModbusRequest;
NewRequest->mStartAddress = StartAddress;
NewRequest->mNbRegisters = RegisterCount;
connect(NewRequest->mRequestTimer,SIGNAL(timeout()),this,SLOT(RequestTimerExpired()));
NewRequest->mPDU.mData.clear();
NewRequest->mPDU.mFunctionCode = MODBUS_FCT_WRITE_MULTIPLE_REGISTERS;
//Start address
quint8 HighByte, LowByte;
LowByte = StartAddress & 0x00FF;
HighByte = (StartAddress >> 8) & 0x00FF;
NewRequest->mPDU.mData.append(HighByte);
NewRequest->mPDU.mData.append(LowByte);
//Nb Registers
LowByte = RegisterCount & 0x00FF;
HighByte = (RegisterCount >> 8) & 0x00FF;
NewRequest->mPDU.mData.append(HighByte);
NewRequest->mPDU.mData.append(LowByte);
//Byte Count
NewRequest->mPDU.mData.append(RegData.size());
NewRequest->mPDU.mData.append(RegData);
NewRequest->mHeader.mMessageLength = NewRequest->mPDU.mData.size() + 2;
NewRequest->mHeader.mProtocolID = 0;
NewRequest->mHeader.mTransactionID = (qint16)GetNewTransactionID();
NewRequest->mHeader.mUnitID = mDeviceID;
mRequestsList.append(NewRequest);
SendModbusRequest(NewRequest);
NewRequest->mRequestTimer->start(mModbusRequestTimeout);
return RET_OK;
}
int CModbusBackend::SendWriteSingleRegisterRequest(quint16 Address)
{
//First, validate that the reading range is within our repo
if(mModbusRepo->IsHRValid(Address,1) == false)
{
//CEngLog::instance()->AddLogString("Trying to send a Write HR in an invalid range SendWriteSingleRegisterRequest()");
return RET_ERROR;
}
//Get data.
bool OK;
QByteArray RegData = mModbusRepo->GetHRData(Address,1,&OK);
if(OK == false)
{
return RET_ERROR;
}
//Create a request.
CModbusRequest *NewRequest = new CModbusRequest;
NewRequest->mStartAddress = Address;
NewRequest->mNbRegisters = 1;
connect(NewRequest->mRequestTimer,SIGNAL(timeout()),this,SLOT(RequestTimerExpired()));
NewRequest->mPDU.mData.clear();
NewRequest->mPDU.mFunctionCode = MODBUS_WRITE_SINGLE_REGISTER;
quint8 HighByte, LowByte;
LowByte = Address & 0x00FF;
HighByte = (Address >> 8) & 0x00FF;
NewRequest->mPDU.mData.append(HighByte);
NewRequest->mPDU.mData.append(LowByte);
NewRequest->mPDU.mData.append(RegData);
NewRequest->mHeader.mMessageLength = NewRequest->mPDU.mData.size() + 2;
NewRequest->mHeader.mProtocolID = 0;
NewRequest->mHeader.mTransactionID = (qint16)GetNewTransactionID();
NewRequest->mHeader.mUnitID = mDeviceID;
mRequestsList.append(NewRequest);
SendModbusRequest(NewRequest);
NewRequest->mRequestTimer->start(mModbusRequestTimeout);
return RET_OK;
}
void CModbusBackend::RequestTimerExpired()
{
//find the expired request
for(int i = 0; i < mRequestsList.size(); i++)
{
if(mRequestsList.at(i)->mRequestTimer->isActive() == false)
{
if(mRequestsList.at(i)->mRetries >= mModbusMaxRetry)
{
//The max number of retry has been reached. The device is probably offline.
// CZTLog::instance()->AddLogString("Modbus Maître: Nombre maximal de tentatives sans réponse atteint avec le partenaire",true);
delete mRequestsList[i];
mRequestsList.removeAt(i);
ModbusRequestMaxRetryReached();
//TODO: Manage this situation (log?)
return;
}
else
{
SendModbusRequest(mRequestsList[i]);
mRequestsList.at(i)->mRequestTimer->start(mModbusRequestTimeout);
mRequestsList[i]->mRetries++;
}
}
}
}
quint16 CModbusBackend::GetNewTransactionID()
{
quint16 ID = mTransactionIDCounter++;
if(mTransactionIDCounter == 0xFFFF - 10)
{
mTransactionIDCounter = 0;
}
return ID;
}
CModbusRequest::CModbusRequest():
mRetries(0)
{
mRequestTimer = new QTimer;
mRequestTimer->setSingleShot(true);
}
CModbusRequest::~CModbusRequest()
{
delete mRequestTimer;
}
QDataStream &operator<<(QDataStream &out, const CModbusHeader &source)
{
out << source.mTransactionID
<< source.mProtocolID
<< source.mMessageLength
<< source.mUnitID
;
return out;
}
QDataStream &operator>>(QDataStream &in, CModbusHeader &dest)
{
in >> dest.mTransactionID
>> dest.mProtocolID
>> dest.mMessageLength
>> dest.mUnitID
;
return in;
}
//Virtual function that should not even get called...
void CModbusBackend::ModbusResponseException(quint8 ExceptionCode, quint8 FctCode)
{
Q_UNUSED(ExceptionCode)
Q_UNUSED(FctCode)
//CEngLog::instance()->AddLogString("ModbusResponseException called from within slave object... weird stuff!");
}
//Virtual function that should not even get called...
void CModbusBackend::ModbusRequestException(quint8 ExceptionCode, quint8 FctCode)
{
Q_UNUSED(ExceptionCode)
Q_UNUSED(FctCode)
//CEngLog::instance()->AddLogString("ModbusResponseException called from within master object... weird stuff!");
}
void CModbusBackend::ModbusWriteMultipleRegsSuccess()
{
qDebug("Weird! Default implementation of ModbusWriteMultipleRegsSuccess() called in CModbusBackend. This function should be reimplemented in the ModbusMaster child class");
}
void CModbusBackend::ModbusWriteSingleRegsSuccess()
{
qDebug("Weird! Default implementation of ModbusWriteSingleRegsSuccess() called in CModbusBackend. This function should be reimplemented in the ModbusMaster child class");
}
void CModbusBackend::ModbusRequestMaxRetryReached()
{
qDebug("Weird! Default implementation of ModbusRequestMaxRetryReached() called in CModbusBackend. This function should be reimplemented in the ModbusMaster child class");
}
+203
View File
@@ -0,0 +1,203 @@
/*******************************************************************************/
/*
Description:
Classe qui implémente le protocole Modbus.
Cette classe est héritée par la classe serveur (CModbusCCMgr) ou client (CModbusMaster).
Elle sert à interpréter ou formatter des paquets Modbus provenant ou destinés au partenaire.
Elle peut être héritée soit un parent qui implémente une instance maître ou client mais pas les deux à la fois.
sur le réseau.
*/
/* ************************************************************************** */
/* Revision:
### YYYMMDD JFM
Verision d'origine.
### YYYYMMDD Description du besoin ou du bug
Description du changement.
*/
/* ************************************************************************** */
#ifndef CMODBUSBACKEND_H
#define CMODBUSBACKEND_H
#include <QObject>
#include <QTcpSocket>
#include "ModbusRepository.h"
#include <QTimer>
#define MODBUS_EXCEPTION_FCT_MASK 0x80
#define MODBUS_RETRY_DELAY 2000 //millisecs
#define MODBUS_RETRY_MAX_COUNT 2 //resend an unanswered request this many times
#define MODBUS_MAX_NB_REGISTERS 0x7D
enum eModbusFunctions
{
MODBUS_FCT_READ_HOLDING_REGISTERS = 3,
MODBUS_WRITE_SINGLE_REGISTER = 6,
MODBUS_FCT_WRITE_MULTIPLE_REGISTERS = 16
};
enum eModbusExceptionCodes
{
MODBUS_EXCEPTION_ILLEGAL_FCT = 1,
MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS = 2,
MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE = 3,
MODBUS_EXCEPTION_SERVER_DEVICE_FAILURE = 4,
MODBUS_EXCEPTION_ACKNOWLEDGE = 5,
MODBUS_EXCEPTION_SERVER_DEVICE_BUSY = 6,
MODBUS_EXCEPTION_MEMORY_PARITY_ERROR = 8,
MODBUS_EXCEPTION_GATEWAY_PATH_UNAVAILABLE = 10,
MODBUS_EXCEPTION_GATEWAY_TARGET_DEV_NOT_RESPONDING = 11
};
//Énumération des différents modes possibles
enum eModbusModes
{
MODBUS_MASTER_MODE,
MODBUS_SLAVE_MODE,
MODBUS_INVALID_MODE
};
class CModbusException
{
public:
};
class CModbusPDU
{
public:
qint8 mFunctionCode; //Type de transaction (voir structure eModbusFunctions) pour les fonctions implémentées par le présent module
QByteArray mData; //Les données de la transaction
};
class CModbusHeader //Objet contenant les données d'un header Modbus (selon la spécification)
{
public:
qint16 mTransactionID;
qint16 mProtocolID;
qint16 mMessageLength;
qint8 mUnitID;
};
class CModbusTransaction //Objet contenant les données d'une transaction Modbus
{
public:
CModbusHeader mHeader; //Le header de la transaction
CModbusPDU mPDU; //Le PDU (fonction & données) de la transaction (voir classe CModbusPDU)
};
class CModbusRequest : public CModbusTransaction //Objet représentant une transaction (mode maître)
{
public:
CModbusRequest();
~CModbusRequest();
int mRetries; //nombre de fois que la transaction a été retransmise
QTimer *mRequestTimer; //Timer servant à retransmettre la requête si aucune réponse ne survient
quint16 mStartAddress, mNbRegisters; //For convinience...
};
class CModbusBackend : public QObject
{
Q_OBJECT
private:
//enum eModbusMasterSMStates
//{
// MODBUS_SM_WAIT_FOR__STATE
//};
//enum eModbusSlaveSMStates
//{
//};
public:
CModbusBackend(CModbusRepository *Repo); //Le pointeur vers le registre des données est obligatoire.
virtual ~CModbusBackend();
QTcpSocket *mModbusTCPSocketHandle; //Pointeur vers le Socket TCP du réseau Modbus.
CModbusRepository *mModbusRepo; //Pointeur vers le registre de données Modbus
bool mDataLinkValid; //Flag indiquant si le lien de communication est en santé
int mModbusMode; //Indique si le lien modbus de cette instance est exploitée en "slave" (serveur) ou un "master" (client) (voir struct eModbusModes ci-haut).
qint8 mDeviceID; //Le device ID Modbus de cette instance.
void ModbusLinkDisconnected(); //Fonction appelée par le parent lors d'une perte de la connection.
void SetDeviceID(qint8 ID){mDeviceID = ID;}
//Master (client)
int SendReadHoldingRegistersRequest(quint16 StartAddress, quint16 RegisterCount); //Fonction servant à transmettre la fonction modbus "Read HR" au partenaire
int SendWriteHoldingRegistersRequest(quint16 StartAddress, quint16 RegisterCount); //Fonction servant à transmettre la fonction modbus "Write HR" au partenaire
int SendWriteSingleRegisterRequest(quint16 Address); //Fonction servant à transmettre la fonction modbus "Write Single Register" au partenaire
virtual void RegistersDatabaseUpdated(quint16 StartAdderss, quint16 Length) = 0; //Fonction virtuelle à être implémentée par la classe dérivée
//Appelée lorsque de nouvelles données ont été reçues du réseau Modbus et écrites dans le registre
//Master virtual functions
virtual void ModbusResponseException(quint8 ExceptionCode, quint8 FctCode); //Fonction virtuelle à être implémentée par la classe dérivée
virtual void ModbusWriteMultipleRegsSuccess(); //Fonction virtuelle à être implémentée par la classe dérivée (Modbus Master seulement). Appelée suite à la réception d'une réponse à une requête d'écriture multiple.
virtual void ModbusWriteSingleRegsSuccess(); //Fonction virtuelle à être implémentée par la classe dérivée (Modbus Master seulement). Appelée suite à la réception d'une réponse à une requête d'écriture simple.
virtual void ModbusRequestMaxRetryReached(); //Fonction virtuelle à être implémentée par la classe dérivée (Modbus Master seulement). Appelée lors de l'atteinte du nombre maximal d'essai de transmission d'une requête.
//Sert à informer la classe dérivée lorsqu'une exception se produit dans la communication Modbus (réponse)
//Slave virtual function
virtual void ModbusRequestException(quint8 ExceptionCode, quint8 FctCode); //Fonction virtuelle à être implémentée par la classe dérivée
//Sert à informer le parent lorsqu'une exception se produit dans la communication Modbus (requête)
private:
//Sur un réseau Modbus, l'eslcave (slave) est un serveur et le maître (master) est un client.
//Slave (server)
int AnalyzeModbusRequest(CModbusTransaction Transaction); //Exécute l'analyse des données reçues d'une requête Modbus
int SendModbusResponse(CModbusTransaction RequestTransaction, QByteArray Data); //Fonction servant à composer et transmettre une réponse à une requête.
int SendErrorResponse(CModbusTransaction RequestTransaction, quint8 ErrorCode); //Fonction servant à composer et transmettre une réponse d'erreur à une requête.
//Master (client)
QList<CModbusRequest*> mRequestsList; //Liste des requêtes courantes en attente d'une réponse du serveur
int SendModbusRequest(CModbusRequest *Request); //Construit et transmet une nouvelle requête Modbus au partenaire.
quint16 GetNewTransactionID(); //Fonction (helper) servant à générer un nouvel ID de transaction nécessaire pour transmettre une nouvelle requête.
int AnalyzeModbusResponse(CModbusTransaction Transaction); //Exécute l'analyse des données reçues d'une réponse Modbus
quint16 mTransactionIDCounter; //Compteur interne des ID de transactions Modbus
int mModbusRequestTimeout; //Valeur du délai d'attente maximal avant de recevoir une réponse à une requête. À l'expiration du timer, la requête est renvoyée.
int mModbusMaxRetry; //Valeur du nombre maximal de renvoi d'une requête avant d'invalider la requête.
signals:
// void RegistersDatabaseUpdated(quint16 StartAdderss, quint16 Length);
// //Master signals
// void ModbusResponseException(quint8 ExceptionCode, quint8 FctCode);
//// //Slave signals
// void ModbusRequestException(quint8 ExceptionCode, quint8 FctCode);
public slots:
void ModbusDataReady(); //Slot indiquant au module que des données sont arrivées et sont attente dans le Socket.
void RequestTimerExpired(); //Slot indiquant qu'un timer de requête a expiré.
};
QDataStream &operator<<(QDataStream &out, const CModbusHeader &source);
QDataStream &operator>>(QDataStream &in, CModbusHeader &dest);
#endif // CMODBUSBACKEND_H
+220
View File
@@ -0,0 +1,220 @@
#include "ModbusRepository.h"
#include <QByteArray>
#include <QBuffer>
#include <QDataStream>
//#include "EngLog.h"
CModbusRepository::CModbusRepository()
{
}
int CModbusRepository::AddHRDataMap(quint16 StartAddress, quint16 Length)
{
mMutex.lockForWrite();
for(int i= 0; i < mHoldingRegisters.size(); i++)
{
if((StartAddress >= mHoldingRegisters.at(i).mStartAddress && StartAddress <= mHoldingRegisters.at(i).mEndAddress) ||
(StartAddress + Length - 1 >= mHoldingRegisters.at(i).mStartAddress && StartAddress + Length - 1 <= mHoldingRegisters.at(i).mEndAddress))
{
mMutex.unlock();
return RET_ERROR; //The data map overlaps an existing map.
}
}
CHRDataMap NewHRMap(StartAddress,Length);
mHoldingRegisters.append(NewHRMap);
mMutex.unlock();
// CEngLog::instance()->AddLogString(QString("Création d'une zone registre modbus à l'adresse: %1 de taille %2").arg(StartAddress).arg(Length));
return RET_OK;
}
bool CModbusRepository::IsHRValid(quint16 StartAddress, quint16 Length, int *index)
{
mMutex.lockForRead();
for(int i= 0; i < mHoldingRegisters.size(); i++)
{
if((StartAddress >= mHoldingRegisters.at(i).mStartAddress && StartAddress + Length - 1 <= mHoldingRegisters.at(i).mEndAddress))
{
if(index != 0)
{
*index = i;
}
mMutex.unlock();
return true;
}
}
mMutex.unlock();
return false;
}
QByteArray CModbusRepository::GetHRData(quint16 StartAddress, quint16 Length, bool *OK)
{
QByteArray Data;
int RegisterIndex = 0;
if(IsHRValid(StartAddress,Length, &RegisterIndex) == false)
{
if(OK != 0)
{
*OK = false;
}
}
else
{
mMutex.lockForRead();
int DataIndex = StartAddress - mHoldingRegisters.at(RegisterIndex).mStartAddress;
for(int i = 0; i < Length; i++)
{
quint16 CurReg = mHoldingRegisters.at(RegisterIndex).mRegistersData.at(DataIndex++);
quint8 HighByte, LowByte;
LowByte = CurReg & 0x00FF;
HighByte = (CurReg >> 8) & 0x00FF;
Data.append(HighByte);
Data.append(LowByte);
}
mMutex.unlock();
if(OK != 0)
*OK = true;
}
return Data;
}
int CModbusRepository::WriteHRData(quint16 StartAddress, quint16 Length, QByteArray Data)
{
int RegisterIndex;
if(IsHRValid(StartAddress,Length, &RegisterIndex) == false)
{
return RET_ERROR;
}
if(Length*2 != Data.size())
{
return RET_ERROR;
}
mMutex.lockForWrite();
int DataIndex = StartAddress - mHoldingRegisters.at(RegisterIndex).mStartAddress;
for(int i = 0; i < Length; i++)
{
quint8 HighByte = Data.at(i*2);
quint8 LowByte = Data.at(i*2 + 1);
qint16 Word = HighByte;
Word <<= 8;
Word += LowByte;
Word = Word &0xFFFF;
mHoldingRegisters[RegisterIndex].mRegistersData[DataIndex++] = Word;
}
mMutex.unlock();
emit RepoChanged(StartAddress,Length);
return RET_OK;
}
int CModbusRepository::WriteSingleReg(quint16 Address, quint16 Value)
{
int RegisterIndex;
if(IsHRValid(Address,1, &RegisterIndex) == false)
{
return RET_ERROR;
}
mMutex.lockForWrite();
int DataIndex = Address - mHoldingRegisters.at(RegisterIndex).mStartAddress;
mHoldingRegisters[RegisterIndex].mRegistersData[DataIndex] = Value;
mMutex.unlock();
emit RepoChanged(Address,1);
return RET_OK;
}
int CModbusRepository::WriteMultipleRegs(quint16 StartAddress, QList<qint16> Data)
{
int RegisterIndex;
if(IsHRValid(StartAddress,Data.size(), &RegisterIndex) == false)
{
return RET_ERROR;
}
mMutex.lockForWrite();
int DataIndex = StartAddress - mHoldingRegisters.at(RegisterIndex).mStartAddress;
for(int i = 0; i < Data.size(); i++)
{
mHoldingRegisters[RegisterIndex].mRegistersData[DataIndex++] = Data.at(i);
}
mMutex.unlock();
emit RepoChanged(StartAddress,Data.size());
return RET_OK;
}
QList<qint16> CModbusRepository::GetRegs(quint16 StartAddress, quint16 Length, bool *OK)
{
int RegisterIndex;
if(IsHRValid(StartAddress,Length, &RegisterIndex) == false)
{
if(OK != 0)
{
*OK = false;
}
return QList<qint16>();
}
if(OK != 0)
{
*OK = true;
}
int DataIndex = StartAddress - mHoldingRegisters.at(RegisterIndex).mStartAddress;
return mHoldingRegisters[RegisterIndex].mRegistersData.mid(DataIndex,Length);
}
quint16 CModbusRepository::GetSingleReg(quint16 Address, bool *OK)
{
int RegisterIndex;
if(IsHRValid(Address,1, &RegisterIndex) == false)
{
if(OK != 0)
*OK = false;
return 0;
}
if(OK != 0)
*OK = true;
mMutex.lockForRead();
int DataIndex = Address - mHoldingRegisters.at(RegisterIndex).mStartAddress;
quint16 Data = mHoldingRegisters[RegisterIndex].mRegistersData[DataIndex];
mMutex.unlock();
return Data;
}
CHRDataMap::CHRDataMap(quint16 StartAddress, quint16 Length)
{
mStartAddress = StartAddress;
mLength = Length;
for(int i = 0; i < Length; i++)
{
mRegistersData.append(0);
}
mEndAddress = StartAddress + Length - 1;
}
+44
View File
@@ -0,0 +1,44 @@
#ifndef MODBUSREPOSITORY_H
#define MODBUSREPOSITORY_H
#include "GlobalDefine.h"
#include <QList>
#include <QReadWriteLock>
class CHRDataMap ///Holding Register data map.
{
public:
CHRDataMap(quint16 StartAddress, quint16 Length);
quint16 mStartAddress,mEndAddress;
quint16 mLength;
QList<qint16> mRegistersData;
};
class CModbusRepository : public QObject
{
Q_OBJECT
public:
CModbusRepository();
//Holding Registers
int AddHRDataMap(quint16 StartAddress, quint16 Length);
bool IsHRValid(quint16 StartAddress, quint16 Length, int *index = 0);
QByteArray GetHRData(quint16 StartAddress, quint16 Length, bool *OK = 0);
quint16 GetSingleReg(quint16 Address, bool *OK = 0);
QList<qint16> GetRegs(quint16 StartAddress, quint16 Length, bool *OK = 0);
int WriteHRData(quint16 StartAddress, quint16 Length, QByteArray Data);
int WriteSingleReg(quint16 Address, quint16 Value);
int WriteMultipleRegs(quint16 StartAddress, QList<qint16> Data);
const QList<CHRDataMap> *GetRepo(){return &mHoldingRegisters;}
private:
QList<CHRDataMap> mHoldingRegisters;
QReadWriteLock mMutex;
signals:
void RepoChanged(quint16 StartAddress, quint16 Length);
};
#endif // MODBUSREPOSITORY_H