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.
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).
Response — msg.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.
get_all_statesget_stateget_historycontrol_devicecontrol_fanget_scenesactivate_scenecontrol_covercontrol_spacontrol_climateget_presenceget_alertsget_groupscontrol_groupset_lightanalyze_patternsget_flowdeploy_flow
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": "…" } }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": "…" } }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": "…" } }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 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
fanat 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": "…" } }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
sceneat 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 or deactivate a scene by name or ID. Use get_scenes to find available scenes.
Requires hardware: at least one item of type
sceneat 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 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
coverat 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 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,airjetsat 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 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 modeat 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": "…" } }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": { } }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 } }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" } }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": "…" } }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,dimmerat 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": "…" } }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 } }These require Allow admin tools to be enabled on the hal2Api node.
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": "…" } }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": [] } }