/* Copyright (c) 2017 European Organization for Nuclear Research (CERN). All rights reserved. This program and the accompanying materials are made available under the terms of the GNU Public License v3.0 which accompanies this distribution, and is available at http://www.gnu.org/licenses/gpl.html Contributors: .European Organization for Nuclear Research (CERN) - initial API and implementation .GSI Helmholtzzentrum für Schwerionenforschung (GSI) - features and bugfixes */ #include <silecs-communication/interface/core/SilecsService.h> #include <silecs-communication/interface/utility/XMLParser.h> #include <silecs-communication/interface/equipment/SilecsPLC.h> #include <silecs-communication/interface/equipment/SilecsBlock.h> #include <silecs-communication/interface/equipment/SilecsRegister.h> #include <silecs-communication/interface/core/SilecsAction.h> #include <silecs-communication/interface/utility/SilecsException.h> #include <silecs-communication/interface/utility/SilecsLog.h> #include <silecs-communication/interface/utility/StringUtilities.h> #include <silecs-communication/interface/equipment/PLCBlock.h> #include <silecs-communication/interface/communication/SilecsConnection.h> #include <silecs-communication/interface/equipment/PLCRegister.h> namespace Silecs { PLCBlock::PLCBlock(const ElementXML& blockNode, long deviceInputAddress, long deviceOutputAddress, Connection& plcConnection_, const SilecsParamConfig& paramConfig) : Block(blockNode, plcConnection_), deviceInputAddress_(deviceInputAddress), deviceOutputAddress_(deviceOutputAddress), paramConfig_(paramConfig) { auto& registerNodes = blockNode.getChildNodes(); for (auto registerIter = registerNodes.begin(); registerIter != registerNodes.end(); registerIter++) { std::string registerName = registerIter->getAttribute("name"); std::unique_ptr<Register> reg; switch (paramConfig.brandID) { // SIEMENS PLCs use Motorola memory convention (BigEndianRegister) case Siemens: reg = std::unique_ptr<Register>(new S7Register(*registerIter, paramConfig)); break; // SCHNEIDER PLCs use Intel memory convention (LittleEndianRegister) // RABBIT microcontrollers use Intel memory convention (LittleEndianRegister) case Schneider: case Digi: reg = std::unique_ptr<Register>(new UnityRegister(*registerIter, paramConfig)); break; // BECKHOFF PLCs use different memory convention depending on PLC type: // . CX model: same convention than Unity // . BC model: special convention for 64 bits data (float, ..): swap only the two 16bits words together (no bytes swap). case Beckhoff: reg = std::unique_ptr<Register>(new TwinCATRegister(*registerIter, paramConfig)); break; default: throw SilecsException(__FILE__, __LINE__, DATA_UNKNOWN_PLC_MANUFACTURER, paramConfig.brand); } AccessArea accessArea = AccessArea::Memory; if (blockNode.hasAttribute("ioType")) // only IO-Blocks have this attribute { accessArea = Block::whichAccessArea(blockNode.getAttribute("ioType")); } reg->setAccessArea(accessArea); reg->setAccessType(Block::whichAccessType(blockNode.getName())); reg->setBlockName(blockNode.getAttribute("name")); registers_.emplace_back(std::move(reg)); } // Create buffer for the block exchanges bufferSize_ = memSize_; //size of one block type (including alignements) pBuffer_ = (unsigned char*)calloc(bufferSize_, sizeof(unsigned char)); } PLCBlock::~PLCBlock() { //Remove the buffer if (pBuffer_ != NULL) free(pBuffer_); pBuffer_ = NULL; } int PLCBlock::recvBlockMode() { unsigned long usedAddress = address_; unsigned long usedSize = memSize_; unsigned long usedDeviceOffset = deviceInputAddress_ * usedSize; // Overwrite device-block address, offset & size in case user wants to resize the block dynamically if (withCustomAttributes() == true) { usedAddress = customAddress_; usedSize = customSize_; usedDeviceOffset = customOffset_; } return readBlock(usedAddress, usedDeviceOffset, usedSize, (unsigned char*)pBuffer_); } int PLCBlock::recvDeviceMode() { unsigned long usedDeviceAddress = deviceInputAddress_; unsigned long usedBlockAddress = address_; unsigned long usedSize = memSize_; // Overwrite device-block address, offset & size in case user wants to resize the block dynamically if (withCustomAttributes() == true) { usedDeviceAddress = customAddress_; usedBlockAddress = customOffset_; usedSize = customSize_; } return readBlock(usedDeviceAddress, usedBlockAddress, usedSize, (unsigned char*)pBuffer_); } int PLCBlock::sendBlockMode() { unsigned long usedAddress = address_; unsigned long usedSize = memSize_; unsigned long usedDeviceOffset = deviceOutputAddress_ * usedSize; // Overwrite device-block address, offset & size in case user wants to resize the block dynamically if (withCustomAttributes() == true) { usedAddress = customAddress_; usedSize = customSize_; usedDeviceOffset = customOffset_; } return sendBlock(usedAddress, usedDeviceOffset, usedSize, (unsigned char*)pBuffer_); } int PLCBlock::sendDeviceMode() { unsigned long usedDeviceAddress = deviceOutputAddress_; unsigned long usedBlockAddress = address_; unsigned long usedSize = memSize_; // Overwrite device-block address, offset & size in case user wants to resize the block dynamically if (withCustomAttributes() == true) { usedDeviceAddress = customAddress_; usedBlockAddress = customOffset_; usedSize = customSize_; } return sendBlock(usedDeviceAddress, usedBlockAddress, usedSize, (unsigned char*)pBuffer_); } int PLCBlock::readBlock(long address, unsigned long offset, unsigned long size, unsigned char* pBuffer) { timeval tod; //Time-of-day for register time-stamping int errorCode = 0; try { // Try to open the connection, if it fails. Throw an exception. On success all the // PLC blocks will be updated this function will be called recursively within doOpen. // Once all blocks are updated continue from here. if (!plcConnection_.doOpen()) { throw SilecsException{__FILE__, __LINE__, COMM_CONNECT_FAILURE}; } switch (accessArea_) { case AccessArea::Digital: errorCode = plcConnection_.readDIO(address, offset, size, pBuffer); break; case AccessArea::Analog: { errorCode = plcConnection_.readAIO(address, offset, size, pBuffer); break; } case AccessArea::Memory: default: { errorCode = plcConnection_.readMemory(address, offset, size, pBuffer); break; } } if (plcConnection_.isConnected() && (errorCode == 0)) { //Data have just been received: get time-of-day to time-stamp the registers gettimeofday(&tod, 0); importRegisters(pBuffer, tod); LOG_DELAY(RECV) << "done for block: " << name_; } } catch(const SilecsException& ex) { LOG(ERROR) << ex.what(); LOG(ERROR) << "RecvAction (execute) for block " << name_ << " has failed."; return ex.getCode(); } return errorCode; } int PLCBlock::sendBlock(long address, unsigned long offset, unsigned long size, unsigned char* pBuffer) { int errorCode = 0; try { // Try to open the connection, if it fails. Throw an exception. On success all the // PLC blocks will be updated this function will be called recursively within doOpen. // Once all blocks are updated continue from here. if (!plcConnection_.doOpen()) { throw SilecsException{__FILE__, __LINE__, COMM_CONNECT_FAILURE}; } if (plcConnection_.isEnabled()) { exportRegisters(pBuffer); } switch (accessArea_) { case AccessArea::Digital: errorCode = plcConnection_.writeDIO(address, offset, size, pBuffer); break; case AccessArea::Analog: { errorCode = plcConnection_.writeAIO(address, offset, size, pBuffer); break; } case AccessArea::Memory: default: errorCode = plcConnection_.writeMemory(address, offset, size, pBuffer); break; } if (SEND & Log::topics_) { if (plcConnection_.isEnabled()) { Log(SEND).getLogDelay() << "done for block: " << name_; } } } catch(const SilecsException& ex) { LOG(ERROR) << ex.what(); LOG(ERROR) << "SendAction (execute) failed"; return ex.getCode(); } return errorCode; } int PLCBlock::send() { if (accessType_ == AccessType::Acquisition) { throw SilecsException(__FILE__, __LINE__, DATA_WRITE_ACCESS_TYPE_MISMATCH); } if (paramConfig_.protocolModeID == BlockMode) { return sendBlockMode(); } else { return sendDeviceMode(); } } int PLCBlock::receive() { if (accessType_ == AccessType::Command) { throw SilecsException(__FILE__, __LINE__, DATA_READ_ACCESS_TYPE_MISMATCH); } if (paramConfig_.protocolModeID == BlockMode) { return recvBlockMode(); } else { return recvDeviceMode(); } } }