lib point
Overview
The point library is used to manage the standard functionality of point records that model sensors, commands, and setpoints. Important features of this library are management of the 16-level priority array for writable points and local history collection.
Point Cur
The term cur indicates synchronization of a point's current real-time value. By real-time we typically mean freshness within the order of of a few seconds. By convention a fooCur
tag is used to configure the address used by the connector for current value synchronization. If a point has a real-time current value, then it must be annotated with the cur
marker tag.
The following transient tags are used to model the current value and status:
curVal
: current value of the point as Number, Bool, or StrcurStatus
: ok, stale, down, fault, disabled, unknown, remoteDown, remoteFault, remoteDisabled, or remoteUnknowncurErr
: error message if curStatus indicated error- additional details such as last ok/fail times, curErr stack trace, etc are available in the connector app via the "Details" command
If the point's connector is not in the "ok" state, then it inherits its error state for curStatus
.
Because the current value might be changing several times a second, the tags above are managed transiently. This means they are not written to disk, nor persisted between server restarts. Changes to these tags are written to Folio using transient diffs.
The current value of points are synchronized one of two ways:
- one-time synchronization is made using the connectors
fooSyncCur()
function - the point is added to a watch
Watches indicate an ongoing, live subscription of the point. Depending on the remote system this might involve an actual subscription over the network protocol. But for simpler protocols which only support polling, this means frequent polling of the point until its watch expires.
Point Writes
Writable points allow for control an output point or setpoint in a remote system via a connector. Writable points are modeled on the BACnet 16-level priority array with a relinquish default which effectively acts as level 17.
Any point rec can be turned into a writable point by adding the writable
tag. Writable points should be cmd
or sp
points (never sensor
points). Writable points must be mapped to a connector to actually command anything. By convention a fooWrite
tag is used to configure the writable address.
The following levels have special behavior:
- Level 1: highest priority reserved for emergency overrides
- Level 8: manual override with ability to set timer to expire back to auto
- Default: implicitly acts as level 17 for relinquish default
The priority array provides for contention resolution when many different control applications are vying for control of a given point. Low level applications like scheduling typically control levels 14, 15, or 16. Then users can override at level 8. But a higher levels like 2 to 7 can be used to trump a user override (for example a demand response energy routine that requires higher priority).
The effective value to write is resolved by starting at level 1 and working down to relinquish default to find the first non-null value. It is possible for all levels to be null, in which case the overall write output is null (which in turn may be auto/null to another system). Anytime a null value is written to a priority level, we say that level has been set to auto or released (this allows the next highest level to take command of the output).
Whenever the priority array's effective value is modified, then the following tags are transiently updated:
writeVal
: this is the current "winning" value of the priority array, or if this tag is missing then the winning value is nullwriteLevel
: number from 1 to 17 indicate the winning priority array levelwriteStatus
: status of the connector's ability to write the last value: ok, disabled, down, fault. The special status "unbound" indicates a writable point that is not bound to an actual connectorwriteErr
: indicates the error message if writeStatus is error condition
Because the write value might be changing several times a second, the tags above are managed transiently. This means they are not written to disk, nor persisted between server restarts. Changes to these tags are written to Folio using transient diffs. They are recalculated on reboot.
Level 1, level 8, and default are special in that they are persistent. Changes to these levels will persist their value in Folio using the tags write1
, write8
, and writeDef
. Timed overrides are not persistent. All other priority levels are transient - it is expected that control applications rewrite these levels on reboot.
The following Axon functions are used to write points:
There is also a pointWrite()
function which allows forced writing to any level. But in general user interfaces should restrict access to the functions above to ensure users stick with levels 1, 8, and relinquish default. The pointWriteArray()
function is used to read the current state of a point's write array.
The functions above all require the admin
user permission. User interfaces should use the pointOverrideCommand()
function. This function allows the framework to grant override permissions to specific points. In SkySpark this is configured with the pointOverrideAccess tag (not supported in Haxall).
Point His
Points which are historized are annotated with the his
marker tag. Any point may be historized in one of two ways:
- conn sync: if the remote system supports its own data logging facilities, then the point's connector should implement history synchronization
- collect: the point history may be be collected locally using COV or interval sampling
The following tags are used to model the current value and status:
hisStatus
: ok, down, fault, disabled, pending, syncing, unknownhisErr
: error message if hisStatus indicated error- additional details such as last ok/fail times, hisErr stack trace, etc are available in the connector app via the "Details" command
If the point's connector is not in the "ok" state, then it inherits its error state for hisStatus
. History synchronization is performed using the connSyncHis()
function which must be manually scheduled using the task lib.
His Collect
Many remote systems may not support local historization of their data points. Or even when supported it may not be enabled for the desired points. In these cases, you can setup history collection locally in the Haxall server. Local collection requires that the a connector (or some application) is updating the curVal
tag.
History collection is setup in two ways:
hisCollectCov
: setups up change of value collectionhisCollectInterval
: setups up interval sample collection
You can combine these tags to perform both COV and interval collection. A recommended practice is to setup bool/enum/ and numeric setpoints using COV, but also with an interval of 1day. For sampled numeric sensor values an interval of 1min to 15min is recommended with a COV tolerance to log rapid swings in the data.
The following tags may be used to tune local history collection:
hisCollectNA
: used to log NA when status is not "ok"hisCollectCovRateLimit
: used to throttle rapidly changing datahisCollectWriteFreq
: used to buffer items in RAM before flushing to Folio
IMPORTANT NOTE: it is always preferable to use remote collection with history synchronization instead of local collection when possible. Remote collection has the following advantages:
- is always more efficient
- ensures no data is lost during network outages
- is much easier to tune since increasing the sync frequency does not result in data loss
Enum Meta
Connectors communicating with remote systems often use integer codes instead of string names for enumerated points. The mappings between string names and integer codes is managed by a singleton record with the enumMeta
marker tag. Each string tag defines an enum definition. The name of the tag defines the enum identifier. The value is a string formatted as a Zinc grid with a name
and code
column.
Here is an example:
dis:"Enum Definitions" enumMeta switchState: ver:"3.0" name,code "off",0 "on",1 speed: ver:"3.0" name,code "unknown",-1 "off",0 "slow",1 "fast",2
The record is annotated with the enumMeta
marker tag. There should only be one record in each project with this tag. In our example above there are two enum defs named "switchState" and "speed". We can use these names with point conversion as follows:
// convert 0 to "off", 1 to "slow" and 2 to "fast" enumNumberToStr(speed) // convert "off" to 0, "slow" to 1, and "fast" to 2 enumStrToNumber(speed)
A given name or given code may be double mapped, in which case the first mapping is defined as the normalized mapping. Consider this example:
// definition grid ver:"3.0" name,code "alpha",0 "alpha",1 "beta",99 "gamma",99 // the mappings from the grid above are defined as follows alpha => 0 beta => 99 gamma => 99 0 => alpha 1 => alpha 99 => beta
An enum definition may also be used to map to/from string/booleans. When mapping from a name to a boolean, any non-zero code is mapped as true and zero code as false. The first non-zero and zero name/code pair defines the mapping from boolean to string. For example:
// definition grid ver:"3.0" name,code off,0 disabled,0 slow,1 fast,2 // mappings for booleans off => false disabled => false slow => true fast => true false => off // first zero code true => slow // first non-zero code
The following functions may be used to query the enum definitions in effect:
enumDefs()
: query the list of enum defs defined by enumMetaenumDef()
: query name/code mapping of a specific enum def
Point Conversion
Point conversions are used to convert between the representations of external systems and the normalized local representations. They are used by the connector framework with the curConvert
, writeConvert
, and hisConvert
tags. Use cases include:
- mathematical conversions of raw modbus registers
- type conversions
- enum mappings between string/number and string/boolean
- sensor voltage resets
- sensor thermistor tables
- unit conversion from fieldbus devices
You can test conversions via the pointConvert()
function.
Examples:
// multiply raw value by 100 * 100 // multiply raw value by 100 and add 75 * 100 + 75 // convert °C to °F °C => °F // replace unit without conversion like 'as()' Axon function as(°F) // raise to the power of given exponent pow(-1) // reset 5-10 to 0-100 reset(5, 10, 0, 100) // thermistor table; run pointThermistorTables() for full list thermistor(10k-2) // invert bool true -> false, vice versa invert() // swap endianness of unsigned two byte integer u2SwapEndian() // swap endianness of unsigned four byte integer u4SwapEndian() // zero to false, non-zero to true numberToBool() // parsing and formatting numbers strToNumber() // checked defaults to true strToNumber(true) // throws exception is not valid number strToNumber(false) // evaluates to null if not a valid number hexToNumber() // parse hex string numberToStr() // convert number to a string numberToHex() // convert number to a hex string // bool functions boolToNumber() // convert false to 0 and true to 1 boolToNumber(2, 4) // convert false to 2 and true to 4 // string functions lower() // convert string to lowercase (ASCII only) upper() // convert string to uppercsae (ASCII only) strReplace('x', '_') // replace "x" with "_" strReplace(' ', '') // replace space with the empty string // enumeration conversions where 'x' is tag on enumMeta; if 'x' is // "self" then use 'enum' tag on the record itself to map zero based ordinals enumStrToNumber(x) // checked defaults to true enumStrToNumber(x,true) // throws exception is mapping not defined enumStrToNumber(x,false) // evaluates to null if mapping not defined enumNumberToStr(x) enumStrToBool(x) enumBoolToStr(x) // elvis operator will pass thru non-null values, or // convert null to given value ?: true // non-null value or Bool true ?: false // non-null value or Bool false ?: NA // non-null value or NA ?: 123 // non-null value or Number 123 ?: foo // non-null value or Str "foo" strToNumber(false) ?: 0 // parse as number or 0 as fallback
Formal grammar for convert tags:
<conv> := <expr> <expr>* <expr> := <math> | <bit> | <elvis> | <unit> | <func> <math> := <add> | <sub> | <mul> | <div> <add> := "+" <float> <sub> := "-" <float> <mul> := "*" <float> <div> := "/" <float> <bit> := <and> | <or> | <xor> | <shiftr> | <shiftl> <and> := "&" <int> <or> := "|" <int> <xor> := "^" <int> <shiftr> := ">>" <int> <shiftl> := "<<" <int> <elvis> := "?:" <literal> <unit> := <from> "=>" <to> <func> := <id> "(" <args> ")" <args> := <arg> ("," <arg>)* <literal> := "true" | "false" | <float> | <str>
Functions:
as(unit) invert() pow(exp) min(limit) max(limit) reset(inLo, inHi, outLo, outHi) thermistor(table-name) u2SwapEndian() u4SwapEndian() numberToBool() strToNumber(checked) hexToNumber(checked) numberToStr() numberToHex() lower() upper() strReplace(from, to) enumStrToNumber(enumDef, checked) enumStrToBool(enumDef, checked) enumNumberToStr(enumDef, checked) enumBoolToStr(enumDef)
Note that multiple conversions can be piped together by separating them with a space. They are always evaluated left to right (there is no precedence for math operators).