// 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/>. #ifndef _SILECS_CONNECTION_H_ #define _SILECS_CONNECTION_H_ #include <silecs-communication/interface/core/SilecsService.h> #include <silecs-communication/interface/utility/Mutex.h> #include <silecs-communication/interface/utility/TimeStamp.h> #include <silecs-communication/protocol/core/silecs.h> namespace Silecs { class PLC; // Time to wait (second) before next reconnection attempt typedef enum { UrgentConnection=2, ShortTermConnection=20, LongTermConnection=60 } ConnectionDelay; #ifdef __x86_64__ typedef long ChannelID; //pointer is 64bits word #else typedef int ChannelID; //pointer is 32bits word #endif class Lock { public: Lock(Mutex* mutex): mutex_(mutex) { mutex_->lock(); } ~Lock() { mutex_->unlock(); } Mutex* mutex_; }; class Connection { public: Connection(PLC* thePLC); virtual ~Connection(); virtual int readData(PLC* thePLC, unsigned long address, unsigned long offset, unsigned long size, unsigned char* buffer) = 0; virtual int writeData(PLC* thePLC, unsigned 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. * enable/disable methods are used from the high-level Cluster API: connect/disconnect. * \param connectNow can be used to force immediate connection (true) */ virtual bool enable(PLC* thePLC, bool connectNow); void disable(PLC* thePLC); bool isEnabled(); static inline bool isAlive() { return isAlive_; } bool isConnected(); virtual int readUnitCode(PLC* thePLC, UnitCodeType& dataStruct); virtual int readUnitStatus(PLC* thePLC, UnitStatusType& dataStruct); virtual int readCPUInfo(PLC* thePLC, CPUInfoType& dataStruct); virtual int readCPInfo(PLC* thePLC, CPInfoType& dataStruct); // true if the "recvUnitStatus" is RUN, false otherwise. Throws exception on failure virtual bool isRunning(PLC* thePLC); //SET PLC COLD RESTART virtual int coldRestart(PLC* thePLC); virtual int plcStop(PLC* thePLC); protected: friend class Cluster; friend class PLC; friend class CNVRecvDeviceMode; friend class CNVSendDeviceMode; /*! * \fn doOpen * \brief This method check the current state of the connection and open it if required. * doOpen that is called on each transaction is reponsible of the automatic reconnection mechanism. */ virtual bool doOpen(PLC* thePLC); void doClose(PLC* thePLC, bool withLock); bool reOpen(PLC* thePLC); virtual bool open(PLC* thePLC) = 0; //open the connection for a particular PLC brand virtual bool close(PLC* thePLC) = 0; //close the connection for a particular PLC brand /*! * \fn checkError * \brief This method attempts to reconnect the PLC in case of "expected" transaction failure. * If the returned value is true the request must be repeated. * The retry flag defines whether the transaction will be repeated or not * IE_CONNECT_ERROR - TCP/IP connect() failed: IeOpen() (PLC is off or the max. number of open connection is reached) * Retry required: * IE_DISCONNECT_ERROR - Explicit/unexpected disconnect request (when the connection has been unactive for some time) * IE_EPIPE_ERROR - TCP/IP send/recv failed (when one of the channel (get/set) has been unactive for some time or PLC is off) * IE_TIMEOUT_ERROR - PLC is connected but does not reply (In mean time the PLC gets off or data is lost) */ virtual bool checkError(PLC* thePLC, int err, bool retry); /*! * \fn updatePLCStatus * \brief Responsible to update the diagnostic variables each time PLC states have changed */ void updateStatus(PLC* thePLC); void logError(PLC* thePLC); /*! * \fn isTimeToReconnect * \brief In order to not overload the network the reconnection attempts are gradually slowed * down from second scale to several minutes. This method checks the elapsed time since connection. * \return true if it's time to try to reconnect (time unit is second). */ bool isTimeToReconnect(); // flag used to enable/disable the transactions independently from the scheduling bool isEnabled_; /* . read-channel and write channel are independent and can be accessed in parallel. * . Each channel must be protected against respective concurrent access. * . The global action (open,close,etc.) must be protected from any concurrent access. */ Mutex* readMux_; //Mutex used to protect the PLC read-channel resource Mutex* writeMux_; //Mutex used to protect the PLC write-channel resource Mutex* connMux_; //Mutex used to protect the global PLC connection resource (for open/close, etc.) // Time counter to manage the automatic reconnection TsCounter* timeConn_; ConnectionDelay delayConn_; //current delay between two connection unsigned long remainConn_; //number of attempt before next slow-down static const unsigned int numberConn_; //number of connection attempt for each connection delay // Communication Diagnostic & Monitoring static bool isAlive_; // PLC has repliyed to the ping: true/false bool isConnected_; // State of this particular connection: FEC/PLC/Class // not copyable object Connection(const Connection&); Connection& operator=(const Connection&); }; } // end namespace #endif // _SILECS_CONNECTION_H_