From d5c644fd294f5f98f474a8e4afb10d60ab8413e4 Mon Sep 17 00:00:00 2001 From: kochalas <konstantinos.chalas@cern.ch> Date: Fri, 8 Sep 2017 12:16:22 +0200 Subject: [PATCH] [SIL-341] Implement byte addressing support for Modbus TCP --- .../interface/communication/CNVConnection.cpp | 214 ++++---- .../interface/communication/CNVConnection.h | 148 ++--- .../interface/communication/MBConnection.cpp | 512 ++++++++++++------ .../interface/communication/MBConnection.h | 73 ++- .../communication/SNAP7Connection.cpp | 17 + .../interface/communication/SNAP7Connection.h | 8 +- .../communication/SilecsConnection.h | 9 +- .../interface/core/PLCRecvAction.cpp | 34 +- .../interface/core/PLCSendAction.cpp | 32 +- 9 files changed, 674 insertions(+), 373 deletions(-) diff --git a/silecs-communication-cpp/src/silecs-communication/interface/communication/CNVConnection.cpp b/silecs-communication-cpp/src/silecs-communication/interface/communication/CNVConnection.cpp index dd6ebf5..77ae841 100644 --- a/silecs-communication-cpp/src/silecs-communication/interface/communication/CNVConnection.cpp +++ b/silecs-communication-cpp/src/silecs-communication/interface/communication/CNVConnection.cpp @@ -14,7 +14,6 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. #ifdef NI_SUPPORT_ENABLED - #include <silecs-communication/interface/communication/CNVConnection.h> #include <silecs-communication/interface/communication/SilecsConnection.h> @@ -28,134 +27,155 @@ namespace Silecs { - CNVConnection::CNVConnection(PLC* thePLC) : Connection(thePLC) - { - LOG(ALLOC) << "CNVConnection (create): " << thePLC->getName(); - } +CNVConnection::CNVConnection(PLC* thePLC) : + Connection(thePLC) +{ + LOG(ALLOC) << "CNVConnection (create): " << thePLC->getName(); +} - CNVConnection::~CNVConnection() - { - //Close the connection before removing resources - //disable(); must be done before removing resource - } +CNVConnection::~CNVConnection() +{ + //Close the connection before removing resources + //disable(); must be done before removing resource +} - /* Check for errors */ - int CNVConnection::errChk(PLC* thePLC, int code) // throw (std::string*) +/* Check for errors */ +int CNVConnection::errChk(PLC* thePLC, int code) // throw (std::string*) +{ + if (code != 0) { - if(code != 0) - { LOG(DEBUG) << "CNV error occurred: " << code << ": " << CNVGetErrorDescription(code); - //Do not close the connection if it's only for data missing (when deleting empty Data object) - if (code != CNVInvalidDataHandleError) - { - LOG(COMM) << "Transaction failure with PXI: " << thePLC->getName() << ". CNV[" << code << "]: " << CNVGetErrorDescription(code); - doClose(thePLC, /*withLock =*/true); - throw SilecsException(__FILE__, __LINE__, CNV_INTERNAL_ERROR, std::string(CNVGetErrorDescription(code))+" in CNVConnection"); - } + LOG(DEBUG) << "CNV error occurred: " << code << ": " << CNVGetErrorDescription(code); + //Do not close the connection if it's only for data missing (when deleting empty Data object) + if (code != CNVInvalidDataHandleError) + { + LOG(COMM) << "Transaction failure with PXI: " << thePLC->getName() << ". CNV[" << code << "]: " << CNVGetErrorDescription(code); + doClose(thePLC, /*withLock =*/true); + throw SilecsException(__FILE__, __LINE__, CNV_INTERNAL_ERROR, std::string(CNVGetErrorDescription(code)) + " in CNVConnection"); } - return code; } + return code; +} - /* Read Data */ - int CNVConnection::readData(PLC* thePLC, CNVBufferedSubscriber *handle,CNVData *data) - { - CNVBufferDataStatus status; +/* Read Data */ +int CNVConnection::readData(PLC* thePLC, CNVBufferedSubscriber *handle, CNVData *data) +{ + CNVBufferDataStatus status; - /* Buffer is used only for performance reason. - * Always return the last value in the buffer discarding the oldest ones. - */ + /* Buffer is used only for performance reason. + * Always return the last value in the buffer discarding the oldest ones. + */ - //read the oldest data in the buffer - int errorCode = errChk(thePLC, CNVGetDataFromBuffer(*handle, data, &status)); + //read the oldest data in the buffer + int errorCode = errChk(thePLC, CNVGetDataFromBuffer(*handle, data, &status)); - if ((errorCode == 0) && (status != CNVStaleData)) + if ( (errorCode == 0) && (status != CNVStaleData)) + { + if (status != CNVNoData) + { //buffer was not empty, try to read again to get the very last data if any + errChk(thePLC, CNVDisposeData(*data)); //CNV requires to free the buffer on every CNVGetDataFromBuffer + errorCode = errChk(thePLC, CNVGetDataFromBuffer(*handle, data, &status)); + } + else { - if (status != CNVNoData) - { //buffer was not empty, try to read again to get the very last data if any - errChk(thePLC, CNVDisposeData(*data));//CNV requires to free the buffer on every CNVGetDataFromBuffer - errorCode = errChk(thePLC, CNVGetDataFromBuffer(*handle, data, &status)); - } - else - { LOG(DEBUG) << "CNVConnection::readData() : buffer is empty (variables may not be initialized properly)"; - errorCode = CNVEmptyDataError; - } + LOG(DEBUG) << "CNVConnection::readData() : buffer is empty (variables may not be initialized properly)"; + errorCode = CNVEmptyDataError; } - return errorCode; } + return errorCode; +} - /* Write Data */ - int CNVConnection::writeData(PLC* thePLC, const char *networkVariablePathname, CNVData data) - { - CNVWriter writer; - - int errorCode = errChk(thePLC, CNVCreateWriter(networkVariablePathname , NULL, NULL, CNVWaitForever , 0, &writer)); +/* Write Data */ +int CNVConnection::writeData(PLC* thePLC, const char *networkVariablePathname, CNVData data) +{ + CNVWriter writer; - if (errorCode ==0) - { CNVWrite (writer, data, 0); - CNVDispose(writer); - } - return errorCode; - } + int errorCode = errChk(thePLC, CNVCreateWriter(networkVariablePathname, NULL, NULL, CNVWaitForever, 0, &writer)); - int CNVConnection::readMemory(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer) - { - return readData(thePLC, address, offset, size, pBuffer); - } - int CNVConnection::writeMemory(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer) + if (errorCode == 0) { - return writeData(thePLC, address, offset, size, pBuffer); + CNVWrite(writer, data, 0); + CNVDispose(writer); } - int CNVConnection::readIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer) - { + return errorCode; +} - LOG(ERROR) << "Read IO not supported for CNV"; - return 0; - } +int CNVConnection::readMemory(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer) +{ + return readData(thePLC, address, offset, size, pBuffer); +} +int CNVConnection::writeMemory(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer) +{ + return writeData(thePLC, address, offset, size, pBuffer); +} +int CNVConnection::readAIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer) +{ - int CNVConnection::writeIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer) - { - LOG(ERROR) << "Write IO not supported for CNV"; - return 0; - } + LOG(ERROR) << "Read IO not supported for CNV"; + return 0; +} - bool CNVConnection::open(PLC* thePLC) - { - //Controller is reachable at this stage (ping done from doOpen) - bool isOK = true;//all subscription is fine a priori +int CNVConnection::writeAIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer) +{ + LOG(ERROR) << "Write IO not supported for CNV"; + return 0; +} - LOG(DEBUG) << "CNVConnection::open(): Subscribe to all input variables"; +int CNVConnection::readDIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer) +{ + + LOG(ERROR) << "Read IO not supported for CNV"; + return 0; +} - // Create all the subscriptions - blockMapType::iterator pBlockIter; - for(pBlockIter = thePLC->getBlockMap().begin(); pBlockIter != thePLC->getBlockMap().end(); ++pBlockIter) +int CNVConnection::writeDIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer) +{ + LOG(ERROR) << "Write IO not supported for CNV"; + return 0; +} + +bool CNVConnection::open(PLC* thePLC) +{ + //Controller is reachable at this stage (ping done from doOpen) + bool isOK = true; //all subscription is fine a priori + + LOG(DEBUG) << "CNVConnection::open(): Subscribe to all input variables"; + + // Create all the subscriptions + blockMapType::iterator pBlockIter; + for (pBlockIter = thePLC->getBlockMap().begin(); pBlockIter != thePLC->getBlockMap().end(); ++pBlockIter) + { + //Attention: only CNVInputBlock has doSubscribe/unSubscribe feature (key-map can be used to select appropriate object) + if (pBlockIter->first.find(Block::whichAccessType(Input)) != std::string::npos) { - //Attention: only CNVInputBlock has doSubscribe/unSubscribe feature (key-map can be used to select appropriate object) - if (pBlockIter->first.find(Block::whichAccessType(Input)) != std::string::npos) - { if (static_cast<CNVInputBlock*>(pBlockIter->second)->doSubscribe() == false) - { isOK = false; - break; - } + if (static_cast<CNVInputBlock*>(pBlockIter->second)->doSubscribe() == false) + { + isOK = false; + break; } } - return isOK; } + return isOK; +} - bool CNVConnection::close(PLC* thePLC) - { - LOG(DEBUG) << "CNVConnection::close(): unSubscribe all input variables"; +bool CNVConnection::close(PLC* thePLC) +{ + LOG(DEBUG) << "CNVConnection::close(): unSubscribe all input variables"; - // Delete all the subscription - blockMapType::iterator pBlockIter; - for(pBlockIter = thePLC->getBlockMap().begin(); pBlockIter != thePLC->getBlockMap().end(); ++pBlockIter) + // Delete all the subscription + blockMapType::iterator pBlockIter; + for (pBlockIter = thePLC->getBlockMap().begin(); pBlockIter != thePLC->getBlockMap().end(); ++pBlockIter) + { + //Attention: only CNVInputBlock has doSubscribe/unSubscribe feature (key-map can be used to select appropriate object) + if (pBlockIter->first.find(Block::whichAccessType(Input)) != std::string::npos) { - //Attention: only CNVInputBlock has doSubscribe/unSubscribe feature (key-map can be used to select appropriate object) - if (pBlockIter->first.find(Block::whichAccessType(Input)) != std::string::npos) - { static_cast<CNVInputBlock*>(pBlockIter->second)->unSubscribe(); - } + static_cast<CNVInputBlock*>(pBlockIter->second)->unSubscribe(); } - isConnected_ = false; - return true; } + isConnected_ = false; + return true; +} } // namespace + #endif //NI_SUPPORT_ENABLED diff --git a/silecs-communication-cpp/src/silecs-communication/interface/communication/CNVConnection.h b/silecs-communication-cpp/src/silecs-communication/interface/communication/CNVConnection.h index dc9d002..ed5c1d1 100644 --- a/silecs-communication-cpp/src/silecs-communication/interface/communication/CNVConnection.h +++ b/silecs-communication-cpp/src/silecs-communication/interface/communication/CNVConnection.h @@ -35,82 +35,86 @@ extern "C" namespace Silecs { +/*! + * \class CNVConnection + * \brief CNV-communication object for PXI Shared Variable Protocol + */ +class CNVConnection : public Connection +{ + +public: + CNVConnection(PLC* thePLC); + virtual ~CNVConnection(); + + /*! + * \brief Read Data from the specified buffer handler + * \return the read CNVData and error code (0 = everything is ok) + */ + //CNVData readData(CNVBufferedSubscriber *handle); + int readData(PLC* thePLC, CNVBufferedSubscriber *handle, CNVData *data); + /*! - * \class CNVConnection - * \brief CNV-communication object for PXI Shared Variable Protocol + * \brief Write the specified variable, data must be already in CNVData format. + * In case of error raises a std::string* exception containing the error message + * \return error code (0 = everything is ok) */ - class CNVConnection : public Connection + int writeData(PLC* thePLC, const char *networkVariablePathname, CNVData data); + + /*! + * \brief Subscribes for changes on the the specified variable. + * In case of error raises a std::string* exception containing the error message + */ + void monitorData(const char *networkVariablePathname); + + // not implemented. here because of virtual in super class + // TODO: review in order to get read of it + int readData(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* buffer) + { + return -1; + } + ; + + // not implemented. here because of virtual in super class + // TODO: review in order to get read of it + int writeData(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* buffer) + { + return -1; + } + ; + int readMemory(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); + int writeMemory(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); + int readAIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); + int writeAIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); + int readDIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); + int writeDIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); + +private: + // Subscriber + CNVSubscriber subscriber; + + /*! + * \brief Print the error message related to the code and throw exception if needed + * \return the error code itself (0 everything is ok) + */ + int errChk(PLC* thePLC, int code); + + /*! + * \brief Open the connection + * It actually return a boolean which indicates if the server responded to a ping operation + */ + bool open(PLC* thePLC); + + /*! + * \brief Close the connection with the server + */ + bool close(PLC* thePLC); + + bool checkError(PLC* thePLC, int err, bool retry) { + return false; + } - public: - CNVConnection(PLC* thePLC); - virtual ~CNVConnection(); - - /*! - * \brief Read Data from the specified buffer handler - * \return the read CNVData and error code (0 = everything is ok) - */ - //CNVData readData(CNVBufferedSubscriber *handle); - int readData(PLC* thePLC, CNVBufferedSubscriber *handle,CNVData *data); - - /*! - * \brief Write the specified variable, data must be already in CNVData format. - * In case of error raises a std::string* exception containing the error message - * \return error code (0 = everything is ok) - */ - int writeData(PLC* thePLC, const char *networkVariablePathname, CNVData data); - - /*! - * \brief Subscribes for changes on the the specified variable. - * In case of error raises a std::string* exception containing the error message - */ - void monitorData(const char *networkVariablePathname); - - // not implemented. here because of virtual in super class - // TODO: review in order to get read of it - int readData(PLC* thePLC, long address, - unsigned long offset, - unsigned long size, - unsigned char* buffer) - { return -1;}; - - // not implemented. here because of virtual in super class - // TODO: review in order to get read of it - int writeData(PLC* thePLC, long address, - unsigned long offset, - unsigned long size, - unsigned char* buffer) - { return -1;}; - int readMemory(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); - int writeMemory(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); - int readIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); - int writeIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); - - private: - // Subscriber - CNVSubscriber subscriber; - - /*! - * \brief Print the error message related to the code and throw exception if needed - * \return the error code itself (0 everything is ok) - */ - int errChk(PLC* thePLC, int code); - - /*! - * \brief Open the connection - * It actually return a boolean which indicates if the server responded to a ping operation - */ - bool open(PLC* thePLC); - - /*! - * \brief Close the connection with the server - */ - bool close(PLC* thePLC); - - bool checkError(PLC* thePLC, int err, bool retry) - { return false;} - - }; +}; } // namespace 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 4b6ca5f..414827e 100644 --- a/silecs-communication-cpp/src/silecs-communication/interface/communication/MBConnection.cpp +++ b/silecs-communication-cpp/src/silecs-communication/interface/communication/MBConnection.cpp @@ -20,234 +20,424 @@ #include <silecs-communication/interface/communication/MBConnection.h> #include <silecs-communication/interface/utility/SilecsException.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 +#include <bitset> + +// 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 { - MBConnection::MBConnection(PLC* thePLC) : - Connection(thePLC) - { - LOG(ALLOC) << "MBConnection (create): " << thePLC->getName(); +MBConnection::MBConnection(PLC *thePLC) : + Connection(thePLC) +{ + LOG(ALLOC) << "MBConnection (create): " << thePLC->getName(); - //Connection use IP address to limit the naming-server accesses - readCtx_ = modbus_new_tcp((char *)thePLC->getIPAddress().c_str(), MODBUS_TCP_DEFAULT_PORT /*=502*/); - writeCtx_ = modbus_new_tcp((char *)thePLC->getIPAddress().c_str(), MODBUS_TCP_DEFAULT_PORT /*=502*/); + // Connection use IP address to limit the naming-server accesses + readCtx_ = modbus_new_tcp((char *)thePLC->getIPAddress().c_str(), MODBUS_TCP_DEFAULT_PORT /*=502*/); + writeCtx_ = modbus_new_tcp((char *)thePLC->getIPAddress().c_str(), MODBUS_TCP_DEFAULT_PORT /*=502*/); - /*TODO: To be adjusted with the next stable libmodbus release (>3.1.1) - which will fix the current timeout response time issue (see libmodbus forum). + /*TODO: To be adjusted with the next stable libmodbus release (>3.1.1) + which will fix the current timeout response time issue (see libmodbus + forum). - Define Modbus response timeout - struct timeval response_timeout; - response_timeout.tv_sec = 0; - response_timeout.tv_usec = 10000; + Define Modbus response timeout + struct timeval response_timeout; + response_timeout.tv_sec = 0; + response_timeout.tv_usec = 10000; - modbus_set_response_timeout(readCtx_ , &response_timeout); - modbus_set_response_timeout(writeCtx_ , &response_timeout); - */ + modbus_set_response_timeout(readCtx_ , &response_timeout); + modbus_set_response_timeout(writeCtx_ , &response_timeout); + */ - modbus_set_slave(readCtx_, 1); - //modbus_set_debug(readCtx_, TRUE); - } + modbus_set_slave(readCtx_, 1); + // modbus_set_debug(readCtx_, TRUE); +} - MBConnection::~MBConnection() - { - //Close the connection before removing resources - //disable(); must be done before removing resource - modbus_free(writeCtx_); - modbus_free(readCtx_); - } +MBConnection::~MBConnection() +{ + // Close the connection before removing resources + // disable(); must be done before removing resource + modbus_free(writeCtx_); + modbus_free(readCtx_); +} - bool MBConnection::open(PLC* thePLC) - { - int readErr = modbus_connect(readCtx_); - int writeErr = modbus_connect(writeCtx_); - return ( (readErr != -1) && (writeErr != -1)); - } +bool MBConnection::open(PLC *thePLC) +{ + int readErr = modbus_connect(readCtx_); + int writeErr = modbus_connect(writeCtx_); + return ( (readErr != -1) && (writeErr != -1)); +} - bool MBConnection::close(PLC* thePLC) - { - modbus_close(readCtx_); - modbus_close(writeCtx_); - return true; - } +bool MBConnection::close(PLC *thePLC) +{ + modbus_close(readCtx_); + modbus_close(writeCtx_); + return true; +} + +int MBConnection::readFrames(modbus_t *ctx, long start_addr, uint16_t count, uint8_t *data, int IO) +{ + uint16_t *destp = (uint16_t *)data; + int word_count, byte_count; - /*----------------------------------------------------------*/ - int MBConnection::mbWriteFrames(modbus_t* ctx, long start_addr, uint16_t count, uint8_t* data) + while (count) { - uint16_t *srcp = (uint16_t*)data; - uint16_t word_count, byte_count; - while (count) + if (count > MAX_READ_DATA_SIZE) { - word_count = static_cast<uint16_t>((count > MAX_WRITE_DATA_SIZE) ? MAX_WRITE_DATA_SIZE / 2 : (count / 2) + (count % 2)); - byte_count = static_cast<uint16_t>(word_count * 2); + /*'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); + } - if (modbus_write_registers(ctx, static_cast<int>(start_addr), static_cast<int>(word_count), srcp) != word_count) - { - return -1; - } + byte_count = (word_count * 2); - //srcp += byte_count-(count%2); - srcp += word_count; - start_addr += word_count; - count = static_cast<uint16_t>(count - (byte_count - (count % 2))); + if (IO) + { + if (modbus_read_input_registers(ctx, static_cast<int>(start_addr), static_cast<int>(word_count), destp) != word_count) + return -1; + } + else + { + if (modbus_read_registers(ctx, static_cast<int>(start_addr), static_cast<int>(word_count), destp) != word_count) + return -1; } - return 0; + // destp += (byte_count-(count%2)); + destp += word_count; + start_addr += word_count; + count = static_cast<uint16_t>(count - (byte_count - (count % 2))); } - /*----------------------------------------------------------*/ - int MBConnection::mbReadFrames(modbus_t* ctx, long start_addr, uint16_t count, uint8_t* data) + return 0; +} + +int MBConnection::writeFrames(modbus_t *ctx, long start_addr, uint16_t count, uint8_t *data) +{ + uint16_t *srcp = (uint16_t *)data; + uint16_t word_count, byte_count; + + while (count) { - uint16_t *destp = (uint16_t*)data; - int word_count, byte_count; + word_count = static_cast<uint16_t>( (count > MAX_WRITE_DATA_SIZE) ? MAX_WRITE_DATA_SIZE / 2 : (count / 2) + (count % 2)); + byte_count = static_cast<uint16_t>(word_count * 2); - while (count) + if (modbus_write_registers(ctx, static_cast<int>(start_addr), static_cast<int>(word_count), srcp) != word_count) { + return -1; + } - 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); - } + // srcp += byte_count-(count%2); + srcp += word_count; + start_addr += word_count; + count = static_cast<uint16_t>(count - (byte_count - (count % 2))); + } - byte_count = (word_count * 2); + return 0; +} - if (modbus_read_registers(ctx, static_cast<int>(start_addr), static_cast<int>(word_count), destp) != word_count) - { - return -1; - } +int MBConnection::readCoils(modbus_t *ctx, long start_addr, uint16_t count, uint8_t *data) +{ + // Need one byte for each bit in buffer, e.g 2 bytes would require a 16 byte array + uint8_t c[count * 8]; + uint16_t bit_count; + uint16_t current_count; + uint16_t current_bit_count; + + int j = 0; + int i = 0; + current_count = static_cast<uint16_t>(count * 8); + // Byte address to bit address + start_addr *= 8; + j = 0; + + while (current_count) + { + bit_count = static_cast<uint16_t>( (current_count > MODBUS_MAX_READ_BITS) ? MODBUS_MAX_READ_BITS : current_count); - //destp += (byte_count-(count%2)); - destp += word_count; - start_addr += word_count; - count = static_cast<uint16_t>(count - (byte_count - (count % 2))); + if (modbus_read_input_bits(ctx, static_cast<int>(start_addr), bit_count, &c[j]) < 0) + { + return -1; } - return 0; + j += bit_count; + start_addr += bit_count; + current_count = static_cast<uint16_t>(current_count - bit_count); } - int MBConnection::readData(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer) + // The buffer is filled with boolean (8 bits) data that need to be converted back to a bitstream + + for (i = 0; i < count; i++) { - int err = 0; + current_bit_count = static_cast<uint16_t>(i * 8); + data[i] = 0x00; + for (j = 0; j < 8; j++) + { + if (c[current_bit_count + j]) + data[i] |= static_cast<unsigned char>(1 << (7 - j)); + } + } - //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)); + return 0; +} - /* . 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. - */ +int MBConnection::writeCoils(modbus_t *ctx, long start_addr, uint16_t count, uint8_t *data) +{ + string tmp; - //(re)connect the PLC if needed and (re)synchronize the retentive registers - if (doOpen(thePLC)) - { - //connection is established then acquire data - readLock(); - //Schneider uses 16bit alignment memory. Block address is expressed in bytes (==> /2) - long addr = (address + offset) / 2; + // Need one byte for each bit in data, e.g 2 bytes would require a 16 byte array + uint8_t c[count * 8]; + uint16_t bit_count; - //DATA topic makes sense with RECV one - if (RECV & Log::topics_) - LOG(DATA) << "Read data, address: %MW" << addr << ", byte-size: " << size; + int i, k; + int j = 0; - err = mbReadFrames(readCtx_, addr, (unsigned short)size, pBuffer); - checkError(thePLC, err, false);// close the connection, will try again at the next access - readUnlock(); + // Convert the bitstream to a bool array + for (i = 0; i < count; i++) + { + std::bitset<8> b(data[i]); + tmp = b.to_string(); + std::copy(tmp.begin(), tmp.end(), &c[j]); + for (k = j; k < j + 8; k++) + { + // Convert char to int + c[k] = static_cast<uint8_t>(c[k] - '0'); } - return err; + j += 8; } - int MBConnection::writeData(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer) - { - int err = 0; + count = static_cast<uint16_t>(count * 8); + // Byte address to bit address + start_addr *= 8; + j = 0; - //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. - */ + while (count) + { + bit_count = static_cast<uint16_t>( (count > MODBUS_MAX_WRITE_BITS) ? MODBUS_MAX_WRITE_BITS : count); - //(re)connect the PLC if needed and (re)synchronize the retentive registers - if (doOpen(thePLC)) + if (modbus_write_bits(ctx, static_cast<int>(start_addr), bit_count, &c[j]) < 0) { - //connection is established then send data - writeLock(); - //Schneider uses 16bit alignment memory. Block address is expressed in bytes (==> /2) - long addr = (address + offset) / 2; - - //DATA topic makes sense with SEND one - if (SEND & Log::topics_) - LOG(DATA) << "Write data, address: %MW" << addr << ", byte-size: " << size; - - err = mbWriteFrames(writeCtx_, (unsigned short)addr, (unsigned short)size, pBuffer); - checkError(thePLC, err, false);// close the connection, will try again at the next access - writeUnlock(); + return -1; } - return err; + + j += bit_count; + start_addr += bit_count; + count = static_cast<uint16_t>(count - bit_count); } + return 0; +} + +int MBConnection::readRegisters(PLC *thePLC, long address, unsigned long offset, unsigned long size, unsigned char *pBuffer, int IO) +{ + int err = 0; - int MBConnection::readMemory(PLC* thePLC, 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)) { - return readData(thePLC, address, offset, size, pBuffer); + // connection is established then acquire data + readLock(); + // Schneider uses 16bit alignment memory. Block address is expressed in + // bytes (==> /2) + long addr = (address + offset) / 2; + + // DATA topic makes sense with RECV one + if (RECV & Log::topics_) + LOG(DATA) << "Read data, address: %MW" << addr << ", byte-size: " << size; + + err = readFrames(readCtx_, addr, (unsigned short)size, pBuffer, IO); + checkError(thePLC, err, false); // close the connection, will try again + // at the next access + readUnlock(); } + return err; +} + +int MBConnection::writeRegisters(PLC *thePLC, long address, unsigned long offset, unsigned long size, unsigned char *pBuffer) +{ + int err = 0; + + // 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)); - int MBConnection::writeMemory(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer) + /* . 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)) { - return writeData(thePLC, address, offset, size, pBuffer); + // connection is established then send data + writeLock(); + // Schneider uses 16bit alignment memory. Block address is expressed in + // bytes (==> /2) + long addr = (address + offset) / 2; + + // DATA topic makes sense with SEND one + if (SEND & Log::topics_) + LOG(DATA) << "Write data, address: %MW" << addr << ", byte-size: " << size; + + err = writeFrames(writeCtx_, (unsigned short)addr, (unsigned short)size, pBuffer); + checkError(thePLC, err, false); // close the connection, will try again + // at the next access + writeUnlock(); } + return err; +} - int MBConnection::readIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer) +int MBConnection::readBits(PLC *thePLC, long address, unsigned long offset, unsigned long size, unsigned char *pBuffer) +{ + int err = 0; + + /* . 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)) { - return readData(thePLC, address, offset, size, pBuffer); + // connection is established then acquire data + readLock(); + long addr = address + offset; + + // DATA topic makes sense with RECV one + if (RECV & Log::topics_) + LOG(DATA) << "Read bits, address: %MW" << addr << ", byte-size: " << size; + + err = readCoils(readCtx_, addr, (unsigned short)size, pBuffer); + checkError(thePLC, err, false); // close the connection, will try again + // at the next access + readUnlock(); } + return err; +} - int MBConnection::writeIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer) +int MBConnection::writeBits(PLC *thePLC, long address, unsigned long offset, unsigned long size, unsigned char *pBuffer) +{ + int err = 0; + + /* . 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)) { - return writeData(thePLC, address, offset, size, pBuffer); + // connection is established then send data + writeLock(); + + long addr = address + offset; + + // DATA topic makes sense with SEND one + if (SEND & Log::topics_) + LOG(DATA) << "Write data, address: %MW" << addr << ", byte-size: " << size; + + err = writeCoils(writeCtx_, (unsigned short)addr, (unsigned short)size, pBuffer); + checkError(thePLC, err, false); // close the connection, will try again + // at the next access + writeUnlock(); } + return err; +} + +int MBConnection::readMemory(PLC *thePLC, long address, unsigned long offset, unsigned long size, unsigned char *pBuffer) +{ + return readRegisters(thePLC, address, offset, size, pBuffer, 0); +} + +int MBConnection::writeMemory(PLC *thePLC, long address, unsigned long offset, unsigned long size, unsigned char *pBuffer) +{ + return writeRegisters(thePLC, address, offset, size, pBuffer); +} + +int MBConnection::readAIO(PLC *thePLC, long address, unsigned long offset, unsigned long size, unsigned char *pBuffer) +{ + return readRegisters(thePLC, address, offset, size, pBuffer, 1); +} + +int MBConnection::writeAIO(PLC *thePLC, long address, unsigned long offset, unsigned long size, unsigned char *pBuffer) +{ + return writeRegisters(thePLC, address, offset, size, pBuffer); +} + +int MBConnection::readDIO(PLC *thePLC, long address, unsigned long offset, unsigned long size, unsigned char *pBuffer) +{ + return readBits(thePLC, address, offset, size, pBuffer); +} + +int MBConnection::writeDIO(PLC *thePLC, long address, unsigned long offset, unsigned long size, unsigned char *pBuffer) +{ + return writeBits(thePLC, address, offset, size, pBuffer); +} //------------------------------------------------------------------------------------------------------------------- - bool MBConnection::checkError(PLC* thePLC, int err, bool retry) +bool MBConnection::checkError(PLC *thePLC, int err, bool retry) +{ + + if (err != 0) { + LOG(COMM) << "Transaction failure with the controller: " << thePLC->getName() << ". MODBUS[" << errno << "]: " << modbus_strerror(errno); - if (err != 0) + if (retry) { - LOG(COMM) << "Transaction failure with the controller: " << thePLC->getName() << ". MODBUS[" << errno << "]: " << modbus_strerror(errno); - - if (retry) - { - LOG(COMM) << "Try to reconnect the controller: " << 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 controller: " << thePLC->getName(); + LOG(COMM) << "Try to reconnect the controller: " << thePLC->getName(); + if (reOpen(thePLC)) + { // can repeat the request after the connection was successfully + // reopened + return true; } - // no retry, we just want to close (use default case) - doClose(thePLC, /*withLock =*/true); + // reconnection has failed again, just close the connection + LOG(COMM) << "Unable to reconnect the controller: " << thePLC->getName(); } - return false; + // no retry, we just want to close (use default case) + doClose(thePLC, /*withLock =*/true); } + return false; +} } // namespace 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 98ef27e..4b9ed19 100644 --- a/silecs-communication-cpp/src/silecs-communication/interface/communication/MBConnection.h +++ b/silecs-communication-cpp/src/silecs-communication/interface/communication/MBConnection.h @@ -21,36 +21,49 @@ namespace Silecs { - class PLC; - class Connection; - - /*! - * \class MBConnection - * \brief Plc-communication object for specific Modbus protocol - */ - class MBConnection : public Connection - { - - public: - MBConnection(PLC* thePLC); - virtual ~MBConnection(); - - int readMemory(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); - int writeMemory(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); - int readIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); - int writeIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); - - private: - modbus_t* readCtx_; - modbus_t* writeCtx_; - bool open(PLC* thePLC); - bool close(PLC* thePLC); - int readData(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); - int writeData(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); - int mbWriteFrames(modbus_t* ctx, uint16_t dataAddr, uint16_t dataSize, uint8_t* dataBuffer); - int mbReadFrames(modbus_t* ctx, uint16_t dataAddr, uint16_t dataSize, uint8_t* dataBuffer); - bool checkError(PLC* thePLC, int err, bool retry); - }; +class PLC; +class Connection; + +/*! + * \class MBConnection + * \brief Plc-communication object for specific Modbus protocol + */ +class MBConnection : public Connection +{ + +public: + MBConnection(PLC* thePLC); + virtual ~MBConnection(); + + int readMemory(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); + int writeMemory(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); + int readAIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); + int writeAIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); + int readDIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); + int writeDIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); + +private: + modbus_t* readCtx_; + modbus_t* writeCtx_; + bool open(PLC* thePLC); + bool close(PLC* thePLC); + + // This set of functions are used to address the analog and memory registers + int readRegisters(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer, int IO); + int writeRegisters(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); + // read and write Frames interfere directly with the libmodbus API + int readFrames(modbus_t* ctx, long dataAddr, uint16_t dataSize, uint8_t* dataBuffer, int IO); + int writeFrames(modbus_t* ctx, long dataAddr, uint16_t dataSize, uint8_t* dataBuffer); + + // This set of functions are used to address the digital registers + int readBits(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); + int writeBits(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); + // read and write Coils interfere directly with the libmodbus API + int readCoils(modbus_t* ctx, long dataAddr, uint16_t dataSize, uint8_t* dataBuffer); + int writeCoils(modbus_t* ctx, long dataAddr, uint16_t dataSize, uint8_t* dataBuffer); + + bool checkError(PLC* thePLC, int err, bool retry); +}; } // namespace 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 a28c4ce..0c11c1b 100644 --- a/silecs-communication-cpp/src/silecs-communication/interface/communication/SNAP7Connection.cpp +++ b/silecs-communication-cpp/src/silecs-communication/interface/communication/SNAP7Connection.cpp @@ -298,6 +298,23 @@ int SNAP7Connection::writeIO(PLC* thePLC, long address, unsigned long offset, un return err; } +int SNAP7Connection::readAIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer) +{ + return readIO(thePLC, address, offset, size, pBuffer); +} +int SNAP7Connection::writeAIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer) +{ + return writeIO(thePLC, address, offset, size, pBuffer); +} +int SNAP7Connection::readDIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer) +{ + return readIO(thePLC, address, offset, size, pBuffer); +} +int SNAP7Connection::writeDIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer) +{ + return writeIO(thePLC, address, offset, size, pBuffer); +} + //------------------------------------------------------------------------------------------------------------------- bool SNAP7Connection::checkError(PLC* thePLC, int err, bool retry) { 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 4fc1ace..7e53000 100644 --- a/silecs-communication-cpp/src/silecs-communication/interface/communication/SNAP7Connection.h +++ b/silecs-communication-cpp/src/silecs-communication/interface/communication/SNAP7Connection.h @@ -45,8 +45,10 @@ public: int writeData(PLC* thePLC, long DBn, unsigned long offset, unsigned long size, unsigned char* pBuffer); int readMemory(PLC* thePLC, long DBn, unsigned long offset, unsigned long size, unsigned char* pBuffer); int writeMemory(PLC* thePLC, long DBn, unsigned long offset, unsigned long size, unsigned char* pBuffer); - int readIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); - int writeIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); + int readAIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); + int writeAIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); + int readDIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); + int writeDIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); //Extension Silecs methods int coldRestart(PLC* thePLC); @@ -57,6 +59,8 @@ private: S7Object sendClient_; bool open(PLC* thePLC); bool close(PLC* thePLC); + int readIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); + int writeIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* pBuffer); int rackNb_; //rackNb - common rack is 0 by default int slotNb_; //slotNb - depends on hardware configuration (scan is required the first connect) 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 9e4e682..7c668b0 100644 --- a/silecs-communication-cpp/src/silecs-communication/interface/communication/SilecsConnection.h +++ b/silecs-communication-cpp/src/silecs-communication/interface/communication/SilecsConnection.h @@ -49,9 +49,14 @@ public: virtual int writeMemory(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* buffer) = 0; - virtual int readIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* buffer) = 0; + virtual int readAIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* buffer) = 0; + + virtual int writeAIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* buffer) = 0; + + virtual int readDIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* buffer) = 0; + + virtual int writeDIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* buffer) = 0; - virtual int writeIO(PLC* thePLC, long address, unsigned long offset, unsigned long size, unsigned char* buffer) = 0; /*! * \fn enable/disable * \brief The client can suspend the data transmission by disabling the connection if required. diff --git a/silecs-communication-cpp/src/silecs-communication/interface/core/PLCRecvAction.cpp b/silecs-communication-cpp/src/silecs-communication/interface/core/PLCRecvAction.cpp index 92230bc..aa9a7b1 100644 --- a/silecs-communication-cpp/src/silecs-communication/interface/core/PLCRecvAction.cpp +++ b/silecs-communication-cpp/src/silecs-communication/interface/core/PLCRecvAction.cpp @@ -96,9 +96,15 @@ int PLCRecvBlockMode::execute(Context* pContext) switch (theBlock_->getAccessArea()) { case Digital: + errorCode = pConn->readDIO(theBlock_->getPLC(), usedAddress, //Base address (or DBn) of the block + usedDeviceOffset, //Device data address within the block + usedSize, //Get one device-block only + pBuffer //Buffer to store the data + ); + break; case Analog: { - errorCode = pConn->readIO(theBlock_->getPLC(), usedAddress, //Base address (or DBn) of the block + errorCode = pConn->readAIO(theBlock_->getPLC(), usedAddress, //Base address (or DBn) of the block usedDeviceOffset, //Device data address within the block usedSize, //Get one device-block only pBuffer //Buffer to store the data @@ -151,9 +157,15 @@ int PLCRecvBlockMode::execute(Context* pContext) switch (area) { case Digital: + errorCode = pConn->readDIO(theBlock_->getPLC(), theBlock_->getAddress(), //Base address (or DBn) of the block + 0, //Get all devices from the first one + ((PLCBlock*)theBlock_)->getBufferSize(), //Get blocks of all devices (full buffer size) + (unsigned char*)theBlock_->getBuffer() //Buffer to store the data (full buffer) + ); + break; case Analog: { - errorCode = pConn->readIO(theBlock_->getPLC(), theBlock_->getAddress(), //Base address (or DBn) of the block + errorCode = pConn->readAIO(theBlock_->getPLC(), theBlock_->getAddress(), //Base address (or DBn) of the block 0, //Get all devices from the first one ((PLCBlock*)theBlock_)->getBufferSize(), //Get blocks of all devices (full buffer size) (unsigned char*)theBlock_->getBuffer() //Buffer to store the data (full buffer) @@ -239,9 +251,15 @@ int PLCRecvDeviceMode::execute(Context* pContext) switch (theBlock_->getAccessArea()) { case Digital: + errorCode = pConn->readDIO(theBlock_->getPLC(), usedDeviceAddress, //Base address (or DBn) of the device + usedBlockAddress, //Block data address within the device + usedSize, //Get one device-block only + (unsigned char*)theBlock_->getBuffer() //Buffer to store the data + ); + break; case Analog: { - errorCode = pConn->readIO(theBlock_->getPLC(), usedDeviceAddress, //Base address (or DBn) of the device + errorCode = pConn->readAIO(theBlock_->getPLC(), usedDeviceAddress, //Base address (or DBn) of the device usedBlockAddress, //Block data address within the device usedSize, //Get one device-block only (unsigned char*)theBlock_->getBuffer() //Buffer to store the data @@ -299,14 +317,20 @@ int PLCRecvDeviceMode::execute(Context* pContext) switch (area) { case Digital: + errorCode = pConn->readDIO(theBlock_->getPLC(), pDev->getInputAddress(area), //Base address (or DBn) of the device + theBlock_->getAddress(), //Block data address within the device + theBlock_->getMemSize(), //Get one device-block only + (unsigned char*)theBlock_->getBuffer() //Buffer to store the data + ); + break; case Analog: { - errorCode = pConn->readIO(theBlock_->getPLC(), pDev->getInputAddress(area), //Base address (or DBn) of the device + errorCode = pConn->readAIO(theBlock_->getPLC(), pDev->getInputAddress(area), //Base address (or DBn) of the device theBlock_->getAddress(), //Block data address within the device theBlock_->getMemSize(), //Get one device-block only (unsigned char*)theBlock_->getBuffer() //Buffer to store the data ); - + break; } case Memory: default: diff --git a/silecs-communication-cpp/src/silecs-communication/interface/core/PLCSendAction.cpp b/silecs-communication-cpp/src/silecs-communication/interface/core/PLCSendAction.cpp index b34efdd..1783ab8 100644 --- a/silecs-communication-cpp/src/silecs-communication/interface/core/PLCSendAction.cpp +++ b/silecs-communication-cpp/src/silecs-communication/interface/core/PLCSendAction.cpp @@ -95,9 +95,15 @@ int PLCSendBlockMode::execute(Context* pContext) switch (area) { case Digital: + errorCode = pConn->writeDIO(theBlock_->getPLC(), usedAddress, //Base address (or DBn) of the block + usedDeviceOffset, //Device data address within the block + usedSize, //Set one device-block only + pBuffer //Buffer which contain the data + ); + break; case Analog: { - errorCode = pConn->writeIO(theBlock_->getPLC(), usedAddress, //Base address (or DBn) of the block + errorCode = pConn->writeAIO(theBlock_->getPLC(), usedAddress, //Base address (or DBn) of the block usedDeviceOffset, //Device data address within the block usedSize, //Set one device-block only pBuffer //Buffer which contain the data @@ -153,9 +159,15 @@ int PLCSendBlockMode::execute(Context* pContext) switch (theBlock_->getAccessArea()) { case Digital: + errorCode = pConn->writeDIO(theBlock_->getPLC(), theBlock_->getAddress(), //Base address (or DBn) of the block + 0, //Set all devices from the first one + ((PLCBlock*)theBlock_)->getBufferSize(), //Set blocks of all devices (full buffer size) + (unsigned char*)theBlock_->getBuffer() //Buffer which contain the data (full buffer) + ); + break; case Analog: { - errorCode = pConn->writeIO(theBlock_->getPLC(), theBlock_->getAddress(), //Base address (or DBn) of the block + errorCode = pConn->writeAIO(theBlock_->getPLC(), theBlock_->getAddress(), //Base address (or DBn) of the block 0, //Set all devices from the first one ((PLCBlock*)theBlock_)->getBufferSize(), //Set blocks of all devices (full buffer size) (unsigned char*)theBlock_->getBuffer() //Buffer which contain the data (full buffer) @@ -230,9 +242,15 @@ int PLCSendDeviceMode::execute(Context* pContext) switch (area) { case Digital: + errorCode = pConn->writeDIO(theBlock_->getPLC(), usedDeviceAddress, //Base address (or DBn) of the device + usedBlockAddress, //Block data address within the device + usedSize, //Set one device-block only + (unsigned char*)theBlock_->getBuffer() //Buffer which contain the data + ); + break; case Analog: { - errorCode = pConn->writeIO(theBlock_->getPLC(), usedDeviceAddress, //Base address (or DBn) of the device + errorCode = pConn->writeAIO(theBlock_->getPLC(), usedDeviceAddress, //Base address (or DBn) of the device usedBlockAddress, //Block data address within the device usedSize, //Set one device-block only (unsigned char*)theBlock_->getBuffer() //Buffer which contain the data @@ -285,9 +303,15 @@ int PLCSendDeviceMode::execute(Context* pContext) switch (area) { case Digital: + errorCode = pConn->writeDIO(theBlock_->getPLC(), pDev->getOutputAddress(area), //Base address (or DBn) of the device + theBlock_->getAddress(), //Block data address within the device + theBlock_->getMemSize(), //Set one device-block only + (unsigned char*)theBlock_->getBuffer() //Buffer which contain the data + ); + break; case Analog: { - errorCode = pConn->writeIO(theBlock_->getPLC(), pDev->getOutputAddress(area), //Base address (or DBn) of the device + errorCode = pConn->writeAIO(theBlock_->getPLC(), pDev->getOutputAddress(area), //Base address (or DBn) of the device theBlock_->getAddress(), //Block data address within the device theBlock_->getMemSize(), //Set one device-block only (unsigned char*)theBlock_->getBuffer() //Buffer which contain the data -- GitLab