#ifndef SUBSYSTEM #define SUBSYSTEM #include "PeriodicAction.hxx" #include "TCPListener.hxx" #include "PeriodicAction.hxx" #include "TCPSender.hxx" #define TIMESTAMP_LEN 12 //%02.2d:%02.2d:%02.2d:%03.3d //in Makefile#define MAXMSG 4096 /** * Base class for MAGIC Subsystem's communication with CC * * It implements commandListener (see MAGIC-TDAS 00-07) as a TCPListener * derived class, to be able to custom the behaviour by overriding its * methods * * Missing properties: Special Report in a GenerateSpecialReport method, also * a final implementation of CheckReportAcknowledge (CC sends back * "RECV:timestamp", with timestamp the one extracted from subsystem * report. So subsystem has to have a member lastReportTimestamp to compare). * Timeout in CheckReportAcknowledge?????. * * @short Base class for MAGIC Subsystem's communication with CC * @author Marc Casaldaliga * @version 0.9 * @see TCPListener */ class Subsystem: public TCPListener { public: /** * Set the string to be sent in the next report that will be sent to * CC (with timestamp but without protocol specific item, * e.g. trailing \n). The timestamp is the data one, the time when * data was taken (the relevant for analysis), nothing to do with the * the time the report will be sent. * * It takes care internally of suspending and resuming communications * not to send something is being modified etc * * @param report CString with pure report (without "\n" ...) */ void SetReportString(char * report); /** * Class unique constructor with the parameters which specify * Subsystem configuration. * * @param portCommandListener TCP/IP port Subsystem will open to * listen to cc commands * * @param reportPeriod Aproximate (+- 500ms) time in microsec, * between each report is sent to CC * * @param ccName C string with CC machine name * * @param portReportListener TCP/IP port opened in CC which Subsystem * will contact to send reports * * @param maxTimeoutCount_. Times subsystem tries to reconnect CC * (with reportPeriod) periodicity, before it considers CC not * available. After this count is reached Subsystem has to react some * way (parking itself, ...). This is done by * Subsystem::HandleConnectionTimeoutIsOver * * @param specialReportOnStartup When connection with CC is * stablished for first time after a Subsystem startup a special * report to CC with subsystem setup must be sent to ensure any setup * changes (which may have happened while CC was down) are * recorded. This C string only contains report content without any * protocol dependent character * * @see #HandleConnectionTimeoutIsOver * */ Subsystem (unsigned short int portCommandListener, unsigned long int reportPeriod, char * ccName, unsigned short int portReportListener, unsigned short int maxTimeoutCount_, char * specialReportOnStartup ); /** * Send (immediately) a special report which a part from * protocol specific items (e.g. trailing \n and special report * identifier) contains the argument specialReport * * ??? timestamp ???? * * It takes care internally of suspending and resuming communications * not to send something is being modified etc * * @param specialReport CString with pure special report (without * "\n" ...) * * @return Returns whether sending will be succesful (true) or not * (false) */ bool SendSpecialReportContaining(char * specialReport); /** * Blocks main thread until Subsystem::Shutdown is called */ void WaitingForShutdown(); /** * Subsystem communications will be shutdown definitely. Call this * inside ProcessCmd or HandleConnectionTimeoutIsOver when your * subsystem has to be stopped. If you only want to go standalone but * still in contact with CC, you have to Ulock it but not this. * * This call unblocks WaitingForShutdown in main, so your subsystem * application can finish. */ void Shutdown(); protected: /** * Field with the report that will be sent to CC (with timestamp but * without protocol specific item, e.g. trailing \n) * * To be modified only within GenerateReport */ char reportString[MAXMSG]; /** To be overriden. It has to do all the job of interpreting CC * commands: (not the ones * regarding starting connection, locking ...) but the ones for the * subsystem function (This way we separate the protocol dependent * stuff). * * Compares Subsystem::ccCmd with known commands from cc and executes * the appropiate function * * This method is automatically called when a command from cc is * received, after checking it is not a communication/locking command * * If receiving a nonsense command call to ResetConnectionToCC() * (Network partner it's not -a properly working- CC). In principle * this will close and open again, but let's leave it this way so that * it is not protocol dependent * * @param ccCmd CString with pure CC command (without "\n" ...) */ virtual void ProcessCmd(char *ccCmd); /** GenerateReport (to be overriden) This next method is automatically * called before sending a report. * * According to state and data this should write * Subsystem::reportString (it * should include the timestamp of the data sent, but without any * trailing \n--this is part of the protocol) * * After it, this Subsystem::reportString plus any protocol dependent * thing is sent. This GenerateReport and send is periodically * repeated with reportPeriod * * Alternatively, one may leave GenerateReport empty and from outside * call to Subsystem::SetReportString when hardware info is updated * (probably what one what like to do). */ virtual void GenerateReport(); /** HandleConnectionTimeoutIsOver. Does what the subsystem is suposed * to do when it has lost definitely connection to CC. To be * overriden. * * After Subsystem::maxTimeoutCount times of reconnection tries CC is * considered not available. Subsystem has to react some way (parking * itself, ... ) depending on its autonomy. This is done by * overriding this method with the desired behaviour. * * After HandleConnectionTimeoutIsOver is done Subsystem will listen * to CC again and start all communication process. * * see #maxTimeoutCount */ virtual void HandleConnectionTimeoutIsOver(); /** SuspendComm. Suspend communication threads * * To prevent Subsystem to access its resources (e.g. send * reportString) when we are about to access them externally (fill * reportString with actual data), one can call to this method and * suspend subsystem activity. * To resume it call to Subsystem::ResumeComm. * * Always paired with a ResumeComm. * * If Subsystem is at that time accessing the resources, SuspendComm * will wait for them to be released and then will lock them. * * @see #ResumeComm * */ void SuspendComm(); /** ResumeComm. Resume communication threads * * After calling SuspendComm() and having access to Subsystem * resources (e.g. fill reportString with actual data) allow * Subsystem to access them again and resume it's activity. we are * about to access them externally (fill reportString with actual * data), one can call to this method and suspend subsystem activity. * * @see #SuspendComm * */ void ResumeComm(); /** * Resets communication to CC, to be used when a nonsense command * arrives from CC * @see #ProcessCmd */ void ResetConnectionToCC(); ///////////////////////////////////////////////////////////////////////// //////////////// IMPLEMENTATION DETAILS ///////////////////////////////// ///////////////////////////////////////////////////////////////////////// //we implement commandListener as a TCPListener derived class, to be //able to custom the behaviour by overriding it's methods, namely //process, which is called when something arrives. //This could be though as inheritance for implementation, which is not //desired. Can be avoided by using signals/slots private: /** * C string with CC machine name */ char* ccName; /** * Aproximate (+- 500ms) time in microsec, between each report is sent * to CC. * It is expressed in microsec, despite the big uncertanty in these * units, because is feed directly to usleep. This should be changed at * least to ms: remember then to multiply per 1000 in usleep. Take care * also with long periods: usleep will accept only long int */ unsigned long int reportPeriod; /** * TCP/IP port for commandListener socket, here at subsystem machine */ unsigned short int portCommandListener; /** * TCP/IP port for reportListener socket in CC machine */ unsigned short int portReportListener; /* maxTimeoutCount. Times subsystem tries to reconnect CC (with reportPeriod) * periodicity, before it considers CC not available. * * After this count is reached Subsystem has to react some way * (parking itself, ...). This is done by * Subsystem::HandleConnectionTimeoutIsOver * * @see #HandleConnectionTimeoutIsOver * */ short int maxTimeoutCount; /** * If locked == true Subsystem is being commanded by CC. */ bool locked; /** * When REPOR received, reporting = true, and thread * ReportingAndCheckingLoop is started */ bool reporting; /** * mutex used to prevent that while reporting state is being checked * in ReportingAndCheckingLoop thread, connection closing is detected * in TCPListener thread and reporting is set at the same time. * * I'm not sure if really needed */ pthread_mutex_t mutex4reporting; /** * Accessor function for reporting bool variable that takes care of * properly locking the resource */ inline bool Reporting(){ pthread_mutex_lock(&mutex4reporting); bool ret=reporting; pthread_mutex_unlock(&mutex4reporting); return ret; } /** * Accessor function for reporting bool variable that takes care of * properly locking the resource */ inline void SetReporting(bool trueValue) { pthread_mutex_lock(&mutex4reporting); reporting=trueValue; pthread_mutex_unlock(&mutex4reporting); } /** * We implement reportSender as an instance of class TCPSender */ TCPSender reportSender; /** * mutex used to prevent that while report is being send * in ReportingAndCheckingLoop thread, it is changed at the same time * in the main thread * * This mutex is for sure crucial * * SuspendComm and ResumeComm lock/unlock it to achieve this purpose */ pthread_mutex_t mutex4report; /** * Method that we override from TCPListener and gets called when some * command arrives. Here is where all the LOCK, REPOR protocol is * checked and where ReportingAndCheckingLoop thread is started and * stopped */ virtual void process(); /** * Method that we also override from TCPListener and mainly gets * called when endofline is received (connection closed by the other * partner); we don't call it explicitely when the protocol is * not followed (this is done in ResetConnectionToCC). As in * ResetConnectionToCC we properly * close reportSender, and stop ReportingAndCheckingLoop */ virtual void ClosingChannel(); /** * Method that actually sends the report via reportSender and returns * if it was successful (to be used inside a PeriodicAction or * whatever). * It will also fail if we didn't receive an acknowledge for * last report several (timeoutCount) times, as asynchronously checked * in method CheckReportAcknowledge. * Before actually sending this->reportString it calls the * GenerateReport method that some real subsystems (as Subsystem * derived classes) will override to fill this string. It also calls * ExtractTimeStampFromLastReportTo to be able to check the timestamp * later (in CheckReportAcknowledge) when the acknowledge arrives. */ bool SendReport(); /** * Does the actual check of the report acknowledge (when it arrives) * and puts acknowledgeReceivedForLastReport to true; for synchronizing * the threads its better it does * not do anything else like resetting connection itself. If the * acknowledge doesn't arrive acknowledgeReceivedForLastReport will * remain false. SendReport will know and increase the timeoutCount. */ void CheckReportAcknowledge(); /** * to be able to checkreportacknowledge on has to have the timestamp of * the last report sent */ char lastTimeStamp[TIMESTAMP_LEN]; /** * Extracts this timestamp to its parameter lastTimeStamp */ void ExtractTimeStampFromLastReportTo(char * lastTimeStamp); /** * the thread handle itself for ReportingAndCheckingLoop */ pthread_t reportThread; /** * make up for pthread use. Allows pthread to effectively start a * non-static function, which is very convenient to avoid the use of * self */ inline static void * pthread_ReportingAndCheckingLoop(void* self) { return (void *) ( ((Subsystem* )self)->ReportingAndCheckingLoop() ); } /** * The actual method started in reportThread */ void * ReportingAndCheckingLoop(); bool acknowledgeReceivedForLastReport; short int timeoutCount; void Lock(); void ULock(); }; #endif