Skip to content
Snippets Groups Projects
SilecsConnection.cpp 14.2 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/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())
                {
                    std::ostringstream os;
                    os << "Shared connection with " << thePLC->getName() << " is established.";
                    if (thePLC->theHeader_ != NULL) TRACE("info") << os.str();
                        LOG(COMM) << os.str();
                    std::ostringstream os;
                    os << "Connection with " << thePLC->getName() <<
                            ":" << thePLC->theCluster_->getClassName() <<
                            "/v" << thePLC->theCluster_->getClassVersion() <<
                            " is established.";
                    if (thePLC->theHeader_ != NULL) TRACE("info") << os.str();
                        LOG(COMM) << os.str();
                }

                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())
			  {
                  std::ostringstream os;
                  os << "Shared connection with " << thePLC->getName() << " is closed.";
                  if (thePLC->theHeader_ != NULL) TRACE("warn") << os.str();
                      LOG(COMM) << os.str();
                  std::ostringstream os;
                  os << "Connection with " << thePLC->getName() <<
                      " (" << thePLC->theCluster_->getClassName() <<
                      "/v" << thePLC->theCluster_->getClassVersion() <<
                      ")" << " is closed.";
                  if (thePLC->theHeader_ != NULL) TRACE("warn") << os.str();
                      LOG(COMM) << os.str();
			  }
			}
			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)
            {
                std::ostringstream os;
                os << errorMsg << "Periodic attempt to reconnect, delay " << delayConn_ << "s (tracing off).";
                if (thePLC->theHeader_ != NULL) TRACE("error") << os.str();
                LOG(COMM) << os.str();
                remainConn_ = 1; //Try to reconnect again and again (=1 means disable tracing).
            }
            /*else
              PLC does not respond anymore. It's probably stopped for a long time.
              Do not log error anymore (but still try to reconnect, with long-term delay).
            */
        else
        {
            std::ostringstream os;
            os << errorMsg << "Next attempt to reconnect in " << delayConn_ << "s if requested. Remains: " << remainConn_;
            if (thePLC->theHeader_ != NULL) TRACE("error") << os.str();
            LOG(COMM) << os.str();
        }
	}

	//------------------------------------------------------------------------------------------------------------------------------------------------
	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