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