Skip to content
Snippets Groups Projects
SilecsConnection.cpp 11.7 KiB
Newer Older
// 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/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::isInit_ = false;
	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

		// Silecs initialization has to be invoked only once per process
		if (isInit_== false)
		{	IeInit();
			isInit_ = true;
		}
	}


	//------------------------------------------------------------------------------------------------------------------------------------------------
	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 ...";
		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())
			{
				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
				}
				if(!isOpen)
				{
					logError(thePLC);
					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))
			{
			  readCh_ = writeCh_ = 0;
			  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::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)
	{
	    std::string errorMsg = "Connection with " + thePLC->getName() +
							   ":" + thePLC->theCluster_->getClassName() +
								"/v" + thePLC->theCluster_->getClassVersion() +
								" has failed. ";

		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