// 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 <silecs-communication/interface/equipment/SilecsCluster.h> #include <silecs-communication/interface/utility/SilecsLog.h> #include <silecs-communication/interface/communication/SilecsConnection.h> #include <silecs-communication/interface/equipment/SilecsPLC.h> #include <silecs-communication/protocol/core/silecs.h> namespace Silecs { // static definition bool Connection::isAlive_ = false; const unsigned int Connection::numberConn_ = 3; //------------------------------------------------------------------------------------------------------------------------------------------------ Connection::Connection(PLC* thePLC) { // Start the PLC Thread readMux_ = new Mutex("readMux"); writeMux_ = new Mutex("writeMux"); connMux_ = new Mutex("connMux"); //Not yet allowed, not yet connected! isEnabled_ = false; isConnected_ = false; //Reset the Reconnection mechanism timeConn_ = NULL; delayConn_ = UrgentConnection; //initial reconnection delay remainConn_ = numberConn_; //initial number of attempt } //------------------------------------------------------------------------------------------------------------------------------------------------ Connection::~Connection() { //Connection has been closed before from the concrete connection object if (timeConn_ != NULL) delete timeConn_; delete connMux_; delete writeMux_; delete readMux_; } //------------------------------------------------------------------------------------------------------------------------------------------------ bool Connection::enable(PLC* thePLC, bool connectNow) { { Lock lock(connMux_); isEnabled_ = true; } if (connectNow) doOpen(thePLC); return isConnected_; } //------------------------------------------------------------------------------------------------------------------------------------------------ void Connection::disable(PLC* thePLC) { if (!isEnabled_) { LOG(DEBUG) << "Trying to disconnect a PLC that is not enabled: " << thePLC->getName(); return; } Lock lock(connMux_); { Lock lock(readMux_); { Lock lock(writeMux_); isEnabled_ = false; doClose(thePLC, /*withLock =*/false); } } } //------------------------------------------------------------------------------------------------------------------------------------------------ bool Connection::doOpen(PLC* thePLC) { //LOG((COMM|DIAG)) << "Start attempt to open connection ..."; .. commented, seem like this creates to many log-entries for KIBANA bool isReachable = false; bool isOpen = false; bool justConnected = false; { Lock lock(connMux_); if (isConnected_) { if (!isEnabled_) { LOG((COMM|DIAG)) << "The PLC is connected but the client wants to disable the transactions"; doClose(thePLC, /*withLock =*/false); } return isConnected_; } if (!isEnabled_) { LOG((COMM|DIAG)) << "The communication currently is not enabled"; return isConnected_; } if (isTimeToReconnect()) { if (isReachable = (IeRfcPing((char *) thePLC->getName().c_str(), NULL) == 0)) { LOG((COMM|DIAG)) << "It's time to reconnect"; // It's time to open the connection according to the (re)connection timing // Let's try several times with limited delay (some ms). // It allows wake-up frozen PLC (SIEMENS in particular) after long stop period. bool isOpen = false; unsigned int nbConn = 2; //for fast low-level iteration for(unsigned int i = 0; i<nbConn; i++) { LOG((COMM|DIAG)) << "Attempt to open PLC connection ...."; isOpen = open(thePLC); if(isOpen) { LOG((COMM|DIAG)) << "Connection opened successfully"; break; } usleep(100000); // wait 100ms } }//pingable? if(!isOpen) { logError(thePLC,isReachable); return isConnected_; } if (thePLC->isSharedConnection()) { LOG((COMM|DIAG)) << "Shared connection with " << thePLC->getName() << " is established."; } else { LOG((COMM|DIAG)) << "Connection with " << thePLC->getName() << ":" << thePLC->theCluster_->getClassName() << "/v" << thePLC->theCluster_->getClassVersion() << " is established."; } isAlive_ = true; isConnected_ = true; justConnected = true; //retentive registers synchronization is required! //Connection status has changed: update the diagnostic variables LOG((COMM|DIAG)) << "Updating PLC status"; updateStatus(thePLC); } }//release lock /* Process the Retentive registers synchronization each time the PLC is just (re)connected. * This is a recursive call: performs task::execute method from doOpen that is * called into execute itself. The recursion is terminated when SilecsHeader connection is closed finally. */ if (justConnected) { LOG((COMM|DIAG)) << "First connection - performing registers synchronization"; thePLC->updateLocalData(); } LOG((COMM|DIAG)) << "isConnected_:" << isConnected_; return isConnected_; } //------------------------------------------------------------------------------------------------------------------------------------------------ void Connection::doClose(PLC* thePLC, bool withLock) { //This process that can be called inside and outside protected section. //withLock argument is used to avoid Recursive mutex that is not supported //by LynxOS platform. if(withLock) Lock lock(connMux_); if (isConnected_) { if (close(thePLC)) { isConnected_ = false; //Connection status has changed: update the diagnostic variables updateStatus(thePLC); if (thePLC->isSharedConnection()) { LOG((COMM|DIAG)) << "Shared connection with " << thePLC->getName() << " is closed."; } else { LOG((COMM|DIAG)) << "Connection with " << thePLC->getName() << " (" << thePLC->theCluster_->getClassName() << "/v" << thePLC->theCluster_->getClassVersion() << ")" << " is closed."; } } else { LOG(COMM) << "Close connection with " << thePLC->getName() << " (" << thePLC->theCluster_->getClassName() << "/v" << thePLC->theCluster_->getClassVersion() << ")" << " has failed."; } } } //------------------------------------------------------------------------------------------------------------------------------------------------ bool Connection::reOpen(PLC* thePLC) { if (IeRfcPing((char *) thePLC->getName().c_str(), 0) == 0) { doClose(thePLC, /*withLock =*/true); return doOpen(thePLC); } isAlive_ = false; return false; } bool Connection::isEnabled() { return isEnabled_; } bool Connection::isConnected() { return isConnected_; } //------------------------------------------------------------------------------------------------------------------- int Connection::readUnitCode(PLC* thePLC, UnitCodeType& dataStruct) { throw SilecsException(__FILE__, __LINE__, DIAG_PLC_REPORT_NOT_SUPPORTED, thePLC->getName()); return -1; } int Connection::readUnitStatus(PLC* thePLC, UnitStatusType& dataStruct) { throw SilecsException(__FILE__, __LINE__, DIAG_PLC_REPORT_NOT_SUPPORTED, thePLC->getName()); return -1; } int Connection::readCPUInfo(PLC* thePLC, CPUInfoType& dataStruct) { throw SilecsException(__FILE__, __LINE__, DIAG_PLC_REPORT_NOT_SUPPORTED, thePLC->getName()); return -1; } int Connection::readCPInfo(PLC* thePLC, CPInfoType& dataStruct) { throw SilecsException(__FILE__, __LINE__, DIAG_PLC_REPORT_NOT_SUPPORTED, thePLC->getName()); return -1; } bool Connection::isRunning(PLC* thePLC) { throw SilecsException(__FILE__, __LINE__, DIAG_PLC_REPORT_NOT_SUPPORTED, thePLC->getName()); return -1; } int Connection::coldRestart(PLC* thePLC) { throw SilecsException(__FILE__, __LINE__, DIAG_PLC_REPORT_NOT_SUPPORTED, thePLC->getName()); return -1; } //PERFORM COLD RESTART int Connection::plcStop(PLC* thePLC) { throw SilecsException(__FILE__, __LINE__, DIAG_PLC_REPORT_NOT_SUPPORTED, thePLC->getName()); return -1; } //------------------------------------------------------------------------------------------------------------------- bool Connection::checkError(PLC* thePLC, int err, bool retry) { if (err < 0) { LOG(COMM) << "Transaction failure with PLC: " << thePLC->getName() << ". SILECS[" << err << "]: " << IeGetErrorMessage(err); switch(err) { case IE_TIMEOUT_ERROR: case IE_EPIPE_ERROR: case IE_DISCONNECT_ERROR: if (retry) { 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(); } // else { // no retry, we just want to close (use default case) default: doClose(thePLC, /*withLock =*/true); } } return false; // no particular error } //------------------------------------------------------------------------------------------------------------------------------------------------ void Connection::updateStatus(PLC* thePLC) { //Connection status has changed (opened/closed) //Update the PLC diagnostic variables thePLC->updateStatus(); //Reset reconnection mecanisme in case of connection succeed if (isConnected_) { delayConn_ = UrgentConnection; //initial reconnection delay remainConn_ = numberConn_; //initial number of attempt } } //------------------------------------------------------------------------------------------------------------------------------------------------ void Connection::logError(PLC* thePLC, bool isReachable) { std::string errorMsg = isReachable ? "Connection with " + thePLC->getName() + ":" + thePLC->theCluster_->getClassName() + "/v" + thePLC->theCluster_->getClassVersion() + " has failed.\n" : "Controller " + thePLC->getName() + " does not respond to ping, might be OFF!\n"; if (delayConn_ == LongTermConnection) { if (remainConn_ > 0) { LOG((COMM|DIAG)) << errorMsg << "Periodic attempt to reconnect, delay " << delayConn_ << "s (logging off)."; remainConn_ = 1; //Try to reconnect again and again (=1 means disable logging). } else { LOG((COMM|DIAG)) << errorMsg << "PLC does not respond anymore. It's probably stopped for a long time. Trying to reconnect with long-term delay"; } } else LOG((COMM|DIAG)) << errorMsg << "Next attempt to reconnect in " << delayConn_ << "s if requested. Remains: " << remainConn_; } //------------------------------------------------------------------------------------------------------------------------------------------------ bool Connection::isTimeToReconnect() { bool toBeReconnected = false; if (timeConn_ != NULL) { //how many time from the last connect attempt double delay = timeConn_->getDelay(S_UNIT); if (delay >= double(delayConn_)) { timeConn_->getValue(S_UNIT); //restart delay counting from now toBeReconnected = true; if (remainConn_ > 0) --remainConn_; if (remainConn_ == 0) { if (delayConn_ != LongTermConnection) { if (delayConn_ == UrgentConnection) delayConn_ = ShortTermConnection; else //(delayConn_ == ShortTermConnection) delayConn_ = LongTermConnection; remainConn_ = numberConn_; } } } } else { //This is the first connection attempt, just start the Time counter. timeConn_ = new TsCounter(true); //using hardware clock timeConn_->getValue(S_UNIT); //start delay counting from now toBeReconnected = true; } return toBeReconnected; } } // end namespace