The NewData Module
While it is true that there is more than one method for reading values from an I/O driver, use of the NewData module is recommended for all new tags.
If your tag includes a module named NewData, with the structure similar to that shown in the following example, the I/O driver will use it to send values from the device to the tag.
NewData should have the following five parameters where the last two, Quality and ServiceSync, are optional.
NewData
(
Addr { Address we asked for };
TimeStamps { Single value or 1D array of UTC timestamps };
Data { Single value or 1D array of unscaled data
values };
Quality { Single value or 1D array of Quality data };
ServiceSync { used to denote that the NewData call was made
as a result of a server synchronization (the
server has just started up and this
data has been taken from a running server) };
)
For purposes of comparison, the body of the NewData modules from both the Analog Status tag and the Digital Status tag are reprinted here.
Items to note are:
- The RawValue is found in the first item of the Data array.
- The NeedValueUpdate variable scopes to the parent tag module. This is used to alert the tag to the fact that a new value has been received and that the tag's value should be updated.
- The largest portion of the module provides support for drivers that send history values. These are scaled and logged as required.
NewData() example for reading analog values:
<
{========================== NewData ===============================}
{ Subroutine that receives and processes incoming data from driver }
{ RefreshData in VTSDriver guarantees that TimeStamps is a valid }
{ time stamp or an array of valid time stamps regardless of the }
{ validity of Data }
{==================================================================}
NewData
(
Addr { Address we asked for };
TimeStamps { Single value or 1D array of UTC timestamps };
Data { Single value or 1D array of unscaled data
values };
)
[
I { General counting variable };
HistSizeRet { Size of history list returned };
MaxHistTS { Highest TS in history list };
LastHistPos { Index of newest data in history list };
HistoryValues { Scaled history array to pass to data logger };
HistImgTimeStamps { For history use, 1D array, image of
TimeStamps };
HistImgData { For history use, 1D array, image of
Data };
NeedValueUpdate = FALSE { TRUE if the tag needs to recalculate
Value };
]
Main [
If 1;
[
IfElse (PickValid(Addr == CurrValAddr, 0), Execute(
{ Current value data }
RawValue = Data[0];
RawTS = PickValid(TimeStamps[0], CurrentTime(1));
NeedValueUpdate = TRUE;
);
{ Else } IfThen (PickValid(Addr == HistoryAddr, 0),
{ History data might have been Recv'd as single value or as an array }
{ Size of history array }
IfElse(Valid(HistSizeRet = ArraySize(Data, 0)), Execute(
{ Data is an array }
HistImgTimeStamps = TimeStamps;
HistImgData = Data;
);
{ Else } Execute(
{ Data is a single value so copy to a single-value array }
HistSizeRet = 1;
HistImgTimeStamps = New(HistSizeRet);
HistImgData = New(HistSizeRet);
HistImgTimeStamps[0] = TimeStamps;
HistImgData[0] = Data;
));
{ If the current tag value comes from history }
IfThen (ValueFromHist,
{ Extract RawValue & RawTS from history list if no
Current Value address defined }
{ Find the newest history TS }
MaxHistTS = AMax(HistImgTimeStamps[0], HistSizeRet);
{ Only use it if greater than current value TS }
IfThen (MaxHistTS > PickValid(TimeStamp, 0),
{ Where is this in the array ? }
LastHistPos = Lookup(HistImgTimeStamps[0],
HistSizeRet, MaxHistTS);
{ Set our values to the newest history value }
RawValue = HistImgData[LastHistPos];
{ Set the time as well }
RawTS = HistImgTimeStamps[LastHistPos];
NeedValueUpdate = TRUE;
);
);
{ Scale the data & log it }
HistoryValues = New(HistSizeRet);
I = 0;
WhileLoop(I < HistSizeRet,
HistoryValues[I] = Scale(HistImgData[I], UnscaledMin,
UnscaledMax, ScaledMin, ScaledMax);
I++;
);
{ Log history values }
\HistorianManager\WriteHistory(Root, HistImgTimeStamps,
HistoryValues);
));
IfThen(NeedValueUpdate && !Valid(ManualValue),
UpdateValue(ValueFromHist);
{ The parameter tells UpdateValue to not log Value if it came
from a history value; this has been logged already }
);
Return(0);
]
]
{ End of AnalogStatus\NewData }
>
NewData() example for reading digital values:
<
{========================= NewData ==================================}
{ Subroutine that receives and processes incoming channel data from }
{ driver RefreshData in VTSDriver guarantees that TimeStamps is a }
{ valid time stamp or an array of valid time stamps regardless of }
{ the validity of Data }
{====================================================================}
NewData
(
Addr { Address we asked for (not used) };
TimeStamps { Single value or 1D array of timestamps };
Data { Single value or 1D array of unscaled data
values };
Quality { Single value or 1D array of Quality data };
)
[
HistSizeRet { Size of history list returned };
MaxHistTS { Highest TS in history list };
LastHistPos { Index of newest data in history list };
HistoryValues { Scaled history array to pass to data logger };
HistImgTimeStamps { For history use, 1D array, image of
TimeStamps };
HistImgData { For history use, 1D array, image of
Data };
]
Main [
If 1;
[
IfElse (PickValid(Addr == Bit0Address, 0), Execute(
{ If b0 address }
RawValue0 = Data[0];
RawTS = PickValid(TimeStamps[0] - TimeZone(0), CurrentTime());
);
{ Else - b1 address } IfElse(PickValid(Addr == b1ValAddr, 0), Execute(
RawValue1 = Data[0];
RawTS = PickValid(TimeStamps[0] - TimeZone(0), CurrentTime());
);
{ Else } IfThen (PickValid(Addr == HistoryAddr, 0), { History Address }
{ History data might have been Recvd as single value or as an array }
{ Size of history array }
IfElse(Valid(HistSizeRet = ArraySize(Data, 0)), Execute(
{ Data is an array }
HistImgTimeStamps = TimeStamps;
HistImgData = Data;
);
{ Else } Execute(
{ Data is a single value so copy to a single-value array }
HistSizeRet = 1;
HistImgTimeStamps = New(HistSizeRet);
HistImgData = New(HistSizeRet);
HistImgTimeStamps[0] = TimeStamps;
HistImgData[0] = Data;
));
{ If the current tag value comes from history }
IfThen (ValueFromHist,
{ Extract RawValue & RawTS from history list if required }
{ Find the highest history TS }
MaxHistTS = AMax(HistImgTimeStamps[0], HistSizeRet);
{ Only use it if greater than current value TS }
IfThen (MaxHistTS - TimeZone(0) > PickValid(TimeStamp, 0),
{ Where is this in the array ? }
LastHistPos = Lookup(HistImgTimeStamps[0], HistSizeRet,
MaxHistTS);
{ Set our value to the newest history value }
RawValue0 = HistImgData[LastHistPos];
RawTS = HistImgTimeStamps[LastHistPos] - TimeZone(0);
);
);
{ Invert the data & log it }
HistoryValues = New(HistSizeRet);
{ Make a copy of the data array. }
ArrayOp2(HistoryValues[0], HistImgData[0], HistSizeRet, 0);
{ Some data massage required... }
{ Force all data to 0/1 values.
This first step actually returns the inverted data. }
ArrayOp1(HistoryValues[0], HistSizeRet, 0, 12 {==});
IfThen (!InvertInput,
{ Invert the data back to normal if not inverted }
ArrayOp1(HistoryValues[0], HistSizeRet, 0, 12 {==});
);
{ Log history values }
\HistorianManager\WriteHistory(Root, HistImgTimeStamps,
HistoryValues);
)));
Return(0);
]
]
{ End of NewData }
>