blob: 2476ccb26ae7ff8d2eef5354153de4643d2cee45 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012 - 2023 AIT, Davor Cihlar
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Filip Andren - initial API and implementation and/or initial documentation
* Davor Cihlar - multiple FBs sharing a single Modbus connection
*******************************************************************************/
#include "modbusclientconnection.h"
#include "devlog.h"
#include "modbuspoll.h"
#include <forte_thread.h>
#include <unistd.h> // for sleep
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#ifndef WIN32
// for open and disabling DTR
#include <termios.h>
#endif
using namespace modbus_connection_event;
/*************************************
* CModbusClientConnection class
*************************************/
CModbusClientConnection::CModbusClientConnection(CModbusHandler* pa_modbusHandler) :
CModbusConnection(pa_modbusHandler), mModbusConnEvent(nullptr), mSlaveId(0xFF){
}
CModbusClientConnection::~CModbusClientConnection() {
if (mConnected){
disconnect();
}
for (auto itRunner : mPollList) {
delete itRunner;
}
if (mModbusConnEvent != nullptr){
delete mModbusConnEvent;
}
}
int CModbusClientConnection::readData(CModbusIOBlock* paIOBlock, void* paData, unsigned int paMaxDataSize){
const unsigned int size = std::min(paMaxDataSize, paIOBlock->getReadSize());
memcpy(paData, paIOBlock->getCache(), size);
return (int)size;
}
void CModbusClientConnection::writeDataRange(EModbusFunction paFunction, unsigned int paStartAddress, unsigned int paNrAddresses, const void *paData){
CCriticalRegion criticalRegion(mModbusLock);
if (!mConnected) {
// TODO: error
return;
}
switch (paFunction) {
case eCoil:
modbus_write_bits(mModbusConn, paStartAddress, paNrAddresses, (const uint8_t*)paData);
break;
case eHoldingRegister:
modbus_write_registers(mModbusConn, paStartAddress, paNrAddresses, (const uint16_t*)paData);
break;
default:
// TODO: error
break;
}
}
int CModbusClientConnection::connect(){
CModbusConnection::connect();
if(mSlaveId != 0xFF){
modbus_set_slave(mModbusConn, mSlaveId);
}
mModbusConnEvent = new CModbusConnectionEvent(1000, getFlowControl(), getDevice());
mModbusConnEvent->activate();
this->start();
return 0;
}
void CModbusClientConnection::disconnect(){
this->end();
if (mConnected){
modbus_close(mModbusConn);
mConnected = false;
}
CModbusConnection::disconnect();
}
void CModbusClientConnection::addNewPoll(long paPollInterval, CModbusIOBlock* paIOBlock){
CModbusPoll *newPoll = nullptr;
for (auto it : mPollList) {
if(it->getUpdateInterval() == paPollInterval){
newPoll = it;
break;
}
}
if(newPoll == nullptr){
newPoll = new CModbusPoll(mModbusHandler, paPollInterval);
mPollList.push_back(newPoll);
}
newPoll->addPollBlock(paIOBlock);
}
void CModbusClientConnection::setSlaveId(unsigned int paSlaveId){
mSlaveId = paSlaveId;
}
void CModbusClientConnection::run(){
while(isAlive()){
if(mConnected){
tryPolling();
}
else{
tryConnect();
}
CThread::sleepThread(1);
}
}
void CModbusClientConnection::tryPolling(){
unsigned int nrErrors = 0, nrPolls = 0;
for (size_t index = 0; index < mPollList.size(); ++index) {
auto itPoll = mPollList[index];
if(itPoll->readyToExecute()){
CCriticalRegion criticalRegion(mModbusLock);
int nrVals = itPoll->executeEvent(mModbusConn, 0);
if(nrVals < 0){
DEVLOG_ERROR("Error reading input status :: %s\n", modbus_strerror(errno));
itPoll->deactivate();
nrErrors++;
}
++nrPolls;
}
}
if((nrErrors == nrPolls) && nrPolls && !mPollList.empty()){
DEVLOG_WARNING("Too many errors on Modbus, reconnecting\n");
CCriticalRegion criticalRegion(mModbusLock);
modbus_close(mModbusConn); // in any case it is worth trying to close the socket
mConnected = false;
mModbusConnEvent = new CModbusConnectionEvent(1000, getFlowControl(), getDevice());
mModbusConnEvent->activate();
}
}
void CModbusClientConnection::tryConnect(){
if(mModbusConnEvent != nullptr){
if(mModbusConnEvent->readyToExecute()){
CCriticalRegion criticalRegion(mModbusLock);
if(mModbusConnEvent->executeEvent(mModbusConn, nullptr) < 0) {
DEVLOG_ERROR("Connection to Modbus server failed: %s\n", modbus_strerror(errno));
} else {
DEVLOG_INFO("Connection to Modbus server succeded\n");
delete mModbusConnEvent;
mModbusConnEvent = nullptr;
mConnected = true;
// Start polling
for (auto itPoll : mPollList) {
itPoll->activate();
}
}
}
}
}
/*************************************
* CModbusConnectionEvent class
*************************************/
CModbusConnectionEvent::CModbusConnectionEvent(long paReconnectInterval, EModbusFlowControl paFlowControl, const char *paDevice) :
CModbusTimedEvent((TForteUInt32)paReconnectInterval), mFlowControl(paFlowControl){
strcpy(mDevice, paDevice);
}
int CModbusConnectionEvent::executeEvent(modbus_t *paModbusConn, void *paRetVal){
(void)paRetVal; // avoid warning
restartTimer();
switch (mFlowControl) {
#ifndef WIN32
case eFlowArduino: {
int fd = open(mDevice, O_RDWR);
if (fd >= 0) {
termios tty;
tcgetattr(fd, &tty);
if (tty.c_cflag & (HUPCL | CRTSCTS)) {
tty.c_cflag &= ~(HUPCL | CRTSCTS);
if (!tcsetattr(fd, TCSANOW, &tty)) {
DEVLOG_INFO("Hardware flow control for Modbus RTU disabled\n");
// Disabling DTR is not perfect and it will be toggled by this open for disabling it.
// Therefore, wait for Arduino to boot only if flags weren't previously set.
sleep(2);
} else {
DEVLOG_ERROR("Failed disabling flow control for Modbus RTU\n");
return -1;
}
} else {
DEVLOG_INFO("Hardware flow control for Modbus RTU was already disabled\n");
}
close(fd);
}
break;
}
#endif
default:
// ignore
break;
};
int retVal = modbus_connect(paModbusConn);
if (retVal >= 0) {
switch (mFlowControl) {
case eFlowLongDelay:
sleep(3);
// fall through
case eFlowDelay:
sleep(2);
break;
default:
// ignore
break;
}
}
return retVal;
}