Skip to content

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, or ON_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:

  1. Global variables are singleton points scoped to the hook. There will only be one instance of this variable for all groups of the hook.
  2. 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.