Skip to content

Latest commit

 

History

History
419 lines (285 loc) · 27.7 KB

File metadata and controls

419 lines (285 loc) · 27.7 KB

hal2 JSON API

The hal2Api node exposes the hal2 tool catalog as a JSON request/response gateway. Wire it behind an http in, MQTT, or any node that produces a JSON message.

Contract

Request — set msg.payload:

{ "tool": "<name>", "args": { /* tool-specific */ } }

msg.tool / msg.args override the payload fields when present. An optional msg.claims object is forwarded to the tool (used for admin role checks).

Responsemsg.payload:

{ "ok": true, "result": /* tool output */ }

On failure:

{ "ok": false, "error": { "code": -32601, "message": "" } }

Most tools return a JSON object in result. A few admin tools return plain text.

Each tool below is tagged 👁 read, ✏️ write or 🔒 admin. Those classes are what the MCP server's access gates act on, so a token can be allowed to observe the house without being allowed to change it. They are informational here: the hal2Api node is a local flow node and gates only its admin tools, via its own checkbox.

Tools

get_all_states 👁 (read)

Returns the current state of all devices/things connected to this event handler. The response includes a location field (e.g. "Home" or "Cabin") identifying which property this server controls. Use fields="summary" (default) for a lightweight list with thing_id, thing_name, type_name and alive — ideal for orientation and ID lookup. Use fields="items" for a compact per-device item index (thing_id, thing_name, type_name, items:[{item_id, item_name, ha_type, history}]) — cheap way to find an item_id without the full dump. Use fields="full" to include all items with item_id, item_name, ha_type and current value. Each item and each device always includes a last_change field (ISO 8601 UTC timestamp, null if the value has not changed since startup) — when the value last actually changed. Use this to answer "when did X happen?" without an extra get_history call. Each device has an alive field (true/false) — if false the device is offline. Only items with a ha_type are included in full mode. Responses include free-text notes and tags on both Thing and Item level when configured — use them to disambiguate what a device actually measures or controls (e.g. "Pool Sensor" notes: "pool water temperature"). Each device also includes a categories field listing which control categories it falls into (climate, spa, light, fan, cover, scene), derived from its items — use this to identify what kind of device it is at a glance. ha_type accepts both literal item types (e.g. "light", "temperature") and category aliases that expand to their underlying types — e.g. "climate" matches devices with target temperature / ac mode / fan mode / swing mode. Supported aliases: climate, spa, light, fan, cover, scene. Use tag to limit results to devices/items tagged with a specific keyword. Supports optional pagination via offset and limit. The response includes total.

Parameters

Parameter Type Required Description
fields summary | items | full no Level of detail — "summary" (default): thing_id, thing_name, type_name, alive; "items": compact item index (item_id, item_name, ha_type, history) for cheap id lookup; "full": includes all items with values + metadata
ha_type string no Filter to devices that have at least one item with this ha_type (e.g. "light", "scene", "cover")
tag string no Filter to devices/items tagged with this value (case-insensitive, exact match)
offset integer no Number of devices to skip (default: 0)
limit integer no Max devices to return (default: all)

Example

{ "tool": "get_all_states", "args": { "fields": "summary", "ha_type": "" } }

get_state 👁 (read)

Returns the complete state for a specific device. Use this to fetch full details for one device by its thing_id. Provide thing_id for an exact lookup or thing_name for a partial, case-insensitive match. Response includes notes and tags on both Thing and Item level when configured. Each item and the device itself include last_change (ISO 8601 UTC) — when the value last actually changed. Optionally provide item_id to return only a single item value — the item is a measurement/control within the device, not the device name. If item_id is wrong, the error response lists available_items for that thing so you can pick the right one.

Parameters

Parameter Type Required Description
thing_id string no Exact thing node ID
thing_name string no Partial, case-insensitive name match (alternative to thing_id)
item_id string no If provided, returns only this item within the device

Example

{ "tool": "get_state", "args": { "thing_id": "", "thing_name": "" } }

get_history 👁 (read)

Returns logged historical values for a specific device item — temperature and other sensor readings over time, time series for a graph/chart, trends, statistics, activity. Use this whenever the user asks about history, statistics, trends, activity over time, how often something happened, when it last changed, or similar time-based questions. Items that support history are marked with history:true in get_all_states. NOTE: a thing is the device, an item is a measurement/control WITHIN it — they are separate namespaces with separate names (e.g. the device "Lake Water Sensor" contains an item named "Temperature"), so a thing name will not match an item name. If you know the device but not the exact item, pass ha_type (e.g. ha_type="temperature") and the server resolves the item for you. If a device has several items of the same ha_type (e.g. an indoor and an outdoor temperature), combine ha_type with tag (e.g. tag="outdoor") to pick the right one — items and their tags are listed in available_items when the match is ambiguous. If item resolution fails, the error response includes available_items (item_id, item_name, ha_type, history) for that thing — pick from it, no full get_all_states dump needed. Returns an array of objects with timestamp (ISO 8601 UTC, e.g. "2026-05-28T12:03:11.000Z") and state fields, sorted oldest-first. Time window — use one of these forms: (1) hours: number of hours back from now (default: 24); (2) from + to: explicit ISO datetime strings or epoch ms, e.g. from="2026-05-01T00:00:00" to="2026-05-02T00:00:00"; (3) from only: from that point until now; (4) at: returns the single most recent record at or before that moment — useful for "what was the value at time X?". Use offset and limit to page through large result sets (default limit: 500). The response includes total so you know how many calls are needed. DOWNSAMPLING: for long ranges of a NUMERIC item (e.g. a week of temperature for a graph), set bucket to "minute", "hour" or "day" (or bucket_seconds for a custom interval). The server then aggregates per time bucket and returns a compact "buckets" array — each entry { start, count, avg, min, max } (avg/min/max rounded to numeric_precision; bucket start is local time, e.g. a "day" is local midnight) — instead of all raw samples. Prefer this over fetching raw data and averaging yourself. Buckets with no data are omitted. Aggregation is numeric-only; for non-numeric items (on/off, mode) use bucket="raw" (the default).

Parameters

Parameter Type Required Description
thing_id string no Exact thing node ID (from get_all_states)
thing_name string no Partial, case-insensitive name match (alternative to thing_id)
item_id string no Item ID (from get_all_states). The item is the measurement within the thing — NOT the thing/device name.
item_name string no Item name, partial case-insensitive match (alternative to item_id). Must be an item name (e.g. "Temperature"), not the device name.
ha_type string no Resolve the item by its ha_type within the thing (e.g. "temperature", "humidity", "power"). Convenient when you know the device but not the item name. Aliases like "climate"/"light" expand.
tag string no Disambiguate items of the same ha_type within the thing by tag (e.g. ha_type="temperature" + tag="outdoor"). Can also be used alone. Item tags appear in available_items.
hours number no Hours back from now (default: 24). Ignored if from/to/at are provided.
from string no Start of time window — ISO datetime string (e.g. "2026-05-01T06:00:00") or epoch ms as string
to string no End of time window — ISO datetime string or epoch ms as string. Defaults to now if omitted.
at string no Point-in-time lookup — ISO datetime string or epoch ms. Returns the single most recent record at or before this moment.
bucket raw | minute | hour | day no Downsampling resolution. "raw" (default) returns individual records; "minute"/"hour"/"day" return server-aggregated avg/min/max/count per local-time bucket (numeric items only).
bucket_seconds integer no Custom bucket size in seconds (epoch-aligned). Overrides bucket. Numeric items only.
numeric_precision integer no Decimal places for avg/min/max when bucketing (default 2).
offset integer no Number of records to skip (default: 0). Not applicable when using at or bucketing.
limit integer no Max records to return (default: 500). Not applicable when using at or bucketing.

Example

{ "tool": "get_history", "args": { "thing_id": "", "thing_name": "" } }

control_device ✏️ (write)

Send a command to a specific device item. Use thing_id and item_id from get_all_states. The item is the control WITHIN the device (e.g. an "On" item), not the device name. If the item_id is wrong or read-only, the error response lists available_items (item_id, item_name, ha_type, read_only) for that thing — pick a controllable one from it.

Parameters

Parameter Type Required Description
thing_id string yes Thing node ID (from get_all_states)
item_id string yes Item ID within the thing type (from get_all_states)
value any yes Value to set (e.g. "on", "off", brightness number, temperature, etc.)

Example

{ "tool": "control_device", "args": { "thing_id": "", "item_id": "", "value": "" } }

control_fan ✏️ (write)

Control a ceiling fan. Identify by thing_id or thing_name (partial, case-insensitive). Speed 0 = off, 1 = low, 2 = medium, 3 = high. Current speed is available via get_all_states.

Requires hardware: at least one item of type fan at this location.

Parameters

Parameter Type Required Description
thing_id string no Exact thing node ID (from get_all_states)
thing_name string no Partial, case-insensitive name match
speed number no 0 = off, 1 = low, 2 = medium, 3 = high

Example

{ "tool": "control_fan", "args": { "thing_id": "", "thing_name": "" } }

get_scenes 👁 (read)

Returns all scenes with their current status (active/inactive) and last_change (ISO 8601 UTC) — when the scene was last activated or deactivated. Use this to answer "is scene X active?", "which scenes are active right now?" or "when was scene Y last activated?".

Requires hardware: at least one item of type scene at this location.

Parameters

Parameter Type Required Description
name string no Optional partial, case-insensitive filter on scene name

Example

{ "tool": "get_scenes", "args": { "name": "" } }

activate_scene ✏️ (write)

Activate or deactivate a scene by name or ID. Use get_scenes to find available scenes.

Requires hardware: at least one item of type scene at this location.

Parameters

Parameter Type Required Description
thing_id string no Exact thing node ID (from get_scenes)
thing_name string no Partial, case-insensitive name match
active boolean no true = activate, false = deactivate

Example

{ "tool": "activate_scene", "args": { "thing_id": "", "thing_name": "" } }

control_cover ✏️ (write)

Control curtains, blinds or shutters. Identify by thing_id or thing_name (partial, case-insensitive). Use position to set an exact opening level, or open/close as a shortcut. Current position is available via get_all_states.

Requires hardware: at least one item of type cover at this location.

Parameters

Parameter Type Required Description
thing_id string no Exact thing node ID (from get_all_states)
thing_name string no Partial, case-insensitive name match
position number no Position 0–100 where 0 = fully closed, 100 = fully open
open boolean no true = fully open (100), false = fully closed (0). Overridden by position if both are given.

Example

{ "tool": "control_cover", "args": { "thing_id": "", "thing_name": "" } }

control_spa ✏️ (write)

Control a spa or hot tub. Identify by thing_id or thing_name (partial, case-insensitive). Current status (water temperature, heater state etc.) is available via get_all_states. All control parameters are optional — only provided ones are sent.

Requires hardware: at least one item of type heater, circulation pump, airjets at this location.

Parameters

Parameter Type Required Description
thing_id string no Exact thing node ID (from get_all_states)
thing_name string no Partial, case-insensitive name match
target_temp number no Desired water temperature in °C
heater boolean no true = turn heater on, false = turn off
pump boolean no true = turn circulation pump on, false = turn off
airjets boolean no true = turn airjets on, false = turn off

Example

{ "tool": "control_spa", "args": { "thing_id": "", "thing_name": "" } }

control_climate ✏️ (write)

Control a heat pump or AC unit. Identify by thing_id or thing_name (partial, case-insensitive). Current status is available via get_all_states. All parameters are optional — only provided ones are sent.

Requires hardware: at least one item of type target temperature, ac mode, fan mode, swing mode at this location.

Parameters

Parameter Type Required Description
thing_id string no Exact thing node ID (from get_all_states)
thing_name string no Partial, case-insensitive name match
mode off | cool | heat | fan_only | dry | heat_cool no HVAC mode
target_temp number no Target temperature in °C
fan_mode auto | diffuse | low | medium | middle | high no Fan speed/mode
swing_mode off | vertical no Swing direction

Example

{ "tool": "control_climate", "args": { "thing_id": "", "thing_name": "" } }

get_presence 👁 (read)

Returns presence information for all people/persons tracked in the system. Shows who is home, who is away, and which room each person is in. Use this to answer questions like "is anyone home?", "where is Alice?", "who is home right now?", "when did Bob come home?", "how long has Alice been away?". Each person includes home_since/away_since (ISO timestamp of last change) and home_for_minutes/away_for_minutes (duration in current state). When home, also includes room, room_since and in_room_for_minutes. thing_id and item ids are included so follow-up tools (get_history, set_light, etc.) can be called without an extra lookup. A summary block provides aggregated counts and name lists.

Parameters

No parameters.

Example

{ "tool": "get_presence", "args": {  } }

get_alerts 👁 (read)

Returns water leak sensor status, devices with low battery, and offline devices in one call. Use this to answer "is there a water leak?", "which sensors have low battery?", "are any devices offline?", "what needs attention?" or similar questions about sensor alerts. Each entry always includes last_change (ISO 8601 UTC, null if unknown) — for water sensors this is when the wet/dry state changed, for low-battery items when the level last changed, and for offline devices when they went offline.

Parameters

Parameter Type Required Description
battery_threshold number no Battery level threshold in percent (default: 20)

Example

{ "tool": "get_alerts", "args": { "battery_threshold": 0 } }

get_groups 👁 (read)

Returns the groups configured at this location, with their current value. A GROUP IS NOT A DEVICE: it is a named set of items drawn from several devices, and it has no items of its own — so it takes no item_id and does not appear in get_state. Its value is COMPUTED from its members by the function shown — latest, min, max, average, median, sum, range (highest minus lowest), any true, all true, any false, all false, count true, count false, percent true (0-100) — and members whose device is offline are left out, so a group value can change with nothing having been switched. Prefer a group over reading its members one by one whenever the user speaks about a set as one thing: "is anything on?", "how warm is it indoors?", "how many windows are open?". Use get_state instead when they mean one specific device. THE FUNCTION IS NOT FIXED: pass function to compute a different one from the same members on this call. That is the point — "any true" answers "is a lamp on?", "all true" answers "did the turn-them-on command work?", "count true" answers "how many are on?", over exactly the same group. It changes nothing: the configured function is what the group keeps reporting, and configured_function appears in the reply when the two differ. A function that does not apply to the members is REFUSED, not answered: asking a temperature group whether all its members are true comes back as an error naming what the members hold and listing suitable_functions — it does not come back as false. On a mixed group a function may cover only part of it (average over a light group uses the dimmers and ignores the on/off members); the reply then carries used alongside live, and a value computed from a minority of the members should be read as such. The default is derived from the group's ha_type, not configured by anyone: a temperature group reports its average, a light or switch group whether any member is true. A group whose ha_type implies no default has no standing value and no function field — pass function to read it, and members > 0 is what says it is worth asking. readable:true means the group has members that carry a state, so it can be read. controllable:true means control_group can command it. The two are independent: sensors contribute a value and take no commands, a switch may take commands and report nothing back. members is how many members carry a state, live how many are contributing right now — a gap between them means devices are offline. last_change is tracked per function, so it is this function's own: "all true" and "any true" over the same group change at different moments. source names the member the value came from, present only for latest, min and max where one member owns it — use it to answer "which room is coldest?" without reading every member. last_changed_by names the member that last moved the value, which is a different question and only the same one for latest. notes and tags say what the group actually covers, which the name usually does not ("All lights" does not tell you whether the outdoor lights are included) — read them before assuming, and use the tag filter to select the set you mean.

Parameters

Parameter Type Required Description
group_id string no Exact group ID — returns just that group, for re-reading one you already know
function latest | min | max | average | median | sum | range | anyTrue | allTrue | anyFalse | allFalse | countTrue | countFalse | percentTrue no Compute this function instead of the group's configured one, for this call only: latest, min, max, average, median, sum, range, anyTrue, allTrue, anyFalse, allFalse, countTrue, countFalse, percentTrue
group_name string no Optional partial, case-insensitive filter on group name
ha_type string no Filter to groups of this ha_type. Accepts the same category aliases as get_all_states (climate, spa, light, fan, cover, scene), so ha_type="light" also matches dimmer groups
tag string no Filter to groups tagged with this value (case-insensitive, exact match)

Example

{ "tool": "get_groups", "args": { "group_id": "", "function": "latest" } }

control_group ✏️ (write)

Sends one command to every member of a group that can accept one — one call instead of one per device. Use get_groups to find groups and see which are controllable. COMMANDING A GROUP COMMANDS ITS MEMBERS. The group's own value is derived and is never written: it follows from what the members report back afterwards, so read it again rather than assuming the command set it. Members that only report (sensors) are skipped, so the number of members commanded can be lower than the member count in get_groups. Members are paced by the group's rate limit, so a large group takes a moment to finish. The value must suit the group's ha_type: on/off (or true/false) for a light or switch group, 0-100 for a dimmer or cover group, a number for a setpoint group. Groups of a structured type (e.g. colour) take whatever value that type expects, so the parameter is deliberately untyped. RETURNS { ok, group_id, name, value, commanded, skipped, delivery }: commanded is how many members the command was queued to, skipped how many were passed over because they only report or could not be resolved. There is NO per-member success or failure — commands are fire-and-forget onto the event bus and paced by the rate limit, so most members have not been sent yet when the call returns. A non-zero commanded means the command was accepted and queued, not that any device has acted on it. To confirm the effect, read the group again with get_groups; do not treat the reply as confirmation and do not re-send on the assumption that nothing happened. To control a single device instead, use set_light or control_device.

Parameters

Parameter Type Required Description
group_id string no Exact group ID (from get_groups)
group_name string no Partial, case-insensitive name match (alternative to group_id)
value any yes Value to send to every commandable member — "on"/"off", true/false, or a number

Example

{ "tool": "control_group", "args": { "value": "" } }

set_light ✏️ (write)

Control a specific light or lamp. Identify the device by thing_id OR thing_name. thing_name supports partial, case-insensitive match against the thing name OR against item labels (the label field in get_all_states items). Labels are friendly names assigned per-device, e.g. a double switch named "Kitchen Double Switch" may have items labelled "Kitchen Ceiling Light" and "Kitchen Counter Light" — searching "counter" will target only that relay. You can turn it on/off and/or set brightness/color_temp/color in one call.

Requires hardware: at least one item of type light, dimmer at this location.

Parameters

Parameter Type Required Description
thing_id string no Exact thing node ID (from get_all_states). Takes priority over thing_name.
thing_name string no Partial, case-insensitive name match (e.g. "office" matches "Office Spotlights").
on boolean no true = turn on, false = turn off
brightness number no Brightness 0–100 (percent)
color_temp number no Color temperature in Kelvin (e.g. 2700 = warm white, 4000 = neutral, 6500 = cool wide)
color string no Color as HSB string "H,S,B" where H=0-360 (hue), S=0-100 (saturation), B=0-100 (brightness). E.g. "0,100,100"=red, "120,100,100"=green, "240,100,100"=blue.

Example

{ "tool": "set_light", "args": { "thing_id": "", "thing_name": "" } }

analyze_patterns 👁 (read)

Analyzes the history database to detect recurring behavioral patterns — e.g. "Living Room Light turns ON around 07:30, 85% consistent". Detects state transitions (actual changes), groups them into time-of-day windows, and returns suggestions sorted by consistency score. Also reports stale items (no activity in 30+ days). By default, state changes caused by hal2 itself are excluded so existing automations are not re-suggested as patterns. Requires history to be enabled on the event handler. Use when the user asks about automating routines or finding patterns in device usage.

Parameters

Parameter Type Required Description
days number no Lookback period in days (default: 30, max: 365)
window_minutes number no Time-of-day bucket size in minutes (default: 30)
threshold number no Minimum consistency ratio 0–1 to include a pattern (default: 0.7)
min_occurrences integer no Minimum number of occurrences to qualify (default: 2)
include_sensors boolean no If true, include continuous/noisy sensors (temperature, humidity, battery, illuminance, power, pressure, depth) — default: false. co2 is always analyzed.
include_internal boolean no If true, include state changes caused by hal2 itself (default: false). Useful for debugging or verifying that automations actually run.
numeric_precision integer no Significant figures used to quantize numeric values before detecting transitions, suppressing micro-noise (e.g. lux 287/289/294 → 290). Default: 2. Range 1–6.

Example

{ "tool": "analyze_patterns", "args": { "days": 0, "window_minutes": 0 } }

Admin tools

These require Allow admin tools to be enabled on the hal2Api node.

get_flow 🔒 (admin)

Lists all Node-RED tabs (ID and node count) when called without arguments. Returns full JSON configuration for a specific tab when called with an id.

Exposed through hal2Api only when Allow admin tools is enabled on the node.

Parameters

Parameter Type Required Description
id string no Flow/tab ID — omit to list all flows

Example

{ "tool": "get_flow", "args": { "id": "" } }

deploy_flow 🔒 (admin)

Creates or updates a Node-RED flow tab. Omit id to create new.

Exposed through hal2Api only when Allow admin tools is enabled on the node.

Parameters

Parameter Type Required Description
id string no Existing flow ID (omit for new flow)
label string yes Flow tab label/name
nodes array yes Array of node objects

Example

{ "tool": "deploy_flow", "args": { "label": "", "nodes": [] } }