/MEng/System/CQC/Runtime/CQCDriverBase

Class Information:

ClassPath:MEng.System.CQC.Runtime.CQCDriverBase
Parent ClassPath:MEng.Object
Copyable:No
Final:No

MEng.System.CQC.Runtime.CQCDriverBase is the base class form which all device drivers are derived. It provides the abstract interface via which the CQCServer framework can manage all CML based drivers abstractly.

Note that you should have read through the Device Driver Development guide before attempting to use this class.

 

Nested Types:

Enum=CQCDrvErrors
    AlreadyConfigured : "It is too late to change the driver configuration";
    BadFldType        : "The field was accessed as a type different from ...
    ConvertErr        : "The value written to field %(1) could not be ....
    CppExcept         : "An unhandled CIDLib C++ exception occurred";
    FldNotFound       : "The named field was not found";
    FldWriteErr       : "";
    GetConfigStr      : "";
    LimitViolation    : "Value '%(1)' exceeds the limits for field %(2)";
    NotConnected      : "You cannot get bytes until in connected state";
    NoOverride        : "%(1) method was not implemented by the driver";
    UnknownConnType   : "%(1) is not a valid source connection type";
    UnknownExcept     : "An unhandled unknown C++ exception occurred";
    FldRegFailed      : "";
    FldIdNotFound     : "Field id %(1) was not found";
    GetMsgFailed      : "";
EndEnum;

These are the errors that this class throws. Some have no text because they are just given the text of an underlying C++ exception that occurred.

Enum=DrvInitRes
    Failed      : "The driver initialization failed";
    WaitConfig  : "The driver wishes to wait for configuration next";
    WaitCommRes : "The driver wishes to wait for comm resources next";
EndEnum;

The initialize method that is called to initialize the driver (according to the type connection) must return one of these values to tell CQC the status of the initialization. If you indicate failure, the driver will not be loaded. Otherwise, you should indicate WaitCommRes, to indicate that you are waiting to open your communications resource. For now, WaitConfig is not used in macro based drivers, but may be eventually.

Enum=CommResults
    Success         : "Success";
    LostConnection  : "Lost connection";
    LostCommRes     : "Lost communications resource";
    ValueRejected   : "The field value written was rejected by the driver";
EndEnum;

The Poll() and Connect() and methods return a value of this type, which indicates the status of the operation.

If CommResults.Success is returned, the driver is moved to the connected state. If CommResults.LostConnection is returned, then the device remains in the waiting for connection state. If CommResults.LostCommRes is returned, the driver returns to the waiting for communications resource state, and the FreeCommResource() method is called.

During sends to the device, if it rejects a value, you should return ValueRejected, which will allow this rejection to be passed on back to the client who sent it. Or your driver code might reject it also, if the field limits mechanism isn't sufficient to insure that you won't get a good value.

Enum=VerboseLvls
    Off	   : "Off";
    Low     : "Low";
    Medium  : "Medium";
    High    : "High";
EndEnum;

Drivers can be put into 'verbose logging mode' by the user, via the Admin Interface, so that the driver will put out extra logging information. This is for problem diagnosis mainly. Since drivers are constantly active, they should never log errors in any of their repetitive callback methods, i.e. poll, connect, field change, etc... Doing so can cause the log server to be flooded with messages, pushing out the useful information.

Instead, the driver should use the GetVerboseLevel() method to ask what verbosity level it is set to and only log information if the verbosity level indicates it should. See GetVerboseLevel() below for details.

VectorOf[MEng.System.CQC.Runtime.CQCFldDef]	CQCFieldList;

This type is accepted by the SetFields() method. This is just a convenience, since any vector of CQCFldDef objects will be accepted, since any collection of the same type with the same element type is considered equivalent.

 

Constructors:

Constructor();

There is just a default constructor available.

 

Required Methods:

These methods are required, so you must override them in your driver. These are the means via which your driver is called to do most of the operations it must do.

Connect() Returns CommResults;

This method is called periodically in order to ask your driver to connect to it's device. At this point the GetCommResource() method has been called, so the device resource is available. It will return one of the com results enumerated values to indicate it's success or failure. 

See the definition of CommResults above for the results of your returning each of the particular values.

FreeCommResource() Returns MEng.Boolean;

This method is called to ask your driver to free any communications resources you have, sockets or ports, because either your poll or connect method indicated that you've lost it so they need to be cleaned up in preparation for re-obtaining them, or because the driver is being unloaded and they need to be closed. You should return True if successful, else False.

GetCommResource() Returns MEng.Boolean;

This method is called to ask your driver to obtain any communications resources it needs. You should NOT do this during the initialization call, but do it here, because this can be called multiple times as the resource is lost and regained. You should return True if successful, else False, in which case this will continue to be called periodically to attempt to get the needed resources.

Poll() Returns CommResults;

This method is called to ask your driver to do it's periodic poll of the device, if that is applicable for your driver, and it is for almost all drivers. You should query the device for any changes and store the results in your fields.

Note that even a device which always sends you change notifications asynchronously should at least be periodically pinged in some way if possible, so that a lost connection will be detected as soon as possible. Otherwise, you won't know it has been lost until you try to write something to it, which might not be for hours or days.

 

Non-Final, Non-Const Methods:

ClientCmd([In] MEng.Card4 CmdId, [In] String CmdData) Returns Card4;

This is an optional method that you can override in order to receive direct commands from clients, bypassing the field interface for some operations. The Devices::SendDrvCmd() action command can be used to make this call to a driver. The command id and command data are completely arbitrary and defined by the driver itself for whatever needs it has. The id is usually used to indicate what the command data string means.

See the QueryDrvText() method for the opposite of this method, to read a text value from a driver.

BoolFldChanged
(
    [In] MEng.Card4 FldId, [In] MEng.Boolean NewValue
)   Returns CommResults;
CardFldChanged
(
    [In] MEng.Card4 FldId, [In] MEng.Card4 NewValue
)   Returns CommResults;
FloatFldChanged
(
    [In] MEng.Card4 FldId, [In] MEng.Float8 NewValue
)   Returns CommResults;
IntFldChanged
(
    [In] MEng.Card4 FldId, [In] MEng.Int4 NewValue
)   Returns CommResults;
StringFldChanged
(
    [In] MEng.Card4 FldId, [In] MEng.String NewValue
)   Returns CommResults;
StrListFldChanged
(
    [In] MEng.Card4 FldId, [In] MEng.StringList NewValue
)   Returns CommResults;
TimeFldChanged
(
    [In] MEng.Card4 FldId, [In] MEng.Card8 NewValue
)   Returns CommResults;

You need to override one or more of these methods if you have any writeable fields. Override the ones for the types of the writeable fields you have. They will be called when fields of that type are modified by the outside world, and your driver should do whatever is required to reflect that in the target device. 

Each method gets the field id of the field that was changed, and the new value that was written to it. The value is already stored in the field storage, you just need to send the appropriate messages to the device to reflect this change.

The return indicates whether you were successful, think you lost connection with the device, or think you lost the communications resource itself. The driver architecture will use this to change your driver's state appropriately. If you lost the resource, you will get a call to FreeCommResource() to close down your resources, then you will start getting calls to GetCommResource() to attempt to re-obtain the resource. If you indicate lost connection, then you will start getting Connect() calls, to start trying to get reconnected.

InitializeOther
(
    [In] MEng.String CfgString
    , [In] MEng.System.Runtime.NamedValMap PromptVals
)   Returns DrvInitRes;
InitializeSerial
(
    [In] CommPort.CommPorts PortToUse
    , [In] MEng.System.Runtime.CommCfg PortCfg
    , [In] MEng.System.Runtime.NamedValMap PromptVals
)   Returns DrvInitRes;
InitializeSocket
(
    [In] MEng.System.Runtime.IPEndPoint RemoteEP
    , [In] MEng.System.Runtime.NamedValMap PromptVals
)   Returns DrvInitRes;

You need to override one of these methods, according to what kind of connection your driver uses. Since you don't have to override them all, they are not Required, so it is possible to not override either and the compiler will not warn you; however, you will get a runtime error.

You will get the information that has been configured by the user to tell you how to create the communications resource, a port number and port configuration for a serial connection, an IP end point for a socket, and a string of text for the 'other' connection type (other is a catchall for things like drivers that know inherently how to find their device, or some connection type not foreseen by Charmed Quark.) You should store these away in members for later use in the GetCommResource() method where applicable.

You will also get a NamedValMap which hold the key/value pairs for any prompted values that the driver's manifest file defined. If you defined none, this list will be empty. Each type of prompt has one or more keys and each key has one or more sub-keys. So you access a prompt value by providing the key and sub-key. See the driver development tutorial for details on the keys and sub-keys.

You return one of the driver initialization results values, defined above, to indicate the status of your initialization.

QueryBoolVal([In] MEng.Card4 ValId) Returns MEng.Boolean;
QueryBufVal
(
    [In]    MEng.Card4  ValId
    , [In]  MEng.String DataName
    , [Out] MEng.Card4  OutBytes
    , [Out] MEng.MemBuf ToFill 
)   Returns MEng.Boolean;

QueryCardVal([In] MEng.Card4 ValId) Returns MEng.Card4;
QueryIntVal([In] MEng.Card4 ValId) Returns MEng.Int4;
QueryTextVal
(
    [In]    MEng.Card4  ValId
    , [In]  MEng.String DataName
    , [Out] MEng.String ToFill
)   Returns MEng.Boolean;

These methods allow clients to do 'backdoor' calls to the driver to query values without going through the normal field interface. The id indicates what value to return, but the meaning of the id and the return values is purely a convention between the driver and the clients that talk to it.

For the buffer and text values, there is a two level structure with an id and a data name. So you can have an id that represents a category of values and then qualify it by indicating a particular item within that category.

Except for QueryTextVal(), these wouldn't be used yet. QueryTextVal() can be invoked from user actions via the Devices::QueryTextVal() command, and can be used to query data from the driver directly into user actions. This is sometimes useful where the field interface isn't really practical to use for some queries.

SendUserActEvent([In] String EvType, [In] String EvData);

Sends a  user action event, which is one of the types of broadcast events supported by CQC. A user action event is generally used for things like reporting a button presses on a device or a call coming in and so forth, i.e. non-field related changes in the system. The event type should be a string that uniquely identifies the type of event from the source source, such as 'ButtonPress' or 'CIDEvent'. It doesn't have to be unique system wide since the receiver of the events can check the source field, which will indicate the source of the event. So the actual unique id is the source plus the event type.

The event data is whatever data needs to be passed, such as an identifier for the button pressed or the phone number or name for a CID event. It shouldn't be very large, since events are sent out as datagram packets.

SetConfigStr(In] MEng.String ToSet);

This method allows you to store a configuration string for your driver. It is not for large amounts of data, just for fairly smallish stuff. So if you have, say, more than a couple kilobytes to store, use a file. You can read this value by in via

SetTOExtension([In] MEng.String ToSet);

When you call any of the message reading helper methods, they will automatically extend your passed timeout value if they are partway through a message when your passed timeout expires, to provide enough time to get the rest of the message read. The default extension is around 250ms, which may not be enough for some really slow devices. So you can set this timeout value. Keep it reasonable. If the device is so slow that it takes more than half a second of extension, you might want to implement some sort of asynchronous message reading mechanism of your own.

Terminate();

If your driver needs to clean up any resources (beyond the comm resource) when it is stopped (either by shutting down CQCServer or unloading the driver or so forth, then it can override this method. The actions taken could be quick and to the point, since CQCServer will only wait so long before assuming the driver isn't going to respond.

WaitConfig() Returns Boolean;

If you return WaitConfig from your driver initialization method, then you will next be called back on this WaitConfig method. Otherwise, you don't need to override it since it will never be called. You should use this method to load up any configuration that your driver needs to load. You should return True if you successfully loaded it (or decided that you should create some default initialization if no existing configuration info is found.) If you return False, then you will continue to be called here and your driver will remain in the 'Wait Config' state.

 

Final, Const Methods:

FldIdFromName([In] MEng.String NameToFind) Returns MEng.Card4;

Looks up a field name and gives you back the id. If you have to deal with a field very often, which is usually to say that it's one that poll regularly, it is worth looking up the id and using that to read and write the field value, because it is more efficient. If the field is not found, you will get a FldNotFound exception.

GetASCIITermedMsg
(
    [InOut] MEng.Object  SrcObj
    , [In]  MEng.Card4   WaitFor
    , [In]  MEng.Card1   Term1
    , [In]  MEng.Card1   Term2
    , [Out] MEng.String  ToFill
)  Returns MEng.Boolean;

GetASCIITermedMsg2
(
    [InOut] MEng.Object  SrcObj
    , [In]  MEng.Card8   EndTime
    , [In]  MEng.Card1   Term1
    , [In]  MEng.Card1   Term2
    , [Out] MEng.String  ToFill
)  Returns MEng.Boolean;

This is a convenience helper method that will read a terminated ASCII message for you, from either a serial port or stream socket.  Many devices use an ASCII based protocol, in which messages are terminated by a single byte or a particular byte pair in sequence. This method will read that message in for you and transcode it into a string object. So it is a very efficient and convenient way of reading such messages.

If you don't need a second termination byte, set it to zero. If you set both of them, then if those two bytes are seen in succession, the message is considered terminated and the characters gotten so far will be given back in the ToFill parameter.

If the read fails, it will either be because if timed out or because of an underlying device error. If it times out, then the return will be False. If a device error occurs, then it will throw the GetMsgFailed exception, the text of which will be set to whatever the underlying device error was. If successful, the return will be True.

Note that this method will bump the bad message counter if it gets a badly formed message, but it does not bump the timeout counter if no message arrives, because it will be called to try to connect to the device as well as to poll it. The timeout counter should only be bumped if you fail to get a response after you are connected, not while trying to connect. So you are still responsible for bumping the timeout counter.

Since this method must deal with multiple source types (serial ports and sockets now, maybe others later), it takes it as a base MEng.Object type and checks the type dynamically at runtime. If you don't pass either a serial port or stream socket object, you will get a UnknownConnType exception.

There is a '2' version of this method that works the same, except that instead of taking a number of milliseconds to wait, it takes an end time in the 64 bit time stamp format. This is often more convenient when you need to do a loop that processes messages for some period of time. You can calculate an end time, and then use it as the loop control and pass it directly to the message reading message on each round.

The term chars are not included in the returned data.

GetASCIIStartStopMsg
(
    [InOut] MEng.Object  SrcObj
    , [In]  MEng.Card4   WaitFor
    , [In]  MEng.Card1   StartByte
    , [In]  MEng.Card1   EndByte
    , [Out] MEng.String  ToFill
)  Returns MEng.Boolean;

GetASCIIStartStopMsg2
(
    [InOut] MEng.Object  SrcObj
    , [In]  MEng.Card8   EndTime
    , [In]  MEng.Card1   StartByte
    , [In]  MEng.Card1   EndByte
    , [Out] MEng.String  ToFill
)  Returns MEng.Boolean;

This is a convenience helper method that will read an ACSII message from a device for you. In this case, it is for protocols that have a unique start and stop byte that delimit the message. For instance, many ASCII protocols will use a STX/ETX as delimiters, and which never appear in the actual message text. This method will parse a message out of the input data stream, then transcode it to text for you.

If the read fails, it will either be because if timed out or because of an underlying device error. If it times out, then the return will be False. If a device error occurs, then it will throw the GetMsgFailed exception, the text of which will be set to whatever the underlying device error was. If successful, the return will be True.

Note that this method will bump the bad message counter if it gets a badly formed message, but it does not bump the timeout counter if no message arrives, because it will be called to try to connect to the device as well as to poll it. The timeout counter should only be bumped if you fail to get a response after you are connected, not while trying to connect. So you are still responsible for bumping the timeout counter.

Since this method must deal with multiple source types (serial ports and sockets now, maybe others later), it takes it as a base MEng.Object type and checks the type dynamically at runtime. If you don't pass either a serial port or stream socket object, you will get a UnknownConnType exception.

There is a '2' version of this method that works the same, except that instead of taking a number of milliseconds to wait, it takes an end time in the 64 bit time stamp format. This is often more convenient when you need to do a loop that processes messages for some period of time. You can calculate an end time, and then use it as the loop control and pass it directly to the message reading message on each round.

The start/stop bytes are included in the returned message.

GetConfigStr(Out] MEng.String ToFill) MEng.Boolean;

This method allows you to retrieve a configuration string previous stored by SetConfigStr(). So you would use SetConfigStr() to store your driver configuration any time it changes. You initialization method would return WaitConfig instead of WaitCommRes(), which will cause your WaitConfig() override to be called. In that method you would call this to read in the configuration.

GetFldErrState([In] MEng.Card4 FldId) Returns MEng.Boolean;

Returns the error state of the field indicated by the passed field id. See the SetFldErrState() method for details on the meaning of the field error state.

GetFldName([In] MEng.Card4 FldId) Returns MEng.String;

Returns the name of the field with the indicated field id. If the id is not valid, the FldIdNotFound exception will be thrown.

GetMoniker() Returns MEng.String;

Returns the moniker of the calling driver. There are times when you might need to know this information, or perhaps use it in a logged error message.

GetIsConnected() Returns MEng.Boolean;

Returns a  Boolean value that indicates whether the driver is currently in connected state. For the most part, this is obvious, since you have callbacks to connect and to poll, and if you are in being asked to connect you obviously aren't connected, if to poll, you are. However, there some occasions where you call common code to process messages that can be processed both during connecting and polling, and those methods need to be aware of the difference. 

The main reason is that the device has configurable parameters, and you need to get these parameters from the device in order to set up device driver data used for dealing with subsequent messages received. If the device sends async messages, it is possible that you might get one of those supposedly subsequent messages while getting connected, and you would want to ignore them.

GetStartLenMsg
(
    [InOut] MEng.Object  SrcObj
    , [In]  MEng.Card4   WaitFor
    , [In]  MEng.Card1   StartByte
    , [In]  MEng.Card4   LenOfs
    , [In]  MEng.Card4   LenBytes
    , [In]  MEng.Card4   TrailBytes
    , [In]  MEng.Card4   MaxBytes
    , [Out] MEng.MemBuf  ToFill
)  Returns MEng.Card4;

GetStartLenMsg2
(
    [InOut] MEng.Object  SrcObj
    , [In]  MEng.Card8   EndTime
    , [In]  MEng.Card1   StartByte
    , [In]  MEng.Card4   LenOfs
    , [In]  MEng.Card4   LenBytes
    , [In]  MEng.Card4   TrailBytes
    , [In]  MEng.Card4   MaxBytes
    , [Out] MEng.MemBuf  ToFill
)  Returns MEng.Card4;

These methods are helpers for dealing with devices that send messages of the type that start with some start byte, and then usually are followed shortly thereafter with a indication of how many more bytes to follow. As with all of the message reading helpers, it comes in two flavors, one that takes a millsecond value it will wait for the reply, and one that takes an end time.

You indicate the start byte it should watch for. Once it sees that byte, it will then read LenOfs more bytes (which is often zero because the length indicator is generally the next byte.) It then assumes the length comes next. You indicate how many bytes are in the length via LenBytes. If it is more than one byte, currently there is an assumption that it is little endian. If not, you cannot currently use these methods.

It will then read that many more bytes. Often the length doesn't include some trailing bytes, such as a sum or CRC. If so, use the TrailBytes value to indicate how many more bytes to read once the indicated number have been read. So if there is a 2 byte CRC at the end, you set TrailBytes to 2.

The resulting message, if any, will be returned in the passed buffer, and the number of bytes put into the buffer will returned as the result of the call.

GetStartStopMsg
(
    [InOut] MEng.Object  SrcObj
    , [In]  MEng.Card4   WaitFor
    , [In]  MEng.Card1   StartByte
    , [In]  MEng.Card1   StopByte
    , [In]  MEng.Card4   MaxBytes
    , [Out] MEng.MemBuf  ToFill
)  Returns MEng.Card4;

GetStartStopMsg2
(
    [InOut] MEng.Object  SrcObj
    , [In]  MEng.Card8   EndTime
    , [In]  MEng.Card1   StartByte
    , [In]  MEng.Card1   StopByte
    , [In]  MEng.Card4   MaxBytes
    , [Out] MEng.MemBuf  ToFill
)  Returns MEng.Card4;

This method is the same as the GetASCIIStartStopMsg method above, so read the docs above first. The difference is that this one gets a binary message instead of an ASCII message. So it fills in a memory buffer instead of a string. The other difference is that it takes a maximum number of bytes to accept before giving up (to avoid large buffer growth if the incoming data isn't as expected (perhaps the user plugged in the wrong device.)

And finally, instead of returning a boolean, it returns the number of bytes it read. With the ASCII version, you can ask the string object how many chars it has. With a buffer you need to know how many bytes got put into it, since it can be larger than the data that was loaded into it.

There is a '2' version of this method that works the same, except that instead of taking a number of milliseconds to wait, it takes an end time in the 64 bit time stamp format. This is often more convenient when you need to do a loop that processes messages for some period of time. You can calculate an end time, and then use it as the loop control and pass it directly to the message reading message on each round.

The start/stop bytes are included in the returned data.

GetTermedMsg
(
    [InOut] MEng.Object  SrcObj
    , [In]  MEng.Card4   WaitFor
    , [In]  MEng.Card1   Term1
    , [In]  MEng.Card1   Term2
    , [In]  MEng.Boolean TwoTerms
    , [Out] MEng.MemBuf  ToFill
)  Returns MEng.Card4;

GetTermedMsg2
(
    [InOut] MEng.Object  SrcObj
    , [In]  MEng.Card8   EndTime
    , [In]  MEng.Card1   Term1
    , [In]  MEng.Card1   Term2
    , [In]  MEng.Boolean TwoTerms
    , [Out] MEng.MemBuf  ToFill
)  Returns MEng.Card4;

This method is the same as the GetASCIITermedMsg method above, so read the docs above first. The difference is that this one gets a binary message instead of an ASCII message. So it fills in a memory buffer instead of a string. The other is that there is a TwoTerms parameter that indicates whether both term bytes are valid or just one. With the ASCII version above, zero is used to indicate that the second term byte is not used, but in a binary message, zero might be a valid terminator, so you must indicate if both are used.

And finally, instead of returning a boolean, it returns the number of bytes it read. With the ASCII version, you can ask the string object how many chars it has. With a buffer you need to know how many bytes got put into it, since it can be larger than the data that was loaded into it.

There is a '2' version of this method that works the same, except that instead of taking a number of milliseconds to wait, it takes an end time in the 64 bit time stamp format. This is often more convenient when you need to do a loop that processes messages for some period of time. You can calculate an end time, and then use it as the loop control and pass it directly to the message reading message on each round.

The term chars are not included in the returned data.

GetVerboseLevel() Returns VerboseLvls;

Gets the current logging verbosity level for the driver. The user can control the verbosity level for in the field debugging purposes and drivers are encouraged to make use of this to add in logging that could not otherwise be done because it could fill the logs with thousands of messages and push out useful information.

The general rule is that the more often something would be logged, the higher the threshold for logging it. So something logged during initialization could be at low level, while something in the poll callback probably should be at high level if it could happen each time through (and the poll rate is high.)

A drivers should normally not log anything if the verbose level is Off, though it is sometimes alright to log something in field change callbacks, since those are driven purely by user activity and won't usually be invoked too often. Otherwise, use this method to keep logging under the control of the user.

ReadBoolFld([In] MEng.Card4 FldId) Returns MEng.Boolean;
ReadBoolFldByName([In] MEng.String FldName) Returns MEng.Boolean;
ReadCardFld([In] MEng.Card4 FldId) Returns MEng.Card4;
ReadCardFldByName([In] MEng.String FldName) Returns MEng.Card4;
ReadFloatFld([In] MEng.Card4 FldId) Returns MEng.Float8;
ReadFloatFldByName([In] MEng.String FldName) Returns MEng.Float8;
ReadIntFld([In] MEng.Card4 FldId) Returns MEng.Int4;
ReadIntFldByName([In] MEng.String FldName) Returns MEng.Int4;
ReadStringFld([In] MEng.Card4 FldId) Returns MEng.String;
ReadStringFldByName([In] MEng.String FldName) Returns MEng.String;
ReadStrListFld([In] MEng.Card4 FldId, [Out] MEng.StringList ToFill);
ReadStrListFldByName([In] MEng.String FldName, [Out] MEng.StringList ToFill);
ReadTimeFld([In] MEng.Card4 FldId) Returns MEng.Card8;
ReadTimeFldByName([In] MEng.String FldName) Returns MEng.Card8;

These methods allow you to read the value of one of your fields. Usually the driver itself doesn't need to read it's own fields, but sometimes this is necessary. If a device bundles up multiple settings in a single message, and you get a FldChanged event for one of them, in order to build a message, you'll have to get the current values of the other fields.

There are two methods per possible field type, one which uses the field id and another which uses the field name. The field id is more efficient, so use it when possible by pre-looking up the ids and store them in members. But this is only required for fields that are used in the poll event mostly. The connect event is hopefully rare.

 

Final, Non-Const Methods:

IncBadMsgs();
IncFailedWrite();
IncNaks();
IncTimeouts();
IncUnknownMsgs();
IncUnknownWrite();

Each driver gets some magic fields that are registered for it by CQCServer. These are used for instrumentation purposes, so that problems can be diagnosed in the field. You should call these methods to bump those instrumentation fields. BadMsgs means that you got a message from the device that was not good. Failed write means that an attempt to write a field change to the device failed. TimeOuts means that the device did not respond to a query or message.  UnknownMsgs means you got a well formed message from the device but didn't know what to do with it. UnknownWrite means you got a FldChanged call for a field id that you didn't expect. Naks are rejections of messages sent to the device, because it couldn't understand it, thought it was not correctly formed, contained a bad value, etc...

SetFldErrState([In] MEng.Card4 FldId, [In] MEng.Boolean ToSet);

In most cases, a device is either all good or all bad, i.e. it is either online (connected to it's device) and all it's fields are valid, or it is offline and all it's fields are in error. However, some devices are designed such that some information might not a;waus be available, or it is composed of multiple sub-units and some might be in error or failed and so forth.

So, you can mark specific fields as being in error separately. This will remain set over losing and regaining the device connection, i.e. until you clear the flag. You can get the error state with the GetFldErrState() method.

SetFields([In] CQCFieldList ToSet);

This is called to set your list of fields. It is normally done during the initialization method, but in some cases you must talk to the device before you can know what fields to register, in which case you can do it in the connect method.

It is also possible, if your device has dynamic or configuration driven settings, to sense this during a poll cycle and call this again during the poll event to change your list of fields. But, you should do this with care because it causes every client out there that is polling any field in this device to get an exception that forces it to re-synch itself with this driver, i.e. to get the list of fields again. And, of course, if you remove a field that some client is looking at, they are going to an error of some sort appropriate to that client application.

If this fails, you will get the FldRegFailed exception.

SetPollTimes([In] MEng.Card4 PollMillis, [In] MEng.Card4 ConnectMillis);

You can call this to modify the default poll and connection periods that are used to pause between calls to the connect or poll events. By default, these are 1 and 3 seconds respectively. But more interactive devices with nice, fast protocol speed will generally using a faster poll time. Slower devices that change rarely can just stick with the defaults. You cannot lower the poll time to less than 100ms or the reconnection time to less than 500ms.

Simulate();

This is only called when developing the driver in the test harness. It will kick off the sequence of events that simulates CQCServer's animation of the driver by doing the appropriate callbacks, and reacting to the returned statuses of these callbacks. You should invoke it in the entry point of your macro implementation class.

WriteBoolFld([In] MEng.Card4 FldId, [In] MEng.Boolean ToWrite);
WriteBoolFldByName([In] MEng.String FldName, [In] MEng.Boolean ToWrite);
WriteCardFld([In] MEng.Card4 FldId, [In] MEng.Card4 ToWrite);
WriteCardFldByName([In] MEng.String FldName, [In] MEng.Card4 ToWrite);
WriteFloatFld([In] MEng.Card4 FldId, [In] MEng.Float8 ToWrite);
WriteFloatFldByName([In] MEng.String FldName, [In] MEng.Float8 ToWrite);
WriteIntFld([In] MEng.Card4 FldId, [In] MEng.Int4 ToWrite);
WriteIntFldByName([In] MEng.String FldName, [In] MEng.Int4 ToWrite);
WriteStringFld([In] MEng.Card4 FldId, [In] MEng.String ToWrite);
WriteStringFldByName([In] MEng.String FldName, [In] MEng.String ToWrite);
WriteStrListFld([In] MEng.Card4 FldId, [In] MEng.StringList ToWrite);
WriteStrListFldByName([In] MEng.String FldName, [In] MEng.StringList ToWrite);
WriteTimeFld([In] MEng.Card4 FldId, [In] MEng.Card8 ToWrite);
WriteTimeFldByName([In] MEng.String FldName, [In] MEng.Card8 ToWrite);

These methods allow you to store data into your fields. There are two versions for each possible field type, one that takes a field id and the other that takes a field name. Where possible use the field id version for fields that you react with constantly, by pre-looking them up after you register them, and storing the resulting ids in members.

 

Deprecated Methods

These methods are deprecated, because they have been replaced by other classes or methods, are no longer going to be supported, and so forth. So don't use them in any new classes you write and try to replace them with whatever mechanisms now provide their functionality.

LogMsg([In] MEng.String ToLog);
LogMsg1([In] MEng.String ToLog, [In] MEng.Formattable Token1);
LogMsg2
(
    [In] MEng.String ToLog
    , [In] MEng.Formattable Token1
    , [In] MEng.Formattable Token2
);

LogMsg3
(
    [In] MEng.String ToLog
    , [In] MEng.Formattable Token1
    , [In] MEng.Formattable Token2
    , [In] MEng.Formattable Token3
);

These APIs are deprecated. You should instead create an MEng.System.CQC.Runtime.CQCLogger object and us it to do logging, if you need to do any logging. It has the same APIs as these, but is more general purpose since you can pass it out to helper classes that might need to log messages.

This method allows drivers to log text messages to the centralized log server. This is an API you must use carefully. On the one hand, logging useful information can massively improve the ability to diagnose problems in the field. However, because of the periodic, and often rather rapid periodic, nature of the driver callbacks, if you get into a situation where you are logging something on every callback, you will quickly fill up the log files, and push out any useful information. Get caught in a loop somehow, logging each time through, and you'll bring the whole system to it's knees.

There are a few variations that also allow you to provide replacement tokens that are formatted to text and used to replace numbered replacement tokens in the log message text. This is built on the standard token replacement functionality in the string class, so see it for more details on the token options. You can pass any object that derives from Formattable, which means that it can be formatted to an output stream, so this is very flexible. Token1 replaces %(1), Token2 replaces %(2), and Token3 replaces %(3). The positions of the tokens in the log message are not important, only their token numbers.

So use this API where appropriate, but be very careful about it's use.