// 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 <silecs-communication/interface/utility/SilecsLog.h> #include <silecs-communication/interface/utility/StringUtilities.h> #include <silecs-communication/interface/communication/SilecsConnection.h> #include <silecs-communication/interface/equipment/SilecsPLC.h> #include <silecs-communication/interface/communication/MBConnection.h> #include <silecs-communication/interface/utility/SilecsException.h> #include <silecs-communication/protocol/modbus/iemdb.h> namespace Silecs { MBConnection::MBConnection(PLC* thePLC) : Connection(thePLC) { LOG(ALLOC) << "MBConnection (create): " << thePLC->getName(); readCtx_ = writeCtx_ = NULL; } MBConnection::~MBConnection() { //Close the connection before removing resources //disable(); must be done before removing resource } bool MBConnection::open(PLC* thePLC) { //Open read and write Modbus channels (using IP address to limit the naming-server accesses) int readErr = IeMdbOpen(&readCtx_, NULL, (char *) thePLC->getIPAddress().c_str(), (int)thePLC->getBaseAddress()); int writeErr = IeMdbOpen(&writeCtx_, NULL, (char *) thePLC->getIPAddress().c_str(), (int)thePLC->getBaseAddress()); return ((readErr == 0) && (writeErr == 0)); } bool MBConnection::close(PLC* thePLC) { int err = 0; err |= IeMdbClose(readCtx_); err |= IeMdbClose(writeCtx_); readCtx_ = writeCtx_ = NULL; return (err == 0); } int MBConnection::readData(PLC* thePLC, unsigned long address, unsigned long offset, unsigned long size, unsigned char* pBuffer) { //Schneider uses 16bit alignment memory. Block address is expressed in bytes, must be an even value! if (address % 2) throw SilecsException(__FILE__, __LINE__, PARAM_INCORRECT_BLOCK_ADDRESS, StringUtilities::toString(address)); /* . There is one read-channel per PLC connection. It must be protected against concurrent access. * . The write-channel is independent and can be accessed in parallel. * . The global action (open,close,etc.) must be protected from any concurrent access. * Attention! * Mutexes are defined with recursive option. It allows doOpen method re-calling readData * method by executing register synchronization if required. */ //(re)connect the PLC if needed and (re)synchronize the retentive registers if (doOpen(thePLC)) { //connection is established then acquire data Lock lock(readMux_); //Schneider uses 16bit alignment memory. Block address is expressed in bytes (==> /2) unsigned short addr = ((unsigned short)address + (unsigned short)offset)/2; //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); if(error) throw SilecsException(__FILE__, __LINE__, UNEXPECTED_ERROR," ModBus Error: " + string(IeGetErrorMessage(error))); } return 0; } int MBConnection::writeData(PLC* thePLC, unsigned long address, unsigned long offset, unsigned long size, unsigned char* pBuffer) { //Schneider uses 16bit alignment memory. Block address is expressed in bytes, must be an even value! if (address % 2) throw SilecsException(__FILE__, __LINE__, PARAM_INCORRECT_BLOCK_ADDRESS, StringUtilities::toString(address)); /* . There is one read-channel per PLC connection. It must be protected against concurrent access. * . The write-channel is independent and can be accessed in parallel. * . The global action (open,close,etc.) must be protected from any concurrent access. * Attention! * Mutexes are defined with recursive option. It allows doOpen method re-calling sendData * method by executing register synchronization if required. */ //(re)connect the PLC if needed and (re)synchronize the retentive registers if (doOpen(thePLC)) { //connection is established then send data Lock lock(writeMux_); //Schneider uses 16bit alignment memory. Block address is expressed in bytes (==> /2) unsigned short addr = ((unsigned short)address + (unsigned short)offset)/2; //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); if(error) throw SilecsException(__FILE__, __LINE__, UNEXPECTED_ERROR," ModBus Error: " + string(IeGetErrorMessage(error))); } return 0; } } // namespace #endif //MODBUS_SUPPORT_ENABLED