lib point

OverviewPoint CurPoint WritesPoint HisHis CollectEnum MetaPoint Conversion


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 Str
  • curStatus: ok, stale, down, fault, disabled, unknown, remoteDown, remoteFault, remoteDisabled, or remoteUnknown
  • curErr: 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 null
  • writeLevel: number from 1 to 17 indicate the winning priority array level
  • writeStatus: 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 connector
  • writeErr: 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, unknown
  • hisErr: 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:

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:

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"

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"

// convert "off" to 0, "slow" to 1, and "fast" to 2

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:


// 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:


// 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 enumMeta
  • enumDef(): 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.


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

// raise to the power of given exponent

// reset 5-10 to 0-100
reset(5, 10, 0, 100)

// thermistor table; run pointThermistorTables() for full list

// invert bool true -> false, vice versa

// swap endianness of unsigned two byte integer

// swap endianness of unsigned four byte integer

// zero to false, non-zero to true

// 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)

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

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


reset(inLo, inHi, outLo, outHi)

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