Classes to help you write clients

Top  Previous  Next

A key concept in C++ is code re-usability. Since I've written all the code needed to create a client task, you shouldn't have to. All you need to do is to use the classes that I created. A class represents an object in a very general sense; in this case, a behavioural task. The main class designed to help you is called CWhiskerTask, and here are the functions that it offers you:

 

This list is incomplete. Examine WHISKERTASK.H for the full catalogue.

 

class CWhiskerTask

{

      typedef void (CWhiskerTask::*WhiskerFuncPtr)(const CString&,  long); // the type of a callback function

public:

       CWhiskerTask();

       virtual ~CWhiskerTask();

 

       // *********************************************

       // Event Callbacks

       // *********************************************

       // The client is expected to override some or all of these

 

       virtual void ServerConnected(); // main connection has just been made

       virtual void ServerFullyConnected(); // main and immediate sockets are both connected

       virtual void ServerDisconnected(); // Called when communication is lost

 

       virtual void IncomingInfo(CString msg, long lTime); // an Info message has arrived

       virtual void IncomingError(CString strError, long lTime); // an Error message has arrived

       virtual void IncomingEvent(CString strEvent, long lTime); // an Event message has arrived

       virtual void IncomingClientMessage(int iSourceClient, CString strMessage, long lTime); // a message from another client has arrived

       virtual void IncomingOther(CString strMessage, long lTime); // some other kind of message has arrived

       virtual void IncomingWarning(CString strMessage, long lTime);

       virtual void IncomingSyntaxError(CString strMessage, long lTime);

       virtual void IncomingKeyEvent(int iKey, bool bDown, CString strDocument, long lTime);

       

       // Callbacks Without timestamps.

       // Override these if you don't want to handle both

       // (these will only be called if the stamped versions are not overriden)

 

       virtual void IncomingInfo(CString msg); // an Info message has arrived

       virtual void IncomingError(CString strError); // an Error message has arrived

       virtual void IncomingEvent(CString strEvent); // an Event message has arrived

       virtual void IncomingClientMessage(int iSourceClient, CString strMessage); // a message from another client has arrived

       virtual void IncomingOther(CString strMessage); // some other kind of message has arrived

       virtual void IncomingWarning(CString strMessage);

       virtual void IncomingSyntaxError(CString strMessage);

       virtual void IncomingKeyEvent(int iKey, bool bDown, CString strDocument);

 

       virtual bool PollPipedEvents(const CString& msg, long lTime); // override to 'pipe' your own events: return true to prevent them from getting to IncomingEvent()

       virtual void StatusMessage(const CString& msg); // Override to handle status messages from this library

 

 

       // *********************************************

       // Whisker Command Wrapper methods

       // *********************************************

 

       // Connecting & Disconnecting

       bool Initialise(const CString& strServerName, int iPort, const CString& strBox = "");

       void DisconnectFromServer(); // closes all sockets

       void ShutDown(); // releases timers/events/lines and closes

       bool IsConnected();

       bool IsImmediateConnected();

 

       // Controlling Devices

       bool ClaimGroup(const CString& strDeviceGroup, const CString& strPrefix = "", const CString& strSuffix = "");

       

       // Timer devices

       bool TimerSetEvent(const CString& strEvent, unsigned long timeInMs, int iRepetitions = 0); // request a timer event from the server

       void TimerClearEvent(const CString& strTimer);

       void TimerClearAllEvents();

       

       // Audio devices

       bool AudioClaim(int iDevice, const CString& strAlias = "");

       

       

       // Line Devices

       bool LineSetState(const CString& strLine, bool bState);

       bool LineReadState(const CString& strLine, bool& bState);

       ...

 

       // Display Devices

       bool DisplayClaim(int iDevice, const CString& strAlias = "");

       ...

 

       // Other multimedia

       bool SetMediaDirectory(const CString& strDirectory);

 

       // Client-client comms

       bool PermitClientMessages(bool bPermit = true);

       bool SendToClient(int iClientNum, CString strMsg);

 

       // Other Whisker Commands

       void ReportName(const CString& strName);

       void ReportStatus(const CString& strName);

       bool TimeStamps(bool bTimeStamps);

       void ResetClock();

       long RequestTime();

       CString ServerVersion();

 

 

       // *********************************************

       // Whisker Helper methods (not part of the Whisker Command set)

       // *********************************************

       CString ThisComputerName(); // returns the IP name of this host

       int ClientNumber(); // returns the client number if connected, -1 if not.

 

       // alternative command names...

       bool SwitchOn(const CString& strLine){return LineSetState(strLine,true);}

       bool SwitchOff(const CString& strLine){return LineSetState(strLine,false);}

 

       void ReplyToEvent(const CString& strEvent, const CString& strReply, bool bTransparent = false);

       void KillReply(const CString&, const CString&);

 

       // Calculation functions

       static DWORD Minutes_ms(int minutes) {return minutes*60*1000;}; // converts minutes to ms

       static DWORD Minutes_ms(float minutes) {return minutes*60*1000;}; // converts minutes to ms

       static DWORD Seconds_ms(int seconds) {return seconds*1000;}; // converts seconds to ms

       static DWORD Seconds_ms(float seconds) {return seconds*1000;}; // converts seconds to ms

 

       // Flashing & Pulsing Lines

       void FlashLine_Pulses(const CString& strLine, int iCount, DWORD on_time, DWORD off_time, bool bOnAtRest = false); // flashes the line iCount times

       void FlashLine_Duration(const CString& strLine, DWORD duration, DWORD on_time, DWORD off_time, bool bOnAtRest = false); // flashes the line for "duration" ms

       CString StartFlashLine(const CString& strLine, DWORD on_time, DWORD off_time, bool bOnAtRest = false); // starts the line going; returns its event label

       void StopFlashLine(const CString& strLine, const CString& strEventLabel, bool bOnAtRest = false); // stops the flashing

 

       // Timing

       bool SendAfterDelay(const CString& strMsg, DWORD delay, const CString& strEventLabel = ""); // send a message after a given delay

       DWORD CurrentTime(); // return system time (ms)

       int TestNetLatency(); // return ms delay for Server->Client->Server messageloop

 

       // Useful string manipulation functions

       static bool GetPrefix(const CString& msg, CString& prefix, CString& remainder); // IN: msg from server. OUT: message prefix and remainder

       static int GetIntegerParam(CString &strSentence); // chops first word out of sentence and converts it to a number

       static void GetNextWord(CString &strWord, CString &strSentence); // chops first word out of strSentence, stores in in strWord

       static CString StringWithQuotes(const CString &string){return "\"" + string + "\"";}; //puts quotes round the string

       CString UniqueString(); // returns a unique string

 

       // Probability functions

       float RandomProbability(); // returns random float, 0-1

       int RandomInt(int max); // returns a random integer, 0-max

       int RandomInt(int min, int max); // returns a random integer, min-max

       bool Chance(float p); // returns true with probability p

       void SeedRandomNumberGenerator(); // seeds random number generator with system clock

       

// Advanced Communications:

 

       // To Server:

       // The client does not need these, for any of the current Whisker Command set, but

       // can be used if the user wants to talk directly to server.

 

       bool Send(const CString &msg); // sends msg to server

       void ImmediateVoid(const CString &msg); // sends msg to immediate socket, ignores reply

       bool ImmediateBoolean(const CString& question); // sends question on immsocket, returns success/failure

       bool ImmediateSocketQuery(const CString& out, CString& in); // sends "out" on immsocket, puts reply in "in"

       CString ImmediateSocketQuery(const CString& question); // sends "question" on immsocket, returns reply

       CString ImmediateSocketQuery(const CString& question, long& Timestamp); // sends "question" on immsocket, returns reply with timing info

       bool RemoveTimestamp(CString& msg, long& Timestamp); // removes timestamp (if any) from reply & rets value

 

       // From Server

       // Any override must call the baseclass version in order to use the

       // library correctly (Immediate Socket connection & callbacks & library event scheduling)

 

       virtual void IncomingMessage(CString strRecvd); // handles raw incoming data from socket

       virtual void Parse(CString& msg); // handles CR/semicolon-stripped messages; also deals with immsocket connection and pinging

 

       //conversions to string

       static        CString OnOffString(bool bOn);

       static        CString DisplayPenOptionsToString(LOGPEN* pOptions);

       static        CString DisplayBrushOptionsToString(WHISKERBRUSHOPTIONS* pOptions);

 

 

I'm not going to go through all of these functions in detail. You have the library source code, which is commented, and you have several examples of clients that use the library (SimpleCPPClient, WhiskerStatus). If you know C++, that should be enough to get you going. Remember, the virtual functions are the ones you might override; the others are functions you call actively.

 

Note that CWhiskerTask provides a whole host of useful functions. Try looking there before you write a routine yourself.

 

Technical note: callback function parameters

TechNote_MagnifyingGlass

Several of the library functions use callback functions. If I was writing 'pure' code, I'd probably have made them of type CObject* (since the library is written using MFC) and use run-time type information (RTTI) to establish what parameters were actually coming into a particular callback function. This would allow a pointer to any CObject-derived class to be passed.

 

Instead, I made the parameter of type const CString& (that is, a reference to a constant [immutable] CString). This necessitates using CString::Format to pack parameters into a string, and there are some custom routines in the library to extract them.

 

It's not as neat; why do it? Basically, I felt it was too optimistic to expect users to manage explicit casts and RTTI checking, even though it makes for poorer code. Everyone understands strings.

 

Technical note: implementing your own callback functions

TechNote_MagnifyingGlass

For technical reasons, you have to do a little bit of work as the client here. These instructions assume your client task (CMyTask) is derived from CWhiskerTask.

 

You should include this snippet in your class definition:

 

class CMyTask : public CWhiskerTask

{

   // ...

   CPipedEventList<CMyTask> m_PipedEvents;

   bool PollPipedEvents(CString msg) { return m_PipedEvents.Poll(); }

   // ...

}

 

Then, to use the code, your callback function must take a single const CString& parameter, and return void. Then you can do this (the example is to deliver a single pellet):

 

void CMyTask::PelletOn()

{

   Send("LineSetState PELLET on");

   Send("TimerSetEvent 40 0 _pellet_dispensed");

   m_PipedEvents.AddEvent("_pellet_dispensed", this, &CMyTask::PelletOff, "hey there", PIPED_EVENT_NUMBER, 1);

}

 

void CMyTask::PelletOff(const CString& message)

{

   // message will contain "hey there",

   // but it could contain something useful!

   Send("LineSetState PELLET off");

}

       

This code is unnecessary as you could just have used SendAfterDelay("LineSetState PELLET off", 40), but it illustrates the principles.