Working with Hooks¶
A "hook" is a single JavaScript function, which runs in a managed environment. They are similar in concept to AWS Lambda functions, in that the invocation lifecycle is managed by the runtime; and the hook author only concerns themself with the "business logic" inside of the function body.
The Normal runtime can manage a number of complex tasks for the hook; including:
- Invoking the hook in response to new data or a schedule
- Managing a command context
- Selecting points based on equipment labels or the data model
- Creating new points (program variable), which can then be used by other hooks or exported as BACnet objects.
Hook Lifecycle¶
An invocation of a hook is initiated by a call to StartHook. Depending on the invocation mode, this may be either explicitly by an external application, or internally by the scheduler or data manager.
graph TB
A[Stopped] -->|Invoked due to schedule,<br> API call, or new data| Groups[Running]
Groups -->|All groups complete| RUNNING[End Hook Run]
Groups -->|Each Group Executes| Groups
RUNNING -->|Hook completes without error| SUCCESS[Success]
RUNNING -->|Hook returns an error code| Error
RUNNING -->|Syntax error or runtime issue| Aborted
When a hook definition includes a grouping mode, the hook function is invoked once per group; and so once StartHook
is called, the hook will transition to the RUNNING
state. Each group will execute to completion, and after all groups have completed, the overall hook run will be marked complete. Each group may record a return value, exit code, and events which are available for applications to inspect later on.
Components of a Hook¶
A hook definition consists of a number of pieces of configuration data which control how the hook is invoked, and with which arguments. A hook shares a JavaScript runtime, dependencies with the entire application.
- Points: a
StructuredQuery
used to determine which points and variables are used by the hook. - Grouping: either an attribute name, or a JavaScript function used to compute groups.
- Invocation Mode: either
SCHEDULED
,ON_DATA
, orON_REQUEST
and determines when the runtime invokes the hook. - Entry Point: the name of the file containing a default export which will be invoked on startup.
Example Definition¶
The easiest way to see some of the important parts of a hook is to look at a simple example. The hook definition below defines a hook named trend-data
for an imaginary protocol named "protocol". It will be called every minute with a list of points that protocol has defined and which have been enabled for trending (that is, they have a period
greater than zero).
{
"hook": {
"name":"trend-data",
"entryPoint":"/hooks/trend-data.js",
"points":{
"layer":"hpl:protocol",
"query": {"field":{"property":"period","numeric":{"minValue":1,"maxInfinity":true}}},
},
"schedule":{"rrule":"DTSTART:20231111T050000Z\nRRULE:FREQ=MINUTELY;INTERVAL=1"},
"mode":"MODE_SCHEDULED",
"invokeTimeout":"60s"
}
}
Invocation Log¶
Point Selection¶
A core concept for hooks is binding the function execution to points in the Point database. Many common use-cases involve binding hooks to points.
Each time a the hook is invoked, it is passed the full list of points which it is bound to, along with the points' latest values. This makes it easy to implement use cases like simple control loops; or database integrations.
The points to be bound are determined by a StructuredQuery
. For a full discussion of how to create a query, see the Query section of the documentation.
Variables¶
Variables are points that are created by the runtime and scoped to a hook. Variables are simply a normal point
object, created in the automation
layer. They can be queried just like any other point in the system. Variables come in two types:
- Global variables are singleton points scoped to the hook. There will only be one instance of this variable for all groups of the hook.
- Group variables are created for each group in the hook. There will be a separate instance of this variable for each group in the hook.
Either kind of variable can be created by setting the groupVariables
or globalVariables
key in UpdateHook
. The only required field to create a variable is the label
; which is used to identify the variable in the SDK. Variable can define additional attributes; the attribute values may be static strings, or templates that refer to points in the group for which the variable is being created.
In this example, a variable labeled Cooling_SetPoint_Requests
adds attributes for the airRef
and equipRef
, copied from the first group member.
{
"label": "Cooling_SetPoint_Requests",
"attrs": {
"airRef": "{{ Point.Attrs.airRef }}",
"equipRef": "{{ Point.Attrs.equipRef }}",
},
"default_value: {
"real": 50
}
}
Variables can also have a default_value
. If set, that value is written to each variable upon creation, and whenever ResetVariables is called. This can be useful for using variable to expose configuration values which can be updated per-group using the timeseries API.
Grouping¶
Two keys in the Point Selector affect the group mode: either groups
or group_function
may be set. If groups
is set to a list of attribute names, those attribute values are used to create a group key. Alternatively, if group_function
is set, it should contain a JavaScript function named groupFn
which takes a point as the first argument, and returns the group key. For instance:
function groupFn(point, points) {
return point.attrs['device_id'];
}
This will group the invocations by the device_id
attribute.
Invocation Mode¶
There are three available invocation modes.
Invocation Mode | Attributes | Behavior |
---|---|---|
MODE_SCHEDULED |
schedule |
If set to scheduled mode, the hook will be invoked each schedule period. The schedule attribute should be set to an RRULE defining the schedule. These are evaluated using StrToRRuleSet which allows complex schedules to be created |
MODE_ON_REQUEST |
In this mode, the hook is invoked only when explicitly invoked by a call to StartHook | |
MODE_ON_DATA |
On data mode causes the hook to be invoked whenver any of the points it is bound to have a new value. This makes it easy to write hooks which respond to events in the system, like BACnet writes to local objects. |
Command Context¶
If the command_expiration
field of a hook is set, the runtime will create and renew a Command context for each group in the hook. This should be used if the desired behavior of a hook is to make temporary overrides that expire; or to periodically update a setpoint to a value.