From 9a15c843759f2088d835aeb11ba7dd448a101bd7 Mon Sep 17 00:00:00 2001 From: aschwinn <al.schwinn@gsi.de> Date: Wed, 26 Jul 2017 10:10:13 +0200 Subject: [PATCH] [SIL-?] Refactor the low level API for writing and reading using libmodbus, move timing conversion functionality to MBHardware and SNAP7Hardware files and refactor the low level error handling --- .../interface/communication/MBConnection.cpp | 68 ++- .../interface/communication/MBConnection.h | 3 + .../interface/communication/MBHardware.cpp | 98 +++++ .../interface/communication/MBHardware.h | 54 +++ .../communication/SNAP7Connection.cpp | 411 ++++++++++-------- .../interface/communication/SNAP7Connection.h | 3 +- .../interface/communication/SNAP7Hardware.cpp | 75 ++++ .../interface/communication/SNAP7Hardware.h | 47 ++ .../communication/SilecsConnection.cpp | 168 ++++++- .../communication/SilecsConnection.h | 5 + .../interface/equipment/PLCRegister.cpp | 2 + .../protocol/core/silecs.cpp | 253 +---------- .../protocol/core/silecs.h | 32 -- .../protocol/modbus/iemdb.cpp | 144 ------ 14 files changed, 746 insertions(+), 617 deletions(-) create mode 100644 silecs-communication-cpp/src/silecs-communication/interface/communication/MBHardware.cpp create mode 100644 silecs-communication-cpp/src/silecs-communication/interface/communication/MBHardware.h create mode 100644 silecs-communication-cpp/src/silecs-communication/interface/communication/SNAP7Hardware.cpp create mode 100644 silecs-communication-cpp/src/silecs-communication/interface/communication/SNAP7Hardware.h diff --git a/silecs-communication-cpp/src/silecs-communication/interface/communication/MBConnection.cpp b/silecs-communication-cpp/src/silecs-communication/interface/communication/MBConnection.cpp index e99ee89..ccceca4 100644 --- a/silecs-communication-cpp/src/silecs-communication/interface/communication/MBConnection.cpp +++ b/silecs-communication-cpp/src/silecs-communication/interface/communication/MBConnection.cpp @@ -21,6 +21,10 @@ #include <silecs-communication/interface/utility/SilecsException.h> #include <silecs-communication/protocol/modbus/iemdb.h> +//Modbus max data size definition (byte counting) +#define MAX_WRITE_DATA_SIZE MODBUS_MAX_WRITE_REGISTERS*2 +#define MAX_READ_DATA_SIZE MODBUS_MAX_READ_REGISTERS*2 + namespace Silecs { @@ -98,7 +102,7 @@ namespace Silecs //DATA topic makes sense with RECV one if (RECV & Log::topics_) LOG(DATA) << "Read data, address: %MW" << addr << ", byte-size: " << size; - int err = IeMBreadData(readCtx_, addr, (unsigned short)size, pBuffer); + int err = mbReadData(readCtx_, addr, (unsigned short)size, pBuffer); if(error) throw SilecsException(__FILE__, __LINE__, UNEXPECTED_ERROR," ModBus Error: " + string(IeGetErrorMessage(error))); } @@ -131,12 +135,72 @@ namespace Silecs //DATA topic makes sense with SEND one if (SEND & Log::topics_) LOG(DATA) << "Write data, address: %MW" << addr << ", byte-size: " << size; - int error = IeMBwriteData(writeCtx_, (unsigned short)addr, (unsigned short)size, pBuffer); + int error = mbWriteData(writeCtx_, (unsigned short)addr, (unsigned short)size, pBuffer); if(error) throw SilecsException(__FILE__, __LINE__, UNEXPECTED_ERROR," ModBus Error: " + string(IeGetErrorMessage(error))); } return 0; } + /*----------------------------------------------------------*/ + int MBConnection::mbWriteData(modbus_t* ctx, uint16_t start_addr, uint16_t count, uint8_t* data) + { + uint16_t *srcp = (uint16_t*)data; + int word_count, byte_count; + + while(count) + { + word_count = (count > MAX_WRITE_DATA_SIZE) ? MAX_WRITE_DATA_SIZE/2 : (count/2)+(count%2); + byte_count = (word_count * 2); + + if (modbus_write_registers(ctx, start_addr, word_count, srcp) != word_count) + { + return errno; + } + + //srcp += byte_count-(count%2); + srcp += word_count; + start_addr += word_count; + count -= (byte_count - (count%2)); + } + + return 0; + } + + /*----------------------------------------------------------*/ + int MBConnection::mbReadData(modbus_t* ctx, uint16_t start_addr, uint16_t count, uint8_t* data) + { + uint16_t *destp = (uint16_t*)data; + int word_count, byte_count; + + while(count) { + + if (count > MAX_READ_DATA_SIZE) + { + /*'word_count' is a word counter*/ + word_count = MAX_READ_DATA_SIZE/2; + } + else + { + /*'word_count' is a word counter*/ + word_count = (count/2) + (count%2); + } + + byte_count = (word_count * 2); + + if (modbus_read_registers(ctx, start_addr, word_count, destp) != word_count) + { + return errno; + } + + //destp += (byte_count-(count%2)); + destp += word_count; + start_addr += word_count; + count -= (byte_count - (count%2)); + } + + return 0; + } + } // namespace #endif //MODBUS_SUPPORT_ENABLED diff --git a/silecs-communication-cpp/src/silecs-communication/interface/communication/MBConnection.h b/silecs-communication-cpp/src/silecs-communication/interface/communication/MBConnection.h index fd67d2d..0cfc8de 100644 --- a/silecs-communication-cpp/src/silecs-communication/interface/communication/MBConnection.h +++ b/silecs-communication-cpp/src/silecs-communication/interface/communication/MBConnection.h @@ -42,6 +42,9 @@ namespace Silecs modbus_t* writeCtx_; bool open(PLC* thePLC); bool close(PLC* thePLC); + int mbWriteData(modbus_t* ctx, uint16_t dataAddr, uint16_t dataSize, uint8_t* dataBuffer); + int mbReadData(modbus_t* ctx, uint16_t dataAddr, uint16_t dataSize, uint8_t* dataBuffer); + bool checkError(PLC* thePLC, int err, bool retry); }; diff --git a/silecs-communication-cpp/src/silecs-communication/interface/communication/MBHardware.cpp b/silecs-communication-cpp/src/silecs-communication/interface/communication/MBHardware.cpp new file mode 100644 index 0000000..35a51cf --- /dev/null +++ b/silecs-communication-cpp/src/silecs-communication/interface/communication/MBHardware.cpp @@ -0,0 +1,98 @@ +// Copyright 2016 CERN and GSI +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +#ifdef MODBUS_SUPPORT_ENABLED +#include "MBHardware.h" + +/*----------------------------------------------------------*/ +/* Time funtion + * IeMdbSetTime: Convert lynxos time_t format to PLC SCHNEIDER + * format. + * + * Details: + * PLC SCHNEIER format is coded on 8 bytes BCD + * format: + * + * 0 1 2 3 4 5 6 7 + * SC 00 HH MN MM DD YY YY + * + * byte 1 is used by gateway to synchronize time setting. + */ +void IeMdbSetTime(unsigned char *dt, time_t epoch) // to PLC +{ + struct tm *dscst; + dscst = localtime(&epoch); + + /*v1.7*/ + /*In fact, each 16bits word is swapped with SCHNEIDER PLC + 1 0 3 2 5 4 7 6 + SC 00 HH MN MM DD YY YY + + Swapping can do it directly during data transferring + */ + + dt[1] = _tobcd(dscst->tm_sec); + dt[0] = 0x00; /*not used*/ + dt[3] = _tobcd(dscst->tm_hour); + dt[2] = _tobcd(dscst->tm_min); + dt[5] = _tobcd(dscst->tm_mon+1); + dt[4] = _tobcd(dscst->tm_mday); + dt[7] = _tobcd(2000/100); + dt[6] = _tobcd(dscst->tm_year-100); +} + +/*----------------------------------------------------------*/ +/* Time funtion + * IeMdbGetTime: Convert PLC SCHNEIDER format to time_t format. + * + * Details: + * PLC SCHNEIER format is coded on 8 bytes BCD + * format: + * + * 0 1 2 3 4 5 6 7 + * SC SC/100 HH MN MM DD YY YY + * + */ +double IeMdbGetTime(unsigned char *dt) // from PLC +{ + struct tm plcst; + time_t plctm; + double ms; + int year; + + /*In fact, each 16bits word is swapped with SCHNEIDER PLC + 1 0 3 2 5 4 7 6 + SC SC/100 HH MN MM DD YY YY + + Swapping can do it directly during data transferring + */ + + plcst.tm_sec = _frombcd(dt[1]); + plcst.tm_hour = _frombcd(dt[3]); + plcst.tm_min = _frombcd(dt[2]); + plcst.tm_mon = _frombcd(dt[5])-1; + plcst.tm_mday = _frombcd(dt[4]); + year = _frombcd(dt[6]); + //look at Schneider DATA_AND_TIME type documentation + plcst.tm_year = ((year >= 90) ? year : year+100); + /*plcst.tm_wday = no used in PLC*/ + /*plcst.tm_yday = no used in PLC*/ + + ms = ((double)_frombcd(dt[0])/100.); + plcst.tm_isdst = -1; // daylight saving time unavailable + plctm = mktime(&plcst); + + return((double)plctm + ms); +} +#endif //MODBUS_SUPPORT_ENABLED diff --git a/silecs-communication-cpp/src/silecs-communication/interface/communication/MBHardware.h b/silecs-communication-cpp/src/silecs-communication/interface/communication/MBHardware.h new file mode 100644 index 0000000..c436c29 --- /dev/null +++ b/silecs-communication-cpp/src/silecs-communication/interface/communication/MBHardware.h @@ -0,0 +1,54 @@ +// Copyright 2016 CERN and GSI +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +#ifdef MODBUS_SUPPORT_ENABLED +#ifndef _MBHARDWARE_H_ +#define _MBHARDWARE_H_ + +#include <silecs-communication/protocol/core/ietype.h> +#include <modbus.h> + +/*----------------------------------------------------------*/ +/* Time funtion + * IeMdbSetTime: Convert time_t epoch date to PLC SCHNEIDER + * format. + * + * Details: + * PLC SCHNEIER format is coded on 8 bytes BCD + * format: + * + * 0 1 2 3 4 5 6 7 + * SC -- HH MN MM DD YY YY + * + * byte 1 is used by gateway to synchronize time setting. + */ +void IeMdbSetTime(unsigned char *dt, time_t epoch); // to PLC + +/*----------------------------------------------------------*/ +/* Time funtion + * IeMdbGetTime: Convert PLC SCHNEIDER format to lynxOS + * time_t format. + * + * Details: + * PLC SCHNEIER format is coded on 8 bytes BCD + * format: + * + * 0 1 2 3 4 5 6 7 + * SC SC/100 HH MN MM DD YY YY + * + */ +double IeMdbGetTime(unsigned char *dt); // from PLC + +#endif /* _MBHARDWARE_H_ */ +#endif //MODBUS_SUPPORT_ENABLED diff --git a/silecs-communication-cpp/src/silecs-communication/interface/communication/SNAP7Connection.cpp b/silecs-communication-cpp/src/silecs-communication/interface/communication/SNAP7Connection.cpp index 861afc4..02859a6 100644 --- a/silecs-communication-cpp/src/silecs-communication/interface/communication/SNAP7Connection.cpp +++ b/silecs-communication-cpp/src/silecs-communication/interface/communication/SNAP7Connection.cpp @@ -25,238 +25,267 @@ namespace Silecs { - SNAP7Connection::SNAP7Connection(PLC* thePLC) : Connection(thePLC) - { - rackNb_ = 0; //rackNb - common rack is 0 so far - slotNb_ = -1; //slotNb - depends on hardware configuration (scan is required the first connect) - - recvClient_ = Cli_Create(); - sendClient_ = Cli_Create(); - LOG(ALLOC) << "SNAP7Connection (create) PLC/Cluster: " << thePLC->getName() << "/" << thePLC->theCluster_->getClassName(); - } - +SNAP7Connection::SNAP7Connection(PLC* thePLC) : + Connection(thePLC) +{ + rackNb_ = 0; //rackNb - common rack is 0 so far + slotNb_ = -1; //slotNb - depends on hardware configuration (scan is required the first connect) - SNAP7Connection::~SNAP7Connection() - { - Cli_Destroy(&recvClient_); - Cli_Destroy(&sendClient_); - } + recvClient_ = Cli_Create(); + sendClient_ = Cli_Create(); - bool SNAP7Connection::open(PLC* thePLC) - { - int err = 0; - bool ret = false; - int slotNb; + LOG(ALLOC) << "SNAP7Connection (create) PLC/Cluster: " << thePLC->getName() << "/" << thePLC->theCluster_->getClassName(); +} - //Slot number is not required, so try to connect on different slots (1..31), depending on hardware layout. - int *slotsScan; - int slotsS7400[4] = { 3,2,4,1 }; //slot scan - most common layout S7-400 - int slotsS71x00[4] = { 1,2,3,4 }; //slot scan - most common layout S7-1200, S7-1500 - int slotsDefault[4] = { 2,3,4,1 }; //slot scan - most common layout S7-other (S7-300, ET200S) - int nbMaxSlot = ((slotNb_ == -1) ? 4 : 1); //slots scan only the first time (slotNb_ == -1) +SNAP7Connection::~SNAP7Connection() +{ + Cli_Destroy(&recvClient_); + Cli_Destroy(&sendClient_); +} +bool SNAP7Connection::open(PLC* thePLC) +{ + int err = 0; + bool ret = false; + int slotNb; + + //Slot number is not required, so try to connect on different slots (1..31), depending on hardware layout. + int *slotsScan; + int slotsS7400[4] = {3, 2, 4, 1}; //slot scan - most common layout S7-400 + int slotsS71x00[4] = {1, 2, 3, 4}; //slot scan - most common layout S7-1200, S7-1500 + int slotsDefault[4] = {2, 3, 4, 1}; //slot scan - most common layout S7-other (S7-300, ET200S) + int nbMaxSlot = ( (slotNb_ == -1) ? 4 : 1); //slots scan only the first time (slotNb_ == -1) + + switch (thePLC->getModelID()) + { + case S7400: + slotsScan = slotsS7400; + break; + case S71200: + case S71500: + slotsScan = slotsS71x00; + break; + default: + slotsScan = slotsDefault; //S7-300, ET200S + } + for (int i = 0; i < nbMaxSlot; i++) + { + slotNb = slotsScan[i]; - switch (thePLC->getModelID()) - { - case S7400: - slotsScan = slotsS7400; break; - case S71200: - case S71500: - slotsScan = slotsS71x00; break; - default: - slotsScan = slotsDefault; //S7-300, ET200S - } + err = ( (slotNb_ == -1) ? Cli_ConnectTo(recvClient_, thePLC->getIPAddress().c_str(), rackNb_, slotNb) : Cli_Connect(recvClient_)); - for(int i=0; i<nbMaxSlot; i++) + if (err == 0) { - slotNb = slotsScan[i]; + LOG(DEBUG) << "SNAP7 connect (channel-1) successful on PLC/rack/slot: " << thePLC->getName() << "/" << rackNb_ << "/" << slotNb; - err = ((slotNb_ == -1) ? Cli_ConnectTo(recvClient_, thePLC->getIPAddress().c_str(), rackNb_, slotNb) : - Cli_Connect(recvClient_)); + //We managed to open the first channel, just open the second one on the same slot. + err = ( (slotNb_ == -1) ? Cli_ConnectTo(sendClient_, thePLC->getIPAddress().c_str(), rackNb_, slotNb) : Cli_Connect(sendClient_)); - if (err == 0) + if (err != 0) { - LOG(DEBUG) << "SNAP7 connect (channel-1) successful on PLC/rack/slot: " << thePLC->getName() << "/" << rackNb_ << "/" << slotNb; - - //We managed to open the first channel, just open the second one on the same slot. - err = ((slotNb_ == -1) ? Cli_ConnectTo(sendClient_, thePLC->getIPAddress().c_str(), rackNb_, slotNb) : - Cli_Connect(sendClient_)); - - if (err != 0) - { - LOG(DEBUG) << "SNAP7 connection (channel-2) failed on PLC/rack/slot: " << thePLC->getName() << "/" << rackNb_ << "/" << slotNb <<". SNAP7[" << err << "]: " << getSNAP7ErrorMessage(err); + LOG(DEBUG) << "SNAP7 connection (channel-2) failed on PLC/rack/slot: " << thePLC->getName() << "/" << rackNb_ << "/" << slotNb << ". SNAP7[" << err << "]: " << getErrorMessage(err); continue; - } + } - LOG(DEBUG) << "SNAP7 connect (channel-2) successful on PLC/rack/slot: " << thePLC->getName() << "/" << rackNb_ << "/" << slotNb; + LOG(DEBUG) << "SNAP7 connect (channel-2) successful on PLC/rack/slot: " << thePLC->getName() << "/" << rackNb_ << "/" << slotNb; - slotNb_ = slotNb; //connection is ok we can store the valid slot number for the next (re)connection (will be faster). - ret = true; - break; - } - LOG(DEBUG) << "SNAP7 connection (channel-1) failed on PLC/rack/slot: " << thePLC->getName() << "/" << rackNb_ << "/" << slotNb <<". SNAP7[" << err << "]: " << getSNAP7ErrorMessage(err); + slotNb_ = slotNb; //connection is ok we can store the valid slot number for the next (re)connection (will be faster). + ret = true; + break; } - return (ret); - } + LOG(DEBUG) << "SNAP7 connection (channel-1) failed on PLC/rack/slot: " << thePLC->getName() << "/" << rackNb_ << "/" << slotNb << ". SNAP7[" << err << "]: " << getErrorMessage(err); + } + return (ret); +} + +bool SNAP7Connection::close(PLC* thePLC) +{ + bool ret = (Cli_Disconnect(recvClient_) == 0) && (Cli_Disconnect(sendClient_) == 0); + return ret; +} + +std::string SNAP7Connection::getErrorMessage(int err) +{ + char text[TextLen]; + Cli_ErrorText(err, text, TextLen); + return std::string(text); +} +//------------------------------------------------------------------------------------------------------------------- +int SNAP7Connection::readUnitCode(PLC* thePLC, UnitCodeType& dataStruct) +{ + int err; + TS7OrderCode S7data; - bool SNAP7Connection::close(PLC* thePLC) - { - bool ret = (Cli_Disconnect(recvClient_) == 0) && - (Cli_Disconnect(sendClient_) == 0); - return ret; - } + if ( (err = Cli_GetOrderCode(recvClient_, &S7data)) == 0) + { + std::ostringstream os; + os << S7data.Code; + dataStruct.code = os.str(); + os.str().clear(); + os << (int)S7data.V1 << "." << (int)S7data.V2 << "." << (int)S7data.V3; + dataStruct.version = os.str(); + LOG(DEBUG) << "Unit order-code received: " << dataStruct.code << ", " << dataStruct.version; + return 0; + } + checkError(thePLC, err, false); + return err; +} +int SNAP7Connection::readUnitStatus(PLC* thePLC, UnitStatusType& dataStruct) +{ + int err; + int status; - std::string SNAP7Connection::getSNAP7ErrorMessage(int err) + if ( (err = Cli_GetPlcStatus(recvClient_, &status)) == 0) { - char text[100]; - Cli_ErrorText(err, text, 100); - return std::string(text); + dataStruct.status = status; + LOG(DEBUG) << "Unit status received: " << (int)dataStruct.status; + return 0; } + checkError(thePLC, err, false); + return err; +} +int SNAP7Connection::readCPUInfo(PLC* thePLC, CPUInfoType& dataStruct) +{ + int err; + TS7CpuInfo S7data; - //------------------------------------------------------------------------------------------------------------------- - int SNAP7Connection::readUnitCode(PLC* thePLC, UnitCodeType& dataStruct) + if ( (err = Cli_GetCpuInfo(recvClient_, &S7data)) == 0) { - TS7OrderCode S7data; - int error = Cli_GetOrderCode(recvClient_, &S7data); - if(error) - throw SilecsException(__FILE__, __LINE__, UNEXPECTED_ERROR," SNAP7 Error: " + getSNAP7ErrorMessage(error)); - std::ostringstream os; - os << S7data.Code; - dataStruct.code = os.str(); - os.str().clear(); - os << (int)S7data.V1 << "." << (int)S7data.V2 << "." << (int)S7data.V3; - dataStruct.version = os.str(); - LOG(DEBUG) << "Unit order-code received: " << dataStruct.code << ", " << dataStruct.version; - return 0; + dataStruct.moduleName = std::string(S7data.ModuleName); + dataStruct.moduleTypeName = std::string(S7data.ModuleTypeName); + dataStruct.serialNumber = std::string(S7data.SerialNumber); + dataStruct.asName = std::string(S7data.ASName); + dataStruct.copyright = std::string(S7data.Copyright); + LOG(DEBUG) << "CPU info received: " << dataStruct.moduleName << ", " << dataStruct.moduleTypeName << ", " << dataStruct.serialNumber << ", " << dataStruct.asName << ", " << dataStruct.copyright; + return 0; } + checkError(thePLC, err, false); + return err; +} +int SNAP7Connection::readCPInfo(PLC* thePLC, CPInfoType& dataStruct) +{ + int err; + TS7CpInfo S7data; - int SNAP7Connection::readUnitStatus(PLC* thePLC, UnitStatusType& dataStruct) + if ( (err = Cli_GetCpInfo(recvClient_, &S7data)) == 0) { - int status; - int error = Cli_GetPlcStatus(recvClient_, &status); - if(error) - throw SilecsException(__FILE__, __LINE__, UNEXPECTED_ERROR," SNAP7 Error: " + getSNAP7ErrorMessage(error)); - dataStruct.status = status; - LOG(DEBUG) << "Unit status received: " << (int)dataStruct.status; - return 0; + dataStruct.maxPduLength = S7data.MaxPduLengt; + dataStruct.maxConnections = S7data.MaxConnections; + dataStruct.maxMPIRate = S7data.MaxMpiRate; + dataStruct.maxBusRate = S7data.MaxBusRate; + LOG(DEBUG) << "CP info received: " << dataStruct.maxPduLength << ", " << dataStruct.maxConnections << ", " << dataStruct.maxMPIRate << ", " << dataStruct.maxBusRate; + return 0; } + checkError(thePLC, err, false); + return err; +} - int SNAP7Connection::readCPUInfo(PLC* thePLC, CPUInfoType& dataStruct) +bool SNAP7Connection::isRunning(PLC* thePLC) +{ + UnitStatusType statusStruct; + this->readUnitStatus(thePLC, statusStruct); + switch (statusStruct.status) { - TS7CpuInfo S7data; - int error = Cli_GetCpuInfo(recvClient_, &S7data); - if(error) - throw SilecsException(__FILE__, __LINE__, UNEXPECTED_ERROR," SNAP7 Error: " + getSNAP7ErrorMessage(error)); - dataStruct.moduleName = std::string(S7data.ModuleName); - dataStruct.moduleTypeName = std::string(S7data.ModuleTypeName); - dataStruct.serialNumber = std::string(S7data.SerialNumber); - dataStruct.asName = std::string(S7data.ASName); - dataStruct.copyright = std::string(S7data.Copyright); - LOG(DEBUG) << "CPU info received: " << dataStruct.moduleName << ", " << dataStruct.moduleTypeName << ", " - << dataStruct.serialNumber << ", " << dataStruct.asName << ", " << dataStruct.copyright; - return 0; + case S7CpuStatusRun: + return true; + case S7CpuStatusStop: + return false; + default: + throw SilecsException(__FILE__, __LINE__, UNKNOWN_ERROR, std::string("PLC Status is: UNKNOWN")); } +} - int SNAP7Connection::readCPInfo(PLC* thePLC, CPInfoType& dataStruct) +//------------------------------------------------------------------------------------------------------------------- +int SNAP7Connection::readData(PLC* thePLC, unsigned long DBn, unsigned long offset, unsigned long size, unsigned char* pBuffer) +{ + int err = 0; + //(re)connect the PLC if needed and (re)synchronize the retentive registers + if (doOpen(thePLC)) { - TS7CpInfo S7data; - int error = Cli_GetCpInfo(recvClient_, &S7data); - if(error) - throw SilecsException(__FILE__, __LINE__, UNEXPECTED_ERROR," SNAP7 Error: " + getSNAP7ErrorMessage(error)); - dataStruct.maxPduLength = S7data.MaxPduLengt; - dataStruct.maxConnections = S7data.MaxConnections; - dataStruct.maxMPIRate = S7data.MaxMpiRate; - dataStruct.maxBusRate = S7data.MaxBusRate; - LOG(DEBUG) << "CP info received: " << dataStruct.maxPduLength << ", " << dataStruct.maxConnections << ", " - << dataStruct.maxMPIRate << ", " << dataStruct.maxBusRate; - return 0; + //connection is established then acquire data + Lock lock(readMux_); + + //DATA topic makes sense with RECV one + if (RECV & Log::topics_) + LOG(DATA) << "Read data, DBn: " << DBn << ", ofs: " << offset << ", byte-size: " << size; + + err = Cli_DBRead(recvClient_, (int)DBn, (int)offset, (int)size, (void *)pBuffer); + checkError(thePLC, err, false); // close the connection, will try again at the next access } + return err; +} - bool SNAP7Connection::isRunning(PLC* thePLC) +int SNAP7Connection::writeData(PLC* thePLC, unsigned long DBn, unsigned long offset, unsigned long size, unsigned char* pBuffer) +{ + int err = 0; + //(re)connect the PLC if needed and (re)synchronize the retentive registers + if (doOpen(thePLC)) { - UnitStatusType statusStruct; - this->readUnitStatus(thePLC, statusStruct); - switch (statusStruct.status) - { - case S7CpuStatusRun : return true; - case S7CpuStatusStop: return false; - default : throw SilecsException(__FILE__, __LINE__, UNKNOWN_ERROR,std::string("PLC Status is: UNKNOWN")); - } + //connection is established then send data + Lock lock(writeMux_); + + //DATA topic makes sense with SEND one + if (SEND & Log::topics_) + LOG(DATA) << "Write data, DBn: " << DBn << ", ofs: " << offset << ", byte-size: " << size; + + err = Cli_DBWrite(sendClient_, (int)DBn, (int)offset, (int)size, (void *)pBuffer); + checkError(thePLC, err, false); // close the connection, will try again at the next access } + return err; +} + +//------------------------------------------------------------------------------------------------------------------- +bool SNAP7Connection::checkError(PLC* thePLC, int err, bool retry) +{ + if (err != 0) + { + LOG(COMM) << "Transaction failure with PLC: " << thePLC->getName() << getErrorMessage(err); - //------------------------------------------------------------------------------------------------------------------- - int SNAP7Connection::readData(PLC* thePLC, unsigned long DBn, unsigned long offset, unsigned long size, unsigned char* pBuffer) - { - //(re)connect the PLC if needed and (re)synchronize the retentive registers - if (doOpen(thePLC)) + if (retry) { - //connection is established then acquire data - Lock lock(readMux_); + LOG(COMM) << "Try to reconnect the PLC: " << thePLC->getName(); + if (reOpen(thePLC)) + { // can repeat the request after the connection was successfully reopened + return true; + } + // reconnection has failed again, just close the connection + LOG(COMM) << "Unable to reconnect the PLC: " << thePLC->getName(); + } + // no retry, we just want to close (use default case) + doClose(thePLC, /*withLock =*/true); + } + return false; +} - //DATA topic makes sense with RECV one - if (RECV & Log::topics_) LOG(DATA) << "Read data, DBn: " << DBn << ", ofs: " << offset << ", byte-size: " << size; +int SNAP7Connection::coldRestart(PLC* thePLC) +{ + if (doOpen(thePLC)) + { + Lock lock(writeMux_); + int error = Cli_PlcColdStart(sendClient_); + if (error) + throw SilecsException(__FILE__, __LINE__, UNEXPECTED_ERROR, " SNAP7 Error: " + getErrorMessage(error)); - int error = Cli_DBRead(recvClient_, (int)DBn, (int)offset, (int)size, (void *)pBuffer); - if(error) - throw SilecsException(__FILE__, __LINE__, UNEXPECTED_ERROR," SNAP7 Error: " + getSNAP7ErrorMessage(error)); - } - return 0; - } - - int SNAP7Connection::writeData(PLC* thePLC, unsigned long DBn, unsigned long offset, unsigned long size, unsigned char* pBuffer) - { - //(re)connect the PLC if needed and (re)synchronize the retentive registers - if (doOpen(thePLC)) - { - //connection is established then send data - Lock lock(writeMux_); - - //DATA topic makes sense with SEND one - if (SEND & Log::topics_) LOG(DATA) << "Write data, DBn: " << DBn << ", ofs: " << offset << ", byte-size: " << size; - - int error = Cli_DBWrite(sendClient_, (int)DBn, (int)offset, (int)size, (void *)pBuffer); - if(error) - throw SilecsException(__FILE__, __LINE__, UNEXPECTED_ERROR," SNAP7 Error: " + getSNAP7ErrorMessage(error)); - } - return 0; - } - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// - //Puts the CPU in RUN mode performing a COLD START. - int SNAP7Connection::coldRestart(PLC* thePLC) - { - if(doOpen(thePLC)) - { - Lock lock(writeMux_); - int error = Cli_PlcColdStart(sendClient_); - if(error) - throw SilecsException(__FILE__, __LINE__, UNEXPECTED_ERROR," SNAP7 Error: " + getSNAP7ErrorMessage(error)); - - } - return 0; - } - - //Puts the CPU in STOP mode. - int SNAP7Connection::plcStop(PLC* thePLC) - { - if(doOpen(thePLC)) - { - Lock lock(writeMux_); - int error = Cli_PlcStop(sendClient_); - if(error) - throw SilecsException(__FILE__, __LINE__, UNEXPECTED_ERROR," SNAP7 Error: " + getSNAP7ErrorMessage(error)); - - } - return 0; - } -// -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + } + return 0; +} + +int SNAP7Connection::plcStop(PLC* thePLC) +{ + if (doOpen(thePLC)) + { + Lock lock(writeMux_); + int error = Cli_PlcStop(sendClient_); + if (error) + throw SilecsException(__FILE__, __LINE__, UNEXPECTED_ERROR, " SNAP7 Error: " + getErrorMessage(error)); + } + return 0; +} -} // namespace +}// namespace diff --git a/silecs-communication-cpp/src/silecs-communication/interface/communication/SNAP7Connection.h b/silecs-communication-cpp/src/silecs-communication/interface/communication/SNAP7Connection.h index 6bb7ee1..ea2b9ce 100644 --- a/silecs-communication-cpp/src/silecs-communication/interface/communication/SNAP7Connection.h +++ b/silecs-communication-cpp/src/silecs-communication/interface/communication/SNAP7Connection.h @@ -57,7 +57,8 @@ namespace Silecs int rackNb_; //rackNb - common rack is 0 by default int slotNb_; //slotNb - depends on hardware configuration (scan is required the first connect) - std::string getSNAP7ErrorMessage(int err); + std::string getErrorMessage(int err); + bool checkError(PLC* thePLC, int err, bool retry); }; } // namespace diff --git a/silecs-communication-cpp/src/silecs-communication/interface/communication/SNAP7Hardware.cpp b/silecs-communication-cpp/src/silecs-communication/interface/communication/SNAP7Hardware.cpp new file mode 100644 index 0000000..a19ef7d --- /dev/null +++ b/silecs-communication-cpp/src/silecs-communication/interface/communication/SNAP7Hardware.cpp @@ -0,0 +1,75 @@ +// Copyright 2016 CERN and GSI +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "SNAP7Hardware.h" + +/*----------------------------------------------------------*/ +/* Time funtion + * IeRfcSetTime: Convert lynxos time_t format to PLC _DT format + * (SIMATIC). + * + * Details: + * PLC DATE_AND_TIME (or DT) format is coded on 8 byte in BCD + * format: + * 0 1 2 3 4 5 6 7 + * YY MM DD HH MN SC SC/100 SC/10e4 + */ +void IeRfcSetTime(unsigned char *dt, time_t epoch) // to PLC +{ + struct tm *dscst; + dscst = localtime(&epoch); + + dt[7] = 0; // not used at the moment + dt[6] = 0; // not used at the moment + dt[5] = _tobcd(dscst->tm_sec); + dt[4] = _tobcd(dscst->tm_min); + dt[3] = _tobcd(dscst->tm_hour); + dt[2] = _tobcd(dscst->tm_mday); + dt[1] = _tobcd(dscst->tm_mon+1); + dt[0] = _tobcd(dscst->tm_year-100); +} + +/*----------------------------------------------------------*/ +/* Time funtion + * IeRfcGetTime: Convert PLC _DT format to time_t format (SIMATIC). + * + * Details: + * PLC DATE_AND_TIME (or DT) format is coded on 8 byte in BCD + * format: + * 0 1 2 3 4 5 6 7 + * YY MM DD HH MN SC SC/100 SC/10e4 + */ +double IeRfcGetTime(unsigned char *dt) // from PLC +{ + struct tm plcst; + time_t plctm; + double ms; + int year; + + plcst.tm_sec = _frombcd(dt[5]); + plcst.tm_min = _frombcd(dt[4]); + plcst.tm_hour = _frombcd(dt[3]); + plcst.tm_mday = _frombcd(dt[2]); + plcst.tm_mon = _frombcd(dt[1])-1; + year = _frombcd(dt[0]); + //look at Siemens DATA_AND_TIME type documentation + plcst.tm_year = ((year >= 90) ? year : year+100); + + ms = ((double)_frombcd(dt[6])/100.)+((double)_frombcd(dt[7])/10000.); + plcst.tm_isdst = -1; // daylight saving time unavailable + plctm = mktime(&plcst); + + return((double)plctm + ms); +} diff --git a/silecs-communication-cpp/src/silecs-communication/interface/communication/SNAP7Hardware.h b/silecs-communication-cpp/src/silecs-communication/interface/communication/SNAP7Hardware.h new file mode 100644 index 0000000..87336b6 --- /dev/null +++ b/silecs-communication-cpp/src/silecs-communication/interface/communication/SNAP7Hardware.h @@ -0,0 +1,47 @@ +// Copyright 2016 CERN and GSI +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +#ifndef _SNAP7HARDWARE_H_ +#define _SNAP7HARDWARE_H_ + +#include <silecs-communication/protocol/core/ietype.h> + +/*----------------------------------------------------------*/ +/* Time funtion + * IeRfcSetTime: Convert time_t epoch date to PLC _DT format + * (SIMATIC). + * + * Details: + * PLC DATE_AND_TIME (or DT) format is coded on 8 byte in BCD + * format: + * 0 1 2 3 4 5 6 7 + * YY MM DD HH MN SC SC/100 SC/10e4 + */ +void IeRfcSetTime(unsigned char *dt, time_t epoch); // to PLC + +/*----------------------------------------------------------*/ +/* Time funtion + * IeRfcGetTime: Convert PLC _DT format to lynxOS time_t format + * (SIMATIC). + * + * Details: + * PLC DATE_AND_TIME (or DT) format is coded on 8 byte in BCD + * format: + * 0 1 2 3 4 5 6 7 + * YY MM DD HH MN SC SC/100 SC/10e4 + */ +double IeRfcGetTime(unsigned char *dt); // from PLC + + +#endif /* _SNAP7HARDWARE_H_ */ diff --git a/silecs-communication-cpp/src/silecs-communication/interface/communication/SilecsConnection.cpp b/silecs-communication-cpp/src/silecs-communication/interface/communication/SilecsConnection.cpp index b072532..7c4eaab 100644 --- a/silecs-communication-cpp/src/silecs-communication/interface/communication/SilecsConnection.cpp +++ b/silecs-communication-cpp/src/silecs-communication/interface/communication/SilecsConnection.cpp @@ -13,6 +13,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. +#include <fcntl.h> +#include <signal.h> + #include <silecs-communication/interface/equipment/SilecsCluster.h> #include <silecs-communication/interface/utility/SilecsLog.h> #include <silecs-communication/interface/communication/SilecsConnection.h> @@ -113,7 +116,7 @@ namespace Silecs if (isTimeToReconnect()) { - if (isReachable = (IeRfcPing((char *) thePLC->getName().c_str(), NULL) == 0)) + if (isReachable = (ping((char *) thePLC->getName().c_str(), NULL) == 0)) { LOG((COMM|DIAG)) << "It's time to reconnect"; @@ -235,7 +238,7 @@ namespace Silecs //------------------------------------------------------------------------------------------------------------------------------------------------ bool Connection::reOpen(PLC* thePLC) { - if (IeRfcPing((char *) thePLC->getName().c_str(), 0) == 0) + if (ping((char *) thePLC->getName().c_str(), 0) == 0) { doClose(thePLC, /*withLock =*/true); return doOpen(thePLC); } @@ -404,5 +407,166 @@ namespace Silecs } return toBeReconnected; } + /* This macro is used to trap the unexpected broken pipe and + return an error instead of exit process. + */ + static __sighandler_t sigpipeHandler = (__sighandler_t)-1; + #define _DISABLE_SIGPIPE sigpipeHandler = signal(SIGPIPE, SIG_IGN) + #define _ENABLE_SIGPIPE signal(SIGPIPE, sigpipeHandler) + + /*----------------------------------------------------------*/ + /* This is a function used to connect an host on a port. + * 'ip' is the ip address string + * 'port' is the port number (102 for rfc1006 server) + * 'dst' is the TSAP destination string (exp: "TCP-1") + * 'src' is the TSAP source string (exp: "TCP-1") + * 'ts' timeout in second (1s minimum) + * return a socket descriptor or <0 on error + * (see constant error) + * + * Details: + * intermediate connect_nonb() function is used to perform a non-blockant connection. + * intermediate rfcPing() function is used to check if PLC is ON + */ + int connect_nonb(int sockfd, struct sockaddr *saptr, socklen_t salen, int nsec) + { + int flags, n, error; + socklen_t len; + fd_set rset, wset; + struct timeval tval; + + flags = fcntl(sockfd, F_GETFL, 0); + fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); + + error = 0; + /*v1.1*/ + _DISABLE_SIGPIPE; + n = connect(sockfd, (struct sockaddr *) saptr, salen); + _ENABLE_SIGPIPE; + if (n < 0){ + if (errno != EINPROGRESS) + return(-1); + } + + /* Do whatever we want while the connect is taking place. */ + + if (n == 0) + goto done; /* connect completed immediately */ + + FD_ZERO(&rset); + FD_SET(sockfd, &rset); + wset = rset; + tval.tv_sec = nsec; + tval.tv_usec = 0; + + if ( (n = select(sockfd+1, &rset, &wset, NULL, + nsec ? &tval : NULL)) == 0) { + close(sockfd); /* timeout */ + errno = ETIMEDOUT; + return(-1); + } + + if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) { + len = sizeof(error); + if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { + return(-1); /* Solaris pending error */ + } + } + + done: + fcntl(sockfd, F_SETFL, flags); /* restore file status flags */ + + if (error) { + /*close(sockfd); just in case */ + errno = error; + return(-1); + } + + return(0); + } + + /*----------------------------------------------------------*/ + int rfcPing(char *ip, long ts) + { + int err, s, val = 1; + struct protoent *pent; + struct sockaddr_in rsock; + + /* Socket create/connect */ + memset ((char *)&rsock,0,sizeof(rsock)); + + if ((s=socket(AF_INET,SOCK_STREAM,0)) == -1) + { + #ifdef COMMENT + printf("#Error - Can't create socket: %s\n", strerror(errno)); + #endif + return RFC_SYS_ERROR; + } + + /* Set TCP_NODELAY option to force immediate TCP/IP acknowledgement */ + if ((pent=getprotobyname("TCP")) == NULL) + { + #ifdef COMMENT + printf("#Error - Can't configure socket: %s\n", strerror(errno)); + #endif + return RFC_SYS_ERROR; + } + if (setsockopt(s,pent->p_proto,TCP_NODELAY,&val,4) == -1) + { + #ifdef COMMENT + printf("#Error - Can't configure socket: %s\n", strerror(errno)); + #endif + return RFC_SYS_ERROR; + } + + #ifdef COMMENT + printf("#Comment - Socket created: %i\n", s); + #endif + + rsock.sin_addr.s_addr=inet_addr(ip); + rsock.sin_family=AF_INET; + /*check any port to detect if the host is OFF*/ + rsock.sin_port=htons(102); + + err = 0; + if (connect_nonb(s,(struct sockaddr *)(&rsock),sizeof(rsock), ts) == -1) + { + /*if hostname is OFF, connect() fails on TIMEOUT*/ + if ((errno == ETIMEDOUT) || (errno == EHOSTDOWN) || (errno == EHOSTUNREACH)) { + err = RFC_TIMEOUT_ERROR; + } + } + + close(s); + return err; + } + + /*..........................................................*/ + int ping(char *hostName, char *plcIP) + { + struct in_addr addr; + struct hostent *hp; + char *ipstr = plcIP; + + if ((hostName == NULL) && (plcIP == NULL)) + return(IE_PARAM_ERROR); + + // use hostName reference in priority else plcIP + if (hostName) + { + hp = gethostbyname(hostName); + if (hp) + { + addr.s_addr = *((unsigned long int *) hp->h_addr); + ipstr = inet_ntoa(addr); + } + } + + if (ipstr == NULL) + return(IE_HOST_UNKNOWN_ERROR); + + /*trying to connect PLC (1 second max)*/ + return (rfcPing(ipstr, 1)); + } } // end namespace diff --git a/silecs-communication-cpp/src/silecs-communication/interface/communication/SilecsConnection.h b/silecs-communication-cpp/src/silecs-communication/interface/communication/SilecsConnection.h index 7d350c7..c87cf3a 100644 --- a/silecs-communication-cpp/src/silecs-communication/interface/communication/SilecsConnection.h +++ b/silecs-communication-cpp/src/silecs-communication/interface/communication/SilecsConnection.h @@ -160,6 +160,11 @@ namespace Silecs unsigned long remainConn_; //number of attempt before next slow-down static const unsigned int numberConn_; //number of connection attempt for each connection delay + /* ping function is a function used to check + * if PLC is ON (~ping) before trying to connect it. + */ + static int ping(char *hostName, char *plcIP); + // Communication Diagnostic & Monitoring static bool isAlive_; // PLC has repliyed to the ping: true/false bool isConnected_; // State of this particular connection: FEC/PLC/Class diff --git a/silecs-communication-cpp/src/silecs-communication/interface/equipment/PLCRegister.cpp b/silecs-communication-cpp/src/silecs-communication/interface/equipment/PLCRegister.cpp index 69a32c5..2c06112 100644 --- a/silecs-communication-cpp/src/silecs-communication/interface/equipment/PLCRegister.cpp +++ b/silecs-communication-cpp/src/silecs-communication/interface/equipment/PLCRegister.cpp @@ -16,6 +16,8 @@ #include "PLCRegister.h" #include <silecs-communication/protocol/core/silecs.h> #include <silecs-communication/protocol/modbus/iemdb.h> +#include <silecs-communication/interface/communication/MBHardware.h> +#include <silecs-communication/interface/communication/SNAP7Hardware.h> namespace Silecs { diff --git a/silecs-communication-cpp/src/silecs-communication/protocol/core/silecs.cpp b/silecs-communication-cpp/src/silecs-communication/protocol/core/silecs.cpp index bf98923..ca0bbab 100644 --- a/silecs-communication-cpp/src/silecs-communication/protocol/core/silecs.cpp +++ b/silecs-communication-cpp/src/silecs-communication/protocol/core/silecs.cpp @@ -43,227 +43,6 @@ /* MACRO DEFINITION */ /* ---------------------------------------------------------*/ -/* This macro is used to trap the unexpected broken pipe and - return an error instead of exit process. -*/ -static __sighandler_t sigpipeHandler = (__sighandler_t)-1; -#define _DISABLE_SIGPIPE sigpipeHandler = signal(SIGPIPE, SIG_IGN) -#define _ENABLE_SIGPIPE signal(SIGPIPE, sigpipeHandler) - -/*----------------------------------------------------------*/ -/* Time funtion - * IeRfcSetTime: Convert lynxos time_t format to PLC _DT format - * (SIMATIC). - * - * Details: - * PLC DATE_AND_TIME (or DT) format is coded on 8 byte in BCD - * format: - * 0 1 2 3 4 5 6 7 - * YY MM DD HH MN SC SC/100 SC/10e4 - */ -void IeRfcSetTime(unsigned char *dt, time_t epoch) // to PLC -{ - struct tm *dscst; - dscst = localtime(&epoch); - - dt[7] = 0; // not used at the moment - dt[6] = 0; // not used at the moment - dt[5] = _tobcd(dscst->tm_sec); - dt[4] = _tobcd(dscst->tm_min); - dt[3] = _tobcd(dscst->tm_hour); - dt[2] = _tobcd(dscst->tm_mday); - dt[1] = _tobcd(dscst->tm_mon+1); - dt[0] = _tobcd(dscst->tm_year-100); -} - -/*----------------------------------------------------------*/ -/* Time funtion - * IeRfcGetTime: Convert PLC _DT format to time_t format (SIMATIC). - * - * Details: - * PLC DATE_AND_TIME (or DT) format is coded on 8 byte in BCD - * format: - * 0 1 2 3 4 5 6 7 - * YY MM DD HH MN SC SC/100 SC/10e4 - */ -double IeRfcGetTime(unsigned char *dt) // from PLC -{ - struct tm plcst; - time_t plctm; - double ms; - int year; - - plcst.tm_sec = _frombcd(dt[5]); - plcst.tm_min = _frombcd(dt[4]); - plcst.tm_hour = _frombcd(dt[3]); - plcst.tm_mday = _frombcd(dt[2]); - plcst.tm_mon = _frombcd(dt[1])-1; - year = _frombcd(dt[0]); - //look at Siemens DATA_AND_TIME type documentation - plcst.tm_year = ((year >= 90) ? year : year+100); - - ms = ((double)_frombcd(dt[6])/100.)+((double)_frombcd(dt[7])/10000.); - plcst.tm_isdst = -1; // daylight saving time unavailable - plctm = mktime(&plcst); - - return((double)plctm + ms); -} - -/*----------------------------------------------------------*/ -/* This is a function used to connect an host on a port. - * 'ip' is the ip address string - * 'port' is the port number (102 for rfc1006 server) - * 'dst' is the TSAP destination string (exp: "TCP-1") - * 'src' is the TSAP source string (exp: "TCP-1") - * 'ts' timeout in second (1s minimum) - * return a socket descriptor or <0 on error - * (see constant error) - * - * Details: - * intermediate connect_nonb() function is used to perform a non-blockant connection. - * intermediate rfcPing() function is used to check if PLC is ON - */ -int connect_nonb(int sockfd, struct sockaddr *saptr, socklen_t salen, int nsec) -{ - int flags, n, error; - socklen_t len; - fd_set rset, wset; - struct timeval tval; - - flags = fcntl(sockfd, F_GETFL, 0); - fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); - - error = 0; - /*v1.1*/ - _DISABLE_SIGPIPE; - n = connect(sockfd, (struct sockaddr *) saptr, salen); - _ENABLE_SIGPIPE; - if (n < 0){ - if (errno != EINPROGRESS) - return(-1); - } - - /* Do whatever we want while the connect is taking place. */ - - if (n == 0) - goto done; /* connect completed immediately */ - - FD_ZERO(&rset); - FD_SET(sockfd, &rset); - wset = rset; - tval.tv_sec = nsec; - tval.tv_usec = 0; - - if ( (n = select(sockfd+1, &rset, &wset, NULL, - nsec ? &tval : NULL)) == 0) { - close(sockfd); /* timeout */ - errno = ETIMEDOUT; - return(-1); - } - - if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) { - len = sizeof(error); - if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { - return(-1); /* Solaris pending error */ - } - } - -done: - fcntl(sockfd, F_SETFL, flags); /* restore file status flags */ - - if (error) { - /*close(sockfd); just in case */ - errno = error; - return(-1); - } - - return(0); -} - -/*----------------------------------------------------------*/ -int rfcPing(char *ip, long ts) -{ - int err, s, val = 1; - struct protoent *pent; - struct sockaddr_in rsock; - - /* Socket create/connect */ - memset ((char *)&rsock,0,sizeof(rsock)); - - if ((s=socket(AF_INET,SOCK_STREAM,0)) == -1) - { - #ifdef COMMENT - printf("#Error - Can't create socket: %s\n", strerror(errno)); - #endif - return RFC_SYS_ERROR; - } - - /* Set TCP_NODELAY option to force immediate TCP/IP acknowledgement */ - if ((pent=getprotobyname("TCP")) == NULL) - { - #ifdef COMMENT - printf("#Error - Can't configure socket: %s\n", strerror(errno)); - #endif - return RFC_SYS_ERROR; - } - if (setsockopt(s,pent->p_proto,TCP_NODELAY,&val,4) == -1) - { - #ifdef COMMENT - printf("#Error - Can't configure socket: %s\n", strerror(errno)); - #endif - return RFC_SYS_ERROR; - } - - #ifdef COMMENT - printf("#Comment - Socket created: %i\n", s); - #endif - - rsock.sin_addr.s_addr=inet_addr(ip); - rsock.sin_family=AF_INET; - /*check any port to detect if the host is OFF*/ - rsock.sin_port=htons(102); - - err = 0; - if (connect_nonb(s,(struct sockaddr *)(&rsock),sizeof(rsock), ts) == -1) - { - /*if hostname is OFF, connect() fails on TIMEOUT*/ - if ((errno == ETIMEDOUT) || (errno == EHOSTDOWN) || (errno == EHOSTUNREACH)) { - err = RFC_TIMEOUT_ERROR; - } - } - - close(s); - return err; -} - -/*..........................................................*/ -int IeRfcPing (char *hostName, char *plcIP) -{ - struct in_addr addr; - struct hostent *hp; - char *ipstr = plcIP; - - if ((hostName == NULL) && (plcIP == NULL)) - return(IE_PARAM_ERROR); - - // use hostName reference in priority else plcIP - if (hostName) - { - hp = gethostbyname(hostName); - if (hp) - { - addr.s_addr = *((unsigned long int *) hp->h_addr); - ipstr = inet_ntoa(addr); - } - } - - if (ipstr == NULL) - return(IE_HOST_UNKNOWN_ERROR); - - /*trying to connect PLC (1 second max)*/ - return (rfcPing(ipstr, 1)); -} - /*----------------------------------------------------------*/ /* This function return the string which describes the * error given by 'error' parameter. @@ -278,32 +57,16 @@ char *IeGetErrorMessage(int err) static struct { - int ie_code; - char * ie_mes; + int ie_code; + char * ie_mes; } errorMap[] = { - {IE_SYS_ERROR, "[RFC lib.] function system error"}, - {IE_PARAM_ERROR, "[IE/RFC lib.] bad parameter(s) value/format"}, - {IE_CONNECT_ERROR, "[RFC lib.] PLC connection failed"}, - {IE_DISCONNECT_ERROR, "[RFC lib.] PLC disconnection requested"}, - {IE_TIMEOUT_ERROR, "[RFC lib.] communication (connect/send/recv) timeout occurred"}, - {IE_EPIPE_ERROR, "[RFC lib.] PLC communication broken"}, - {IE_FRAME_ERROR, "[RFC lib.] PLC data frame error"}, - {IE_DEFER_CMD_BUFFER_FULL, "[IE lib.] deferred commands buffer overflow"}, - {IE_DEFER_REP_BUFFER_EMPTY, "[IE lib.] deferred replies empty"}, - {IE_BLOCK_SIZE_ERROR, "[IE lib.] block size exceed PLC memory allocation"}, - {IE_NB_MONITORING_ERROR, "[IE lib.] monitoring descriptor out of limit"}, - {IE_HOST_PING_ERROR, "[IE lib.] PLC does not reply to ping"}, - {IE_HOST_UNKNOWN_ERROR, "[IE lib.] PLC hostname unknown"}, - {IE_CONFIG_FILE_ERROR, "[IE lib.] pair PLC/FEC not found in the config. file"}, - {IE_EQUIPMENT_UNKNOWN_ERROR, "[PLC] equipment id (devclass+eqp) unknown"}, - {IE_MEMORY_OVERFLOW_ERROR, "[PLC] get data buffer overflow"}, - {IE_ACCESS_VIOLATION_ERROR, "[PLC] DB unknown or get/set access violation"}, - {IE_MD_MONITORING_ERROR, "[PLC] bad monitoring descriptor or already used"}, - {IE_ALLOC_MONITORING_ERROR, "[PLC] monitoring record allocation failed"}, - {IE_UNALLOC_MONITORING_ERROR, "[PLC] monitoring record unallocation failed"}, - {IE_SET_TIME_IOCTL_ERROR, "[PLC] PLC date and time setting failed"}, - {IE_KEEP_ALIVE_IOCTL_ERROR, "[PLC] PLC keep-alive transactions failed"}, + {IE_SYS_ERROR, "[RFC lib.] function system error"}, //unused + {IE_PARAM_ERROR, "[IE/RFC lib.] bad parameter(s) value/format"}, //used by Silecs + {IE_CONNECT_ERROR, "[RFC lib.] PLC connection failed"}, //unused + {IE_DISCONNECT_ERROR, "[RFC lib.] PLC disconnection requested"}, //unused + {IE_TIMEOUT_ERROR, "[RFC lib.] communication (connect/send/recv) timeout occurred"}, //used by Snap7 + {IE_HOST_UNKNOWN_ERROR, "[IE lib.] PLC hostname unknown"}, //used by Silecs, PlcSendAction and PlcRcvAction }; static int errorMapSize = sizeof(errorMap)/sizeof(errorMap[0]); diff --git a/silecs-communication-cpp/src/silecs-communication/protocol/core/silecs.h b/silecs-communication-cpp/src/silecs-communication/protocol/core/silecs.h index 34ecce7..50a4666 100644 --- a/silecs-communication-cpp/src/silecs-communication/protocol/core/silecs.h +++ b/silecs-communication-cpp/src/silecs-communication/protocol/core/silecs.h @@ -38,38 +38,6 @@ /* ---------------------------------------------------------*/ #include "ietype.h" -/*----------------------------------------------------------*/ -/* Time funtion - * IeRfcSetTime: Convert time_t epoch date to PLC _DT format - * (SIMATIC). - * - * Details: - * PLC DATE_AND_TIME (or DT) format is coded on 8 byte in BCD - * format: - * 0 1 2 3 4 5 6 7 - * YY MM DD HH MN SC SC/100 SC/10e4 - */ -void IeRfcSetTime(unsigned char *dt, time_t epoch); // to PLC - -/*----------------------------------------------------------*/ -/* Time funtion - * IeRfcGetTime: Convert PLC _DT format to lynxOS time_t format - * (SIMATIC). - * - * Details: - * PLC DATE_AND_TIME (or DT) format is coded on 8 byte in BCD - * format: - * 0 1 2 3 4 5 6 7 - * YY MM DD HH MN SC SC/100 SC/10e4 - */ -double IeRfcGetTime(unsigned char *dt); // from PLC - -/*----------------------------------------------------------*/ -/* IeRfcPing function is a function used to check - * if PLC is ON (~ping) before trying to connect it. - */ -int IeRfcPing (char *hostName, char *plcIP); - /*----------------------------------------------------------*/ /* This function return the string which describes the * error given by 'error' parameter. diff --git a/silecs-communication-cpp/src/silecs-communication/protocol/modbus/iemdb.cpp b/silecs-communication-cpp/src/silecs-communication/protocol/modbus/iemdb.cpp index 58b2722..7439990 100644 --- a/silecs-communication-cpp/src/silecs-communication/protocol/modbus/iemdb.cpp +++ b/silecs-communication-cpp/src/silecs-communication/protocol/modbus/iemdb.cpp @@ -1,9 +1,6 @@ #ifdef MODBUS_SUPPORT_ENABLED #include "iemdb.h" #include <modbus.h> -//Modbus max data size definition (byte counting) -#define MAX_WRITE_DATA_SIZE MODBUS_MAX_WRITE_REGISTERS*2 -#define MAX_READ_DATA_SIZE MODBUS_MAX_READ_REGISTERS*2 /* ---------------------------------------------------------*/ /* FUNCTIONS CODE */ @@ -27,145 +24,4 @@ static inline struct timespec d_to_timespec(double time) { /*----------------------------------------------------------*/ /* Glocal functions */ /* ---------------------------------------------------------*/ - -/*----------------------------------------------------------*/ -/* Time funtion - * IeMdbSetTime: Convert lynxos time_t format to PLC SCHNEIDER - * format. - * - * Details: - * PLC SCHNEIER format is coded on 8 bytes BCD - * format: - * - * 0 1 2 3 4 5 6 7 - * SC 00 HH MN MM DD YY YY - * - * byte 1 is used by gateway to synchronize time setting. - */ -void IeMdbSetTime(unsigned char *dt, time_t epoch) // to PLC -{ - struct tm *dscst; - dscst = localtime(&epoch); - - /*v1.7*/ - /*In fact, each 16bits word is swapped with SCHNEIDER PLC - 1 0 3 2 5 4 7 6 - SC 00 HH MN MM DD YY YY - - Swapping can do it directly during data transferring - */ - - dt[1] = _tobcd(dscst->tm_sec); - dt[0] = 0x00; /*not used*/ - dt[3] = _tobcd(dscst->tm_hour); - dt[2] = _tobcd(dscst->tm_min); - dt[5] = _tobcd(dscst->tm_mon+1); - dt[4] = _tobcd(dscst->tm_mday); - dt[7] = _tobcd(2000/100); - dt[6] = _tobcd(dscst->tm_year-100); -} - -/*----------------------------------------------------------*/ -/* Time funtion - * IeMdbGetTime: Convert PLC SCHNEIDER format to time_t format. - * - * Details: - * PLC SCHNEIER format is coded on 8 bytes BCD - * format: - * - * 0 1 2 3 4 5 6 7 - * SC SC/100 HH MN MM DD YY YY - * - */ -double IeMdbGetTime(unsigned char *dt) // from PLC -{ - struct tm plcst; - time_t plctm; - double ms; - int year; - - /*In fact, each 16bits word is swapped with SCHNEIDER PLC - 1 0 3 2 5 4 7 6 - SC SC/100 HH MN MM DD YY YY - - Swapping can do it directly during data transferring - */ - - plcst.tm_sec = _frombcd(dt[1]); - plcst.tm_hour = _frombcd(dt[3]); - plcst.tm_min = _frombcd(dt[2]); - plcst.tm_mon = _frombcd(dt[5])-1; - plcst.tm_mday = _frombcd(dt[4]); - year = _frombcd(dt[6]); - //look at Schneider DATA_AND_TIME type documentation - plcst.tm_year = ((year >= 90) ? year : year+100); - /*plcst.tm_wday = no used in PLC*/ - /*plcst.tm_yday = no used in PLC*/ - - ms = ((double)_frombcd(dt[0])/100.); - plcst.tm_isdst = -1; // daylight saving time unavailable - plctm = mktime(&plcst); - - return((double)plctm + ms); -} - -/*----------------------------------------------------------*/ -int IeMBwriteData(modbus_t* ctx, uint16_t start_addr, uint16_t count, uint8_t* data) -{ - uint16_t *srcp = (uint16_t*)data; - int word_count, byte_count; - - while(count) - { - word_count = (count > MAX_WRITE_DATA_SIZE) ? MAX_WRITE_DATA_SIZE/2 : (count/2)+(count%2); - byte_count = (word_count * 2); - - if (modbus_write_registers(ctx, start_addr, word_count, srcp) != word_count) - { - return IE_EPIPE_ERROR; - } - - //srcp += byte_count-(count%2); - srcp += word_count; - start_addr += word_count; - count -= (byte_count - (count%2)); - } - - return 0; -} - -/*----------------------------------------------------------*/ -int IeMBreadData(modbus_t* ctx, uint16_t start_addr, uint16_t count, uint8_t* data) -{ - uint16_t *destp = (uint16_t*)data; - int word_count, byte_count; - - while(count) { - - if (count > MAX_READ_DATA_SIZE) - { - /*'word_count' is a word counter*/ - word_count = MAX_READ_DATA_SIZE/2; - } - else - { - /*'word_count' is a word counter*/ - word_count = (count/2) + (count%2); - } - - byte_count = (word_count * 2); - - if (modbus_read_registers(ctx, start_addr, word_count, destp) != word_count) - { - return IE_EPIPE_ERROR; - } - - //destp += (byte_count-(count%2)); - destp += word_count; - start_addr += word_count; - count -= (byte_count - (count%2)); - } - - return 0; -} #endif //MODBUS_SUPPORT_ENABLED -- GitLab