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