Communication Driver Template
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 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" ):>; HelpKey <:TagField("SQL_VARCHAR(255)", "HelpKey" ):>; { Other parameters will vary by driver but often include the following } RespTimeOut <:TagField("SQL_VARCHAR(255)", "RespTimeout" ):>; SiteID <:TagField("SQL_VARCHAR(255)", "SiteID" ):>; UTCOffset <:TagField("SQL_VARCHAR(255)", "UTCOffset" ):>; StoreOutputs <:TagField("SQL_VARCHAR(255)" ):> { When TRUE permits storing of output values }; Hold <:TagField("SQL_INTEGER", "Hold" ):> = 0 { 1=hold last value 0=invalidate on error }; AutoRewrite <:TagField("SQL_VARCHAR(255)" ):> { When TRUE permits the automatic Rewriting of Outputs }; ) [ {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; …
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 VTScada drivers *****} DriverNameLabel = "DemoDriver"; Driver { The generic driver module instance }; Ready { Indicates to VTScada driver the module is 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 text 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 VTScada time }; HistPoll Module { Sets the history poll values }; ReadLineStatus Module { Reads a line and returns status }; CheckCommsLossErr Module { Checks the comms fail errors }; { 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; Shared ReWriteOutputsBtn; { Used if implementing Rewrite Outputs } ] [ (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 } { Configuration parameter } RPCService = PickValid(\USGSSharedRPC, 0) ? "USGSDriver" : Name; { Return object value } Root = Self(); DriverObj = Root; Refresh(); 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 there is a 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. If implementing Auto-Rewrites, include code to ensure that the StoreOutputs value is always true if AutoRewrite is true.
< {=========================== 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); StoreOutputs = PickValid(Cast(StoreOutputs, \#VTypeShort), 0); AutoRewrite = PickValid(Cast(AutoRewrite, \#VTypeShort), 0); { Sort the parms, when AutoRewrite is true, StoreOutputs must be true } IfThen(AutoRewrite && !StoreOutputs, StoreOutputs = 1; ); IfThen(!StoreOutputs && PickValid(*(Driver\RPCStatus) == \#RPCServer, 0), Driver\ResetOutputDict(); ); Return(0); ] ] { Refresh } >
Refresh is followed by the standard modules of a communication driver: VTSGetAddr, VTSMaxBlock, VTSRead and VTSWrite. In the following examples, 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 VTScada 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 - 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 } >
If implementing automatic rewrites of saved data, you must have CheckCommsLossErr() module similar to the following. The specific error codes will depend on your driver - those used in the example are for the Allen Bradley driver only.
< {================== ABDriver\CheckCommsLossErr ====================} {==================================================================} CheckCommsLossErr ( ErrorVal; ) Main [ If 1; [ {***** Comms Loss errors noted for the AB driver *****} IfThen(ErrorVal == 4 || ErrorVal == 32 || ErrorVal == 48 || ErrorVal == 64 || ErrorVal == 0x200 || ErrorVal == 0x202 || ErrorVal == 0x213 || (ErrorVal >= 0x300 && ErrorVal <= 0x309), Return(1); ); Return(0); ] ] { End of ABDriver\CheckCommsLossErr } >
In the code for your driver's configuration panel, if you are implementing output rewrites, you must also provide a check box for AutoRewrite and StoreOutputs. Include the following code to ensure that if AutoRewrite is true, StoreOutputs can't be false:
If Watch(0, Parms[\#StoreOutputs]); [ IfThen(!Parms[\#StoreOutputs], { Ensures if StoreOutputs is false then AutoRewrite can't be true } Parms[\#AutoRewrite] = 0; ); ] If Watch(0, Parms[\#AutoRewrite]); [ IfThen(Parms[\#AutoRewrite], { Ensures if AutoRewrite is true then StoreOutputs can't be false } Parms[\#StoreOutputs] = 1; ); ]