Main Page | Features | Central Services | csv-Files | Types | Transfer | Access | API-C | API-VB/ActiveX | API-Java | Examples | Downloads
Working with a Standard Server

Standard Server

Call up the Wizard for your project and generate VB code and then compile and run the generated projects.

If you want to continue using the generated .csv database files, you can. You should then comment out any lines of code that explicitly call EqpRegisterPropertyEx() or EqpRegisterModule(). These will be found in the "init" function.

A Visual Basic standard server makes use of the tine server activeX control srv.ocx, and this is included in the project and appears on the server's form (although the control is invisible at run time). Likewise a Visual Basic timer is included on the server's form in order to simulate hardware io.

wkvbstdsrv1.jpg

The generated code behind this 'top' level form is minimal. Instead most of the work is carried out in routines found in a generated .bas module. In this example: 'Wkseqm.bas'.

You now have full control over all information passed to the server by the caller. It is also much easier to support more complex data types as well as user defined structures.

You should repeat some of the tests made with the buffered server, by editing the configuration files by hand.

With the standard server, you will now have an event handler (usually refered to as the equipment module service routine) which will be fired when ever a property is accessed. In particular you can examine all aspects of the request, such as the requested data format, the incoming data format, the data access flags, etc. and make response decisions accordingly.

In the first go-around, if you just take the generated code and compile, link and run it, your server should behave just like the buffered server. You can start refining things by perhaps checking who the caller is (GetCallerInformation()) or maybe checking the access flags (check whether the CA_HIST flag is on or not -> indicates whether the call is coming from the local history server) etc. and change your response under certain conditions (e.g. maybe you want to filter the data a bit more for the local history system than for a network caller).

For our example in the last section, with three properties "Sine", "Amplitude", and "Temperature", you might generate code something like the following (where the export name "WKSineGen" was used instead of "MYSINE":

Attribute VB_Name = "WkseqmModule"
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' WkseqmModule.bas    REVISION HISTORY:
' Generated by SRVWIZARD on Thu Apr 20 16:10:52 2006
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Global variables and constants
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private Const WkseqmLocalName$ = "WKSEQM"
Private Const WkseqmNumDevices% = 100
Global Const PRP_TEMPERATURE_SIZE = 100
Global Const PRP_TEMPERATURE_INSIZE = 1
Global Const PRP_TEMPERATURE_UPRLIMIT = 100
Global Const PRP_TEMPERATURE_LWRLIMIT = 0
Global Const PRP_AMPLITUDE_SIZE = 100
Global Const PRP_AMPLITUDE_INSIZE = 1
Global Const PRP_AMPLITUDE_UPRLIMIT = 500
Global Const PRP_AMPLITUDE_LWRLIMIT = 0
Global Const PRP_SINE_SIZE = 1024
Global Const PRP_SINE_UPRLIMIT = 500
Global Const PRP_SINE_LWRLIMIT = -500

Global prpTemperatureBuffer(PRP_TEMPERATURE_SIZE - 1) As Single
Global prpTemperatureIn As Single
Global prpAmplitudeBuffer(PRP_AMPLITUDE_SIZE - 1) As Single
Global prpAmplitudeIn As Single
Global prpSineBuffer(PRP_SINE_SIZE - 1) As Single

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' WkseqmSimulateData is a useful routine for test servers
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function WkseqmSimulateData(Offset As Double, range As Double) As Double
WkseqmSimulateData = Offset + range * Rnd
End Function

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' include WkseqmBackgroundFunction in a Timer to perform background IO, etc.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function WkseqmBackgroundFunction(srvControl As Srv) As Integer
Dim i As Integer
' TODO: put your IO or other background activity here

' Clear alarms at start of IO task (see if they come back)
  srvControl.EqpClearAlarm -1

' TODO: replace WkseqmSimulateData() with your own data acquisition
'       and manipulation routines
  For i = 0 To PRP_TEMPERATURE_SIZE - 1
     prpTemperatureBuffer(i) = WkseqmSimulateData(PRP_TEMPERATURE_LWRLIMIT, PRP_TEMPERATURE_UPRLIMIT - PRP_TEMPERATURE_LWRLIMIT)
  Next
  For i = 0 To PRP_SINE_SIZE - 1
     prpSineBuffer(i) = WkseqmSimulateData(PRP_SINE_LWRLIMIT, PRP_SINE_UPRLIMIT - PRP_SINE_LWRLIMIT)
  Next
' make use of SetAlarm() as needed :

' srvControl.EqpSetAlarm devNr, almCode, CADDR(almData(0)), 0

' set error codes as needed:
  WkseqmBackgroundFunction = 0
End Function

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' include WkseqmInitFunction in Form_load (for instance) to initialize the server
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function WkseqmInitFunction(srvControl As Srv, expName As String) As Integer
Dim rc As Integer, id As Integer

' FECNAME: either read from file 'fecid.csv' or user API :
'rc = RegisterFecName(fecname$, desc$, loc$, hdw$, resp$, portoffset)

' DEVICE SERVER: device server parameters
srvControl.EqpNumberModules = WkseqmNumDevices%
srvControl.EqpName = WkseqmLocalName$
srvControl.ExportName = expName
'now enable the server
srvControl.Enabled = True
rc = srvControl.EqpStatus
If rc Then GoTo ExitWkseqmInitFunction
'Property Registration:
id = srvControl.EqpRegisterPropertyEx("Temperature", PRP_TEMPERATURE_INSIZE, CF_FLOAT, "", PRP_TEMPERATURE_SIZE, CF_FLOAT, "", CA_READ, "[0:100 'C][CHANNEL]Temperature of the Sine generato")
If id < 0 Then GoTo ExitWkseqmInitFunction
id = srvControl.EqpRegisterPropertyEx("Amplitude", PRP_AMPLITUDE_INSIZE, CF_FLOAT, "", PRP_AMPLITUDE_SIZE, CF_FLOAT, "", CA_READ + CA_WRITE, "[0:500 V][CHANNEL]Sine Curve Amplitude")
If id < 0 Then GoTo ExitWkseqmInitFunction
id = srvControl.EqpRegisterPropertyEx("Sine", 0, CF_NULL, "", PRP_SINE_SIZE, CF_FLOAT, "", CA_READ, "[-500:500 V][SPECTRUM]Sine Curve")
If id < 0 Then GoTo ExitWkseqmInitFunction
'Device Name Registration:
' TODO: fill in the devices name which make sense for your server
For i = 0 To WkseqmNumDevices% - 1
  rc = srvControl.EqpRegisterModule("DEVICE" + Trim$(str$(i)), i)
  If rc Then GoTo ExitWkseqmInitFunction
Next
' Everything should be okay, check for deeper problems:
WkseqmInitFunction = SRVSTATUS
'
ExitWkseqmInitFunction:
If rc Then
  WkseqmInitFunction = rc
ElseIf id < 0 Then
  WkseqmInitFunction = -id
End If
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' include WkseqmEqpFcn in the EqpFcn event of the Srv control
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Sub WkseqmEqpFcn(srvControl As Srv, ByVal devName As String, ByVal devProperty As String, ByVal outArrayLen As Long, ByVal inArrayLen As Long, ByVal devAccess As Integer)
Dim i As Integer
Dim devNr As Integer
Dim lclTemperature As Single
Dim lclAmplitude As Single
' If your properties make use of the device number associated with the device name:
devNr = srvControl.EqpGetModuleNumber(devName)
If devNr < 0 Then
    srvControl.EqpSetCompletion illegal_equipment_number, ""
    Exit Sub
End If
' check which property was asked for:
Select Case devProperty
  Case "Temperature"
    If (devAccess And CA_WRITE) Then
      srvControl.EqpSetCompletion illegal_read_write, ""
      Exit Sub
    End If
    If inArrayLen > 0 Then
      ' Get the incoming data:
      srvControl.EqpRecvData lclTemperature
      ' Do range checking:
      If lclTemperature > PRP_TEMPERATURE_UPRLIMIT Then
        srvControl.EqpSetCompletion out_of_range, ""
        Exit Sub
      End If
      If lclTemperature < PRP_TEMPERATURE_LWRLIMIT Then
        srvControl.EqpSetCompletion out_of_range, ""
        Exit Sub
      End If
      ' Copy incoming data into global data buffer
      prpTemperature = lclTemperature
    End If
    If (outArrayLen > 0) And (outArrayLen <= (PRP_TEMPERATURE_SIZE - devNr)) Then
      srvControl.EqpSendData prpTemperatureBuffer(devNr)
    Else
      srvControl.EqpSetCompletion dimension_error, ""
      Exit Sub
    End If
  Case "Amplitude"
    If (devAccess And CA_WRITE) Then
      If inArrayLen = 0 Then
        srvControl.EqpSetCompletion illegal_read_write, ""
        Exit Sub
      End If
      ' Get the incoming data:
      srvControl.EqpRecvData lclAmplitude
      ' Do range checking:
      If lclAmplitude > PRP_AMPLITUDE_UPRLIMIT Then
        srvControl.EqpSetCompletion out_of_range, ""
        Exit Sub
      End If
      If lclAmplitude < PRP_AMPLITUDE_LWRLIMIT Then
        srvControl.EqpSetCompletion out_of_range, ""
        Exit Sub
      End If
      ' Copy incoming data into global data buffer
      prpAmplitude = lclAmplitude
    End If
    If (outArrayLen > 0) And (outArrayLen <= (PRP_AMPLITUDE_SIZE - devNr)) Then
      srvControl.EqpSendData prpAmplitudeBuffer(devNr)
    Else
      srvControl.EqpSetCompletion dimension_error, ""
      Exit Sub
    End If
  Case "Sine"
    If (devAccess And CA_WRITE) Then
      srvControl.EqpSetCompletion illegal_read_write, ""
      Exit Sub
    End If
    If outArrayLen > 0 Then
      srvControl.EqpSendData prpSineBuffer
    End If
  Case Else
    srvControl.EqpSetCompletion illegal_property, ""
End Select
' If it's made it this far, it's a success:
srvControl.EqpSetCompletion 0, ""
End Sub

You will notice that there are several 'TODO' statements, which indicate where you should concentrate your coding efforts. Most noteably, the 'io' routine does nothing but simulate actual data io, and you should of course change this routine to correspond to your actual data io. The io routine is refered to as a 'background' routine and in this case the wizard gave it the name WkseqmBackgroundFunction().

For our example, you should change this background io routine to generate a sine curve for the property 'Sine' and a temperature array for the property 'Temperature'. Note that the background routine doesn't touch the Amplitude. This is because we declared it as a WRITE/READ property, so the Wizard left it alone. You should set this to some reasonable initial value in the initialization routine. We've declared Amplitude to be a CHANNEL array so that there is a different stored and settable value for each Sine Curve.

So do the following: In the initialization routine fill in the Amplitude array with some value, say '200' by adding code such as:

Dim i As Integer

For i = 0 To PRP_AMPLITUDE_SIZE - 1
  prpAmplitudeBuffer(i) = 200
Next

Now the initialization routine should look something like:

Function WkseqmInitFunction(srvControl As Srv, expName As String) As Integer
Dim rc As Integer, id As Integer

Dim i As Integer

For i = 0 To PRP_AMPLITUDE_SIZE - 1
  prpAmplitudeBuffer(i) = 200
Next

' FECNAME: either read from file 'fecid.csv' or user API :
'rc = RegisterFecName(fecname$, desc$, loc$, hdw$, resp$, portoffset)

' DEVICE SERVER: device server parameters
srvControl.EqpNumberModules = WkseqmNumDevices%
srvControl.EqpName = WkseqmLocalName$
srvControl.ExportName = expName
'now enable the server
srvControl.Enabled = True
rc = srvControl.EqpStatus
If rc Then GoTo ExitWkseqmInitFunction
'Property Registration:
id = srvControl.EqpRegisterPropertyEx("Temperature", PRP_TEMPERATURE_INSIZE, CF_FLOAT, "", PRP_TEMPERATURE_SIZE, CF_FLOAT, "", CA_READ, "[0:100 'C][CHANNEL]Temperature of the Sine generato")
If id < 0 Then GoTo ExitWkseqmInitFunction
id = srvControl.EqpRegisterPropertyEx("Amplitude", PRP_AMPLITUDE_INSIZE, CF_FLOAT, "", PRP_AMPLITUDE_SIZE, CF_FLOAT, "", CA_READ + CA_WRITE, "[0:500 V][CHANNEL]Sine Curve Amplitude")
If id < 0 Then GoTo ExitWkseqmInitFunction
id = srvControl.EqpRegisterPropertyEx("Sine", 0, CF_NULL, "", PRP_SINE_SIZE, CF_FLOAT, "", CA_READ, "[-500:500 V]Sine Curve")
If id < 0 Then GoTo ExitWkseqmInitFunction
'Device Name Registration:
' TODO: fill in the devices name which make sense for your server
For i = 0 To WkseqmNumDevices% - 1
  rc = srvControl.EqpRegisterModule("DEVICE" + Trim$(str$(i)), i)
  If rc Then GoTo ExitWkseqmInitFunction
Next
' Everything should be okay, check for deeper problems:
WkseqmInitFunction = SRVSTATUS
'

ExitWkseqmInitFunction:
If rc Then
  WkseqmInitFunction = rc
ElseIf id < 0 Then
  WkseqmInitFunction = -id
End If
End Function

Go to the background routine WkseqmBackgroundFunction() to fill in a global sine array and Temperature as we did for the buffered server case. The background routine should look something like:

Function WkseqmBackgroundFunction(srvControl As Srv) As Integer
Dim i As Integer
' TODO: put your IO or other background activity here

' Clear alarms at start of IO task (see if they come back)
  srvControl.EqpClearAlarm -1

' TODO: replace WkseqmSimulateData() with your own data acquisition
'       and manipulation routines
  For i = 0 To PRP_TEMPERATURE_SIZE - 1
     prpTemperatureBuffer(i) = 20 + 5 * Rnd
  Next
  For i = 0 To PRP_SINE_SIZE - 1
     prpSineBuffer(i) = Sin((i * 6.2832) / 1024) + 0.25 * Rnd
  Next
' make use of SetAlarm() as needed :

' srvControl.EqpSetAlarm devNr, almCode, CADDR(almData(0)), 0

' set error codes as needed:
  WkseqmBackgroundFunction = 0
End Function

We're going to save some memory space concerning all of our sine curves (one for each of the 100 devices!). So we have made the Amplitude a CHANNEL array as the read-back property. So we'll just multiply the same sine array by the corresponding amplitude when the property gets called. We'll then need a temporary read-back buffer (we'll see why in a minute). So create this as a global buffer of the same dimensions as the prpSineBuffer() array.

Global rbSineBuffer(PRP_SINE_SIZE - 1) As Single

Now create a routine which scales an array by a factor (we'll use this to multiply the sine buffer by the corresponding amplitude).

Sub scaleArray(ByVal scaleFactor As Single, myArray() As Single)
  Dim i As Integer
  For i = LBound(myArray) To UBound(myArray)
    myArray(i) = myArray(i) * scaleFactor
  Next
End Sub

And now it's on the equipment module. What should happen when a property is accessed? For property "Temperature", the generated code will server our purposes just fine. For property "Amplitude", the input amplitude value during a write operation should actually go directly into the Amplitude channel array buffer, so assign it to the appropriate element as follows (instead of assigning to the wizard generated variable 'prpAmplitude'):

      prpAmplitudeBuffer(devNr) = lclAmplitude

Finally, when "Sine" gets called, copy the current sine buffer into the read-back buffer and scale it with the amplitude of the device called.

    If outArrayLen > 0 Then
      MEMCPY rbSineBuffer(0), prpSineBuffer(0), PRP_SINE_SIZE * 4
      scaleArray prpAmplitudeBuffer(devNr), rbSineBuffer()
      srvControl.EqpSendData rbSineBuffer
    End If

So we have just tweaked the generated code to serve our purposes for this example. This should behave much the same as in the buffered server example, except that here we return a sine curve for any device called and not just those which have been 'pushed'. We've also cheated a bit on the amount of reserved space, as we're using the same sine buffer scaled to a differed amplitude for each of the 100 devices. This is opposed to the buffered server case, where 100 different buffers were reserved up front.

In this case we have only reserved two buffers for the sine curve, an 'io' buffer and a read-back buffer.

One more thing before we try this out. Actually, if you've already tried this out you may have noticed when browsing the property "SINE" in the instant client, it did not know that this should be a spectrum array and therefore offered a simple text display. Why did this happen? This might have happened if you did not comment out the property registration inside the initializatin routine, and once again the wizard wasn't able to pass this information on to the generated code. If this is the case, add some x-axis units to the property sine, something like:

id = srvControl.EqpRegisterPropertyEx("Sine", 0, CF_NULL, "", PRP_SINE_SIZE, CF_FLOAT, "", CA_READ, "[-500:500 V][0:1024 usec]Sine Curve")

Your equipment module code should now look something like the following:

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' WkseqmModule.bas    REVISION HISTORY:
' Generated by SRVWIZARD on Thu Apr 20 16:10:52 2006
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Global variables and constants
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private Const WkseqmLocalName$ = "WKSEQM"
Private Const WkseqmNumDevices% = 100
Global Const PRP_TEMPERATURE_SIZE = 100
Global Const PRP_TEMPERATURE_UPRLIMIT = 100
Global Const PRP_TEMPERATURE_LWRLIMIT = 0
Global Const PRP_AMPLITUDE_SIZE = 100
Global Const PRP_AMPLITUDE_INSIZE = 1
Global Const PRP_AMPLITUDE_UPRLIMIT = 500
Global Const PRP_AMPLITUDE_LWRLIMIT = 0
Global Const PRP_SINE_SIZE = 1024
Global Const PRP_SINE_UPRLIMIT = 500
Global Const PRP_SINE_LWRLIMIT = -500

Global prpTemperatureBuffer(PRP_TEMPERATURE_SIZE - 1) As Single
Global prpTemperatureIn As Single
Global prpAmplitudeBuffer(PRP_AMPLITUDE_SIZE - 1) As Single
Global prpAmplitudeIn As Single
Global prpSineBuffer(PRP_SINE_SIZE - 1) As Single
Global rbSineBuffer(PRP_SINE_SIZE - 1) As Single

Sub scaleArray(ByVal scaleFactor As Single, myArray() As Single)
  Dim i As Integer
  For i = LBound(myArray) To UBound(myArray)
    myArray(i) = myArray(i) * scaleFactor
  Next
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' WkseqmSimulateData is a useful routine for test servers
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function WkseqmSimulateData(Offset As Double, range As Double) As Double
WkseqmSimulateData = Offset + range * Rnd
End Function

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' include WkseqmBackgroundFunction in a Timer to perform background IO, etc.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function WkseqmBackgroundFunction(srvControl As Srv) As Integer
Dim i As Integer
' TODO: put your IO or other background activity here

' Clear alarms at start of IO task (see if they come back)
  srvControl.EqpClearAlarm -1

' TODO: replace WkseqmSimulateData() with your own data acquisition
'       and manipulation routines
  For i = 0 To PRP_TEMPERATURE_SIZE - 1
     prpTemperatureBuffer(i) = 20 + 5 * Rnd
  Next
  For i = 0 To PRP_SINE_SIZE - 1
     prpSineBuffer(i) = Sin((i * 6.2832) / 1024) + 0.25 * Rnd
  Next
' make use of SetAlarm() as needed :

' srvControl.EqpSetAlarm devNr, almCode, CADDR(almData(0)), 0

' set error codes as needed:
  WkseqmBackgroundFunction = 0
End Function

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' include WkseqmInitFunction in Form_load (for instance) to initialize the server
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function WkseqmInitFunction(srvControl As Srv, expName As String) As Integer
Dim rc As Integer, id As Integer

Dim i As Integer

For i = 0 To PRP_AMPLITUDE_SIZE - 1
  prpAmplitudeBuffer(i) = 200
Next

' FECNAME: either read from file 'fecid.csv' or user API :
'rc = RegisterFecName(fecname$, desc$, loc$, hdw$, resp$, portoffset)

' DEVICE SERVER: device server parameters
srvControl.EqpNumberModules = WkseqmNumDevices%
srvControl.EqpName = WkseqmLocalName$
srvControl.ExportName = expName
'now enable the server
srvControl.Enabled = True
rc = srvControl.EqpStatus
If rc Then GoTo ExitWkseqmInitFunction
'Property Registration:
id = srvControl.EqpRegisterPropertyEx("Temperature", 0, CF_NULL, "", PRP_TEMPERATURE_SIZE, CF_FLOAT, "", CA_READ, "[0:100 'C][CHANNEL]Temperature of the Sine generato")
If id < 0 Then GoTo ExitWkseqmInitFunction
id = srvControl.EqpRegisterPropertyEx("Amplitude", PRP_AMPLITUDE_INSIZE, CF_FLOAT, "", PRP_AMPLITUDE_SIZE, CF_FLOAT, "", CA_READ + CA_WRITE, "[0:500 V][CHANNEL]Sine Curve Amplitude")
If id < 0 Then GoTo ExitWkseqmInitFunction
id = srvControl.EqpRegisterPropertyEx("Sine", 0, CF_NULL, "", PRP_SINE_SIZE, CF_FLOAT, "", CA_READ, "[-500:500 V][0:1024 usec]Sine Curve")
If id < 0 Then GoTo ExitWkseqmInitFunction
'Device Name Registration:
' TODO: fill in the devices name which make sense for your server
For i = 0 To WkseqmNumDevices% - 1
  rc = srvControl.EqpRegisterModule("DEVICE" + Trim$(str$(i)), i)
  If rc Then GoTo ExitWkseqmInitFunction
Next
' Everything should be okay, check for deeper problems:
WkseqmInitFunction = SRVSTATUS
'

ExitWkseqmInitFunction:
If rc Then
  WkseqmInitFunction = rc
ElseIf id < 0 Then
  WkseqmInitFunction = -id
End If
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' include WkseqmEqpFcn in the EqpFcn event of the Srv control
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Sub WkseqmEqpFcn(srvControl As Srv, ByVal devName As String, ByVal devProperty As String, ByVal outArrayLen As Long, ByVal inArrayLen As Long, ByVal devAccess As Integer)
Dim i As Integer
Dim devNr As Integer
Dim lclAmplitude As Single
' If your properties make use of the device number associated with the device name:
devNr = srvControl.EqpGetModuleNumberEx(devName, devProperty)
If devNr < 0 Then
    srvControl.EqpSetCompletion illegal_equipment_number, ""
    Exit Sub
End If
' check which property was asked for:
Select Case devProperty
  Case "Temperature"
    If (devAccess And CA_WRITE) Then
      srvControl.EqpSetCompletion illegal_read_write, ""
      Exit Sub
    End If
    If (outArrayLen > 0) And (outArrayLen <= (PRP_TEMPERATURE_SIZE - devNr)) Then
      srvControl.EqpSendData prpTemperatureBuffer(devNr)
    Else
      srvControl.EqpSetCompletion dimension_error, ""
      Exit Sub
    End If
  Case "Amplitude"
    If (devAccess And CA_WRITE) Then
      If inArrayLen = 0 Then
        srvControl.EqpSetCompletion illegal_read_write, ""
        Exit Sub
      End If
      ' Get the incoming data:
      srvControl.EqpRecvData lclAmplitude
      ' Do range checking:
      If lclAmplitude > PRP_AMPLITUDE_UPRLIMIT Then
        srvControl.EqpSetCompletion out_of_range, ""
        Exit Sub
      End If
      If lclAmplitude < PRP_AMPLITUDE_LWRLIMIT Then
        srvControl.EqpSetCompletion out_of_range, ""
        Exit Sub
      End If
      ' Copy incoming data into global data buffer
      prpAmplitudeBuffer(devNr) = lclAmplitude
    End If
    If (outArrayLen > 0) And (outArrayLen <= (PRP_AMPLITUDE_SIZE - devNr)) Then
      srvControl.EqpSendData prpAmplitudeBuffer(devNr)
    Else
      srvControl.EqpSetCompletion dimension_error, ""
      Exit Sub
    End If
  Case "Sine"
    If (devAccess And CA_WRITE) Then
      srvControl.EqpSetCompletion illegal_read_write, ""
      Exit Sub
    End If
    If outArrayLen > 0 Then
      MEMCPY rbSineBuffer(0), prpSineBuffer(0), PRP_SINE_SIZE * 4
      scaleArray prpAmplitudeBuffer(devNr), rbSineBuffer()
      srvControl.EqpSendData rbSineBuffer
    End If
  Case Else
    srvControl.EqpSetCompletion illegal_property, ""
    Exit Sub
End Select
' If it's made it this far, it's a success:
srvControl.EqpSetCompletion 0, ""
End Sub

You should now compile and run this server code and see that it basically behaves like the buffered server at this stage of development.

If you stick to the .csv configuration files, all of the lessons learned in the case of the buffered server apply here as well. Of course all features can be accessed via API calls instead. For instance, calls to RedirectProperty() or RedirectDeviceName() can be used at intialization time (in the initialization routine) to redirect calls to specific devices or properties. The one feature you will have to program in yourself is 'Property Scheduling.

Property Scheduling

To try this out, add a call to the scheduler when the amplitude changes. This can only change because a caller changed it, so this happens directly inside the equipment module. Add a boolean variable amplitudeChanged inside the equipment module and set it to true when someone sets the amplitude.

  Case "Amplitude"
    If (devAccess And CA_WRITE) Then
      If inArrayLen = 0 Then
        srvControl.EqpSetCompletion illegal_read_write, ""
        Exit Sub
      End If
      ' Get the incoming data:
      srvControl.EqpRecvData lclAmplitude
      ' Do range checking:
      If lclAmplitude > PRP_AMPLITUDE_UPRLIMIT Then
        srvControl.EqpSetCompletion out_of_range, ""
        Exit Sub
      End If
      If lclAmplitude < PRP_AMPLITUDE_LWRLIMIT Then
        srvControl.EqpSetCompletion out_of_range, ""
        Exit Sub
      End If
      ' Copy incoming data into global data buffer
      prpAmplitudeBuffer(devNr) = lclAmplitude
      amplitudeChanged = True
    End If
    If (outArrayLen > 0) And (outArrayLen <= (PRP_AMPLITUDE_SIZE - devNr)) Then
      srvControl.EqpSendData prpAmplitudeBuffer(devNr)
    Else
      srvControl.EqpSetCompletion dimension_error, ""
      Exit Sub
    End If

If the call is successful, check whether amplitudeChanged is 'true' and if so call the scheduler:

srvControl.EqpSetCompletion 0, ""
If amplitudeChanged Then srvControl.ScheduleProperty "Amplitude"

Now any clients monitoring the "Amplitude" property will be updated immediately with the new value.

Why did we call the scheduler after setting the completion code to 0? It turns out this is only necessary for Windows ActiveX Servers (i.e. Visual Basic) but the reason has to do with the event queue maintained for the equipment module. Calling the scheduler will cause the equipment module to be immediately reentered if there are any clients listening on property "Amplitude" (in this case). More often you will want to call the scheduler in your io loop when important data have changed.

Besides maybe calling the scheduler when data have changed, you will more often than not want to make a note of the timestamp when the data have changed and use this timestamp when a caller asks for the data. Then the caller will now how old the data really are. To try this out, make a global variable sineCurveTimestamp As Double and then get back into your background routine and set this variable when you modifiy the sine curve. Your background routine probably looks something like the following:

Function WkseqmBackgroundFunction(srvControl As Srv) As Integer
Dim i As Integer
' TODO: put your IO or other background activity here

' Clear alarms at start of IO task (see if they come back)
  srvControl.EqpClearAlarm -1

' TODO: replace WkseqmSimulateData() with your own data acquisition
'       and manipulation routines
  For i = 0 To PRP_TEMPERATURE_SIZE - 1
     prpTemperatureBuffer(i) = 20 + 5 * Rnd
  Next
  For i = 0 To PRP_SINE_SIZE - 1
     prpSineBuffer(i) = Sin((i * 6.2832) / 1024) + 0.25 * Rnd
  Next
  sineCurveTimestamp = makeDataTimeStamp
' make use of SetAlarm() as needed :

' srvControl.EqpSetAlarm devNr, almCode, CADDR(almData(0)), 0

' set error codes as needed:
  WkseqmBackgroundFunction = 0
End Function

Now go back to your equipment module and set the data timestamp to the value of sineCurveTimestamp:

  Case "Sine"
    If (devAccess And CA_WRITE) Then
      srvControl.EqpSetCompletion illegal_read_write, ""
      Exit Sub
    End If
    If outArrayLen > 0 Then
      srvControl.setDataTimeStamp sineCurveTimestamp
      MEMCPY rbSineBuffer(0), prpSineBuffer(0), PRP_SINE_SIZE * 4
      scaleArray prpAmplitudeBuffer(devNr), rbSineBuffer()
      srvControl.EqpSendData rbSineBuffer
    End If

Make sure you set the timestamp before you call the EqpSendData method.

Note:
In the case of the buffered server, the data timestamp is automatically set and recorded when a 'push' call is made.

Generated for TINE API by  doxygen 1.5.8