A sample communication driver is displayed in part here. Code that is useful only for the particular hardware this driver was designed to communicate with is not included. This particular driver was designed to be read-only. It does not contain a VTSWrite module. The tag’s configuration and common modules are not shown here.
The driver starts with standard tag definitions:
{===============================================================}
(
Name <:TagField("SQL_VARCHAR(64)", "Name" ):>;
Area <:TagField("SQL_VARCHAR(255)", "Area" ):>;
Description <:TagField("SQL_VARCHAR(255)", "Description" ):>;
RespTimeOut <:TagField("SQL_VARCHAR(255)", "RespTimeout" ):>;
SiteID <:TagField("SQL_VARCHAR(255)", "SiteID" ):>;
UTCOffset <:TagField("SQL_VARCHAR(255)", "UTCOffset" ):>;
HelpKey <:TagField("SQL_VARCHAR(255)", "HelpKey" ):>;
)
[ {Variables}
{==================Version Control Information =================}
{ V 0.0.01 - Original issue - 9 December, 2008 }
{===============================================================}
Constant DriverVersion = "0.10.0 6 January, 2009";
Constant DrawLabel = "USGSDriver";
Constant #Name = 0;
Constant #Area = 1;
Constant #Description = 2;
Constant #ResponseTimeOut = 3;
Constant #SiteID = 4;
Constant #UTCOffset = 5;
Constant #HelpKey = 6;
In the variable declaration modules and error constants are also defined:
{ Module Numbers }
Constant #VTSGetAddr = 0;
Constant #VTSRead = 1;
{ Error Constants }
Constant #NoError = 0;
Constant #ConnectionError = 1;
…
As well as the required modules and variables (plus a few extra shown here):
{***** Required for all VTS drivers *****}
DriverNameLabel = "DemoDriver";
Driver { The generic driver module instance };
Ready { Indicates to VTS driver we are ready };
RPCService { Name of the RPC service for this tag };
Value { Driver status };
{ Modules }
ShowComm Module "DemoShowComm.SRC" { Show communication };
ShowStats Module "DemoShowStats.SRC" { Show comm statistics };
VTSMaxBlock Module { Returns Maximum record size };
VTSRead Module { The Read module };
VTSGetAddr Module { Subroutine to parse address };
Refresh Module { Refresh module };
ErrMessage Module { Converts error codes to test strings };
ReportTraffic Module { Translator for the traffic monitor };
SetStats Module { Updates driver statistics };
TransmitReceiveData Module { Transmit/Receive Module };
ProcessReturnedData Module { Processes ret data into arrays };
SiteTime_2_UTCTS Module { Converts Site time to VTS time };
HistPoll Module { Sets the history poll values };
ReadLineStatus Module { Reads a line and returns status };
{ Variables }
Root { Root object value };
DriverObj { Object value of driver };
CommPortObj { Communication port tag object };
ErrorMessages { Array of Error Messages };
Counts = 0 { # of successful transactions };
TimeStamp { Time of last successful transaction };
Error = 0 { Global Error code };
ErrorCounts = 0 { # of errors since starting };
ConsecErrCount = 0 { Consecutive time out error counts };
ErrorMemAddr { Memory address of last error };
ErrorTime { Time of last error };
LastError { Error code for last error };
Shared Message[100] { List of error messages };
CommDisplay { Object communications display module };
ErrorModule { Module number generating error };
ErrorDate { Date of last unsuccessful com attempt};
ErrorOwner { Object value of module last error };
dt { Time since last transaction };
DateStamp { Date of last successful comm. };
LastURL { Last URL generated };
ResponseTO { Response Time Out variable };
DataFilePath { Path for data file to write/read };
Status { Status of file directory creation };
Finally, the standard groups memberships are defined:
{ Parameter Constants }
[ (GROUPS)
Shared Numeric;
Shared Drivers { This is a driver point };
]
[ (GRAPHICS)
Shared CommIndicator;
Shared CommStatistics;
Shared CommMessages;
]
[ (PLUGINS)
Shared ConfigFolder = "DemoDriverConfig";
Shared Common = "DemoDriverCommon";
]
The module opens with an initialization state:
DemoDriverInit [
If \NetworkValues\Started WaitServer;
[
{ Set the RPC service for this instance depending on the }
{ Config.INI parameter }
RPCService = PickValid(\USGSSharedRPC, 0) ? "USGSDriver" : Name;
{ Return object value }
Root = Self();
DriverObj = Root;
Refresh();
{ Set the data file path and create Logs directory if required }
Status = MkDir(Concat(\LibX\ProjPath, "Logs\"));
DataFilePath = Concat(\LibX\ProjPath, "Logs\", Name,
" FileDataFields.LOG");
{ Delete old file }
FWrite(DataFilePath, 3, 0, "");
IfThen(!Valid(Message[#NoError]);
{ Set up of driver statistics display labels }
CountsLabel = PickValid(\CountsLabel, "Counts");
{ … etc … }
{ Driver internal errors }
Message[#NoError ] = "No Error";
{ … etc … }
) { IfThen };
{ Register with the network values service }
\NetworkValues\Register(Self(), Name);
]
]
Rather than one main state, the driver has three: Wait, Client and Server. Most of the time it will be in the Wait state, waiting to send or receive data.
Wait [
{ If this PC is a data server for this PLC, }
{ go to the Server state }
If PickValid(Driver\Started, 1) &&
PickValid(*(Driver\RPCStatus), \#RPCServer { Server }) == \#RPCServer Server;
{ If this PC is a client for this PLC, go to the Client state }
If PickValid(Driver\Started, 1) &&
*(Driver\RPCStatus) == \#RPCClient Client;
]
Server [
{ If no longer server go to wait state }
If *(Driver\RPCStatus) != \#RPCServer Wait;
{ Get object values in steady state }
CommPortObj = USGSTCPIPPort;
Ready = 1;
{ Reset if we switch from a server to a client }
If *(Driver\RPCStatus) != \#RPCServer Wait;
]
Client [
{ Get object values in steady state }
Ready = 1;
If PickValid(*(Driver\RPCStatus),\#RPCServer) != \#RPCClient Wait;
]
The last tag-specific part of the driver is the Refresh module.
<
{=========================== Refresh =============================}
{ Refresh subroutine. }
{=================================================================}
Refresh
(
Parm { Array parameters prior to their change }
)
Refresh [
If Watch(1);
[
RPCService = Name { Set RPCService };
UTCOffset = PickValid(Cast(UTCOffset, 2) , TimeZone(0));
ResponseTO = PickValid(ResponseTimeOut, 10);
Return(0);
]
]
{ Refresh }
>
Refresh is followed by the standard modules of a communication driver: VTSGetAddr, VTSMaxBlock, VTSRead and VTSWrite. In the examples shown below, any code that is not general in purpose has been removed.
<
{================ USGSDriver\VTSGetAddr ========================}
{ }
{===============================================================}
VTSGetAddr
(
Address { Raw address specified by point parameters};
RetAddress { Returned address };
BitNum { Pointer to bit number variable to set };
TableName { Table name };
Info2 { };
Info3 { };
DType { Pointer to data type to return };
Read { True if a READ address, otherwise write };
Rate { Rate for read coalescing };
)
[
NRead { Number of Values not read };
SiteNumber { Station Number Field };
ParameterField { Parameter Field };
AddressError { Set on error };
]
Main [
If Watch(1);
[
AddressError = 0;
{ Need an valid non-null value address }
IfElse(Valid(Address) && StrLen(Address) > 0;
{ Set to incoming address - }
{ sends array of addresses to VTSRead }
*RetAddress = Address;
*AddressError = 1;
); { IfElse }
{ Return error }
Return(AddressError);
]
]
{ End of VTSGetAddr }
>
<
{======================= VTSMaxBlock =========================}
{ Returns the Maximum size for block reads and writes. In }
{ example, VTSMaxBlock will simply return a constant. }
{=============================================================}
VTSMaxBlock
(
Info1 { Not used };
Info2 { Not used };
Info3 { Not used };
DType { Pointer to data type };
)
[
Constant #VTSMaxBlock = 100;
]
VTSMaxBlock [
Return(#VTSMaxBlock);
]
{ VTSMaxBlock }
>
Here comes the VTSRead module. Again – only the most general purpose of statements have been included.
<
{================= USGSDriver\VTSRead =======================}
{ This module performs the I/O reads. }
{============================================================}
VTSRead
(
Trigger { Will do read on positive edge };
Array { Array to put the result into };
N { The number of values to read };
MemAddr { Field Name Parameter };
BitNum { Pointer to bit number variable (not used)};
Info1 { Site Number to read };
Info2 { };
Info3 { };
DType { Data type };
RefreshContext { Where to call the RefreshData() module };
)
[
Name = "VTSRead" { Module name for stats };
LocErr { Local Error };
Counts = 0 { # of successful transactions };
TimeStamp { Time of last successful transaction };
Error = 0 { Global Error code };
ErrorCounts = 0 { # of errors since starting };
ErrorMemAddr { Memory address of last error };
ErrorTime { Time of last error };
LastError { Error code for last error };
ModNumber { Module number for VTS read };
SiteData { Data array to pass to refresh context};
SiteTimeStamp { Timestamp array to pass to refresh context};
…
]
Init [
If 1 VTSWaitTrigger;
[
Return(Self);
]
]
VTSWaitTrigger [
If Trigger GetSiteData;
[
ResetParm(Self(), 1) { Reset the trigger };
LocErr = Invalid();
SiteData = New(N);
SiteTimeStamp = New(N);
{ Type of poll we need to do - default to data since last poll }
PollType = PickValid(HistPollType, #HistPollSinceLast);
]
]
GetSiteData [
LocErr = TransmitReceiveData(SiteID, MemAddr, StartDateTS,
EndDateTS);
If !LocErr ProcessData;
[
LocErr = Invalid();
]
If LocErr SetStats;
]
ProcessData [
LocErr = ProcessReturnedData(&SiteData, &SiteTimeStamp,
LastTSRead);
If !LocErr SetStats;
[
{ Send the new data and timestamps }
RefreshContext\RefreshData(SiteTimeStamp, SiteData);
{ Update the network variable }
LastReadingTS = Max(PickValid(LastReadingTS, 0), LastTSRead);
]
If LocErr SetStats;
]
SetStats [
If 1 VTSWaitTrigger;
[
SetStats(Error = LocErr, Self(), ModNumber, 0, 0)
{ Set driver stats };
IfElse(LocErr;
++ErrorCounts;
{ Else no error }
++Counts { Increment counts };
) { IfElse };
]
]
{ USGSDriver\VTSRead }
>