Sensors live in the Devices section of Business Made (/devices). A sensor is a device_config plugged into a Device Hub. The hub is placed in a zone, and everything on the hub is in that zone. When the hub reports an event for one of its ports, the engine records it as a device_event. Before writing it, the engine runs the device's policies: the device's own policy, plus the policy of every group it is in. The API is appengine devices/… (src/devices/).
sensor → hub port → hub (in a zone) ──event──→ engine
├─ device's policy + its groups' policies
│ rule matches? → do its actions in order
└─ device_event written, with every action's result| Record | What it is |
|---|---|
access_zone | A place. It is the same zone tree Access uses: see Access control for doors and cards. |
device_hub | The box on site. Its zone is where everything plugged into it is. A hub without a zone records nothing. |
device_config | One device on one hub port (hubName + endpointName). Kinds for sensors: contact, motion, beam, temperature, power, vibration, gas, smoke, leak, button, siren. |
device_group | Devices that act the same wherever they are ("every freezer probe"). It holds devices (device names) and one policy. |
device_policy | rules. Each rule matches one event and runs its actions (do) in order. |
device_event | What happened: type, level (info, warning, alarm), data, occurredAt, and actions (what each action did). |
device_reading | A measurement (a temperature every 30 s). It is stored for charts. A reading never runs a rule. |
Events and readings are different. An event arrives over the hub's socket as { type: "event", kind, endpointName, data }. kind becomes the event's type (open, gas-alarm, reading…). data.level sets the level (anything else is info). data.at sets when it happened. The rest of data is what rules compare. Readings arrive in batches over HTTPS, and they are only charted. A freezer alert therefore needs the hub to send an event when the probe is too warm (or to send each reading as an event), not just readings.
Ask first
- Where? The places (zones) the sensors are in, and the hub in each place. Is the hub installed and online?
- Which sensors? For each one: what it is (kind), a name people will recognise, and the hub port it is on.
- When? The event that should act: its type (
gas-alarm,open,reading), a level (alarm,warning), and one value to compare (value > 8). - Then what? In order:
- which devices to command, and with which command (
siren-on,unlock); - which staff to tell;
- which workflow a task should start on, and in which stage;
- which automation should take it further (an email or text, for example).
- which devices to command, and with which command (
- Does the hub send that event? Ask what the hub reports for this sensor. See What it can't do yet.
Steps
1. Devices › Zones: the place
Find the zone with repo_find on access_zone (data.name or data.title). If there is none, make it as in step 1 of Access control for doors and cards, with parent for a room inside a building. The engine fills scopePath (/<zone name> for a top zone, tested). Devices › Events and Devices › Devices filter by a zone and everything beneath it, using that path.
Hands on: the zone's name.
Check: Devices › Zones (/devices/zones) lists it in its place in the tree.
2. Devices › Hubs: the hub, in the zone
A hub that is already connected only needs its zone. repo_find on device_hub by data.name, then set zone. For a new box:
{ "datatype": "device_hub", "isNew": true,
"data": { "name": "cold-room-hub", "displayName": "Cold room hub", "zone": "<zone name>" } }Then make its key with call_appengine POST /device-integrations/hubs/<hub sk>/api-key. The key comes back once (apiKey, with "Save this key now — the server only stores its hash and cannot show it again."). On screen, the hub drawer (/devices/hubs?hub=<sk>) shows it with a connection string (Server=…;OrgId=…;HubName=…;ApiKey=…) to paste into the hub's setup. Installing the agent is Device Hub setup.
Making a new key for a hub that is already running cuts it off at once. The box stays offline until someone enters the new key on it (Managing hubs). Never re-key a working hub to "check" it.
Hands on: the hub's name.
Check: call_appengine GET /devices/hubs shows the hub with its zone, online, keySet and the ports it reports. Hubs with no zone sort first, because nothing they report is recorded yet.
3. Devices › Devices: one device per sensor
One device_config per sensor, on its port:
{ "datatype": "device_config", "isNew": true,
"data": { "name": "walk-in-probe", "displayName": "Walk-in freezer probe", "kind": "temperature",
"connection": "remote", "adapterName": "remote-hub",
"hubName": "<hub name>", "endpointName": "<port>", "policy": "<policy name, after step 6>" } }| Field | What it does |
|---|---|
name | Required, unique, letters, digits, - and _. Groups and rules name devices by it. The screen makes it from the title. |
kind | One of the sensor kinds above. Rules can command "only these kinds" (siren). |
hubName, endpointName | The hub and the port. The port is how an event finds its device. The drawer lists the ports the hub reports; type one only when the hub isn't reporting it. |
policy | The device's own policy (optional). It runs as well as its groups' policies. |
isActive / status | false / inactive shows the device as Disabled and stops commands going to it. It does not stop its events from running its policies (tested). |
The device's zone is always its hub's zone. There is no zone on the device.
One sensor per port. A second sensor on a port that has one is refused with 409, "Port probe-1 on hub … is already used by … Pick another port, or move that device first." (reason: port-taken, tested). A drawer riding on its printer may share a port; sensors may not.
Hands on: each device's name.
Check: GET /devices/list?zone=<zone name> shows each device with its zone, hub, policy, groups and status.
| Status | Means |
|---|---|
alarm | Its latest event was an alarm. It stays until the device reports something else. A hub going offline does not clear it. |
offline | Its hub isn't connected, or the hub says that port is offline or in error. |
warning | Its latest event was a warning. |
no-hub | It has no hub. |
disabled | It is switched off. |
ok | None of these. |
On screen: Devices › Devices (/devices/list; ?device=<sk> opens one, with its recent events and a readings chart).
4. Devices › Device groups: devices that act alike (optional)
When several devices should do the same thing wherever they are, group them and give the group one policy:
{ "datatype": "device_group", "isNew": true,
"data": { "name": "freezer-probes", "title": "Freezer probes", "devices": ["walk-in-probe", "reach-in-probe"], "policy": "<policy name>" } }A device may be in many groups and follows all their policies. A group without a policy does nothing; its devices still record every event.
Check: Devices › Device groups (/devices/groups?group=<sk>), or the device's groups in GET /devices/list.
5. Workflow and Automation: what the rules hand on to (optional)
Only if a rule will start a task or hand the event on:
- A task. You need a workflow with its stages: see A stage workflow with time limits. The workflow must not be switched off (
enabled: false), or no task starts. The event then reads "The workflow did not start a task". The task is owned by the device (owner.datatype: device_config), and itstriggerIdisdevice_event:<event id>. - An automation. The policy runs it directly, whatever its own trigger is. It must be active: a draft or paused automation is skipped, while the event still reads "handed to
" (tested). The run gets these variables at the top level:
| Variable | Value |
|---|---|
{{type}}, {{level}} | The event's type and level |
{{value}}, {{unit}}, and any other key the device sent | The event's data |
{{device}}, {{deviceTitle}} | The device's name and title |
{{zone}} | The zone's name (not its title) |
{{id}}, {{occurredAt}} | The event's id and time |
{{data.value}} and other {{data.…}} paths are not filled (tested). For automations, see Automation when a record is added or updated.
Hands on: the workflow's sk and its stage names; the automation's sk.
6. Devices › Device policies: the rules
repo_create with datatype device_policy:
{ "datatype": "device_policy", "isNew": true,
"data": { "name": "cold-chain", "title": "Cold chain", "rules": [
{ "on": ["reading"], "field": "value", "op": ">", "value": "8", "enabled": false, "do": [
{ "type": "alert", "to": ["[email protected]"], "message": "{{device}} reads {{value}} in {{zone}}" },
{ "type": "command", "target": "zone", "kinds": ["siren"], "capability": "siren-on" },
{ "type": "task", "workflowId": "<workflow sk>", "title": "{{device}}: {{value}}" },
{ "type": "automation", "automationId": "<automation sk>" } ] },
{ "on": ["reading"], "field": "value", "op": "<=", "value": "8", "enabled": false, "do": [
{ "type": "move-task", "workflowId": "<workflow sk>", "stage": "Fixed" } ] } ] } }When a rule matches. All parts must hold:
| Part | Matches |
|---|---|
on | The event's type is in the list. Empty means every event. |
level | The event's level is this one. Empty means any level. |
field, op, value | One value the event carries, compared with =, !=, >, >=, <, <=. Compared as numbers when both sides are numbers ("12" > 8, tested), else as text. If the event doesn't carry the field, the rule doesn't match. |
enabled | false switches the rule off (tested). |
Every rule that matches runs, in every policy that applies. Rules run side by side; one rule's actions run in order. A failed action doesn't stop the next one.
The actions (do):
type | Config | What happens |
|---|---|---|
command | capability (the command, e.g. siren-on), target: zone (default: every device in the device's zone), device (itself), group (+ group), named (+ device); kinds narrows the target | Sent to each target's hub port, and waits up to 8 s for each. Disabled devices and devices with no hub or port are left out. With no target left: "No device to send siren-on to" (tested). |
alert | to: staff emails; message | An in-app notice to each person. Subject: "message, or the subject when there is none. Nothing is emailed or texted. |
task | workflowId, stage (empty = first), title | Starts a workflow task owned by the device. |
move-task | workflowId, stage | Moves the device's newest open task on that workflow to the stage. "no open task to move" when it has none. |
automation | automationId | Hands the event to the automation, in the background (step 5). |
In message and title, {{device}} is the device's title, {{zone}} the zone's title, {{event}} the event type, and {{value}} the value with its unit (9.4 °C, tested).
Then point devices at it: policy on a device (step 3) or on a group (step 4). The screen is Devices › Device policies (/devices/policies?policy=<sk>).
Stop: a policy acts on the next event after it is saved. A command drives real hardware (sirens, locks), and an alert reaches real people. Policies have no draft, so save each rule with enabled: false. Then show the person:
- the devices it covers (its own and its groups');
- each rule's condition;
- each action: which devices get which command, who is told and what it says, the workflow and stage, the automation.
Set the rules to enabled: true only after a clear yes.
Hands on: the policy's name.
Check: GET /devices/list shows the policy on each device and its groups. After the next real event, check step 7.
7. Devices › Events: did it work?
call_appengine GET /devices/events?device=<device name> (or ?zone=, ?type=, ?level=alarm, ?from=/?to=, ?q=), newest first. Each event carries actions. Each action has the policy and where it came from (device, or group <title>), its type, ok, and a detail such as:
- "told manager@…";
- "siren-on → Siren 1";
- "task
" ; - "task
→ Fixed" ; - "handed to …";
- "No device to send unlock to".
An event with no actions matched no rule. The screen is Devices › Events (/devices/events).
The engine can't fire a rule for testing. To prove it, ask the person to trigger the sensor (open the door, warm the probe), then read the event.
GET /devices/overview gives every zone that holds devices, worst status first, with counts and its last event. It is the answer to "are any sensors in alarm?".
Readings and charts
A hub sends readings with POST /devices/ingest/readings, signed with its own x-hub-name and x-hub-key headers (not a person's sign-in). The body is { readings: [{ endpoint, value, metric?, unit?, at? }] }, at most 2,000 per batch. The answer is { accepted, skipped, invalid }:
- a port with no device in a zone is skipped;
- a value that isn't a number is invalid;
- a time more than 5 minutes ahead is invalid.
A wrong key gets 401 "Unknown hub or wrong key" (all tested). metric defaults to the device's kind (temperature).
GET /devices/readings?device=<name>&metric=&from=&to= returns points (avg, min, max, count), with the bucket size picked from the range:
| Range | Buckets |
|---|---|
| up to 3 h | 5 minutes |
| up to 13 h | 15 minutes |
| up to 2 days | 1 hour |
| up to 60 days | 1 day |
| more | 1 week |
It also returns the device's metrics, the latest value, and a range that defaults to the last 24 hours. The device drawer draws this chart.
When it's done
Tell the person:
- the zones, and the hub in each;
- each device, its port and its status;
- the groups, and which policy each device follows;
- each rule in words ("when the walk-in reads over 8, sound the sirens in the cold room, tell Sam, start a task") and whether it is on;
- that the first real event will show what each action did, under Devices › Events.
Open Devices › Device policies on the policy.
What it can't do yet
- The hub agent doesn't send sensor events yet. The Device Hub agent reports scanner reads (
scan), card taps (card-tap) and printer errors (printer-error). It has no sensor drivers, and its readings queue is never started (websitemint/apps/hub-agent/src/readings.ts; nothing calls it). Temperature, gas, contact and leak alerts need hardware or an agent that sendseventmessages. A scanner or printer on a hub in a zone does record its events, and a policy can act on them. - One event at a time. A rule can't say "above 8 °C for 5 minutes", "three times in an hour", or compare two devices. Readings never run rules. For more, hand the event to an automation.
- No de-duplication. Every matching event acts again. Two warm events mean two alerts and two tasks (tested). Use
move-taskto close the task when the value is back. - Old events act when they arrive. An event the hub held while offline runs its rules as soon as it arrives (tested with a time an hour earlier). It shows in the log at the time it happened.
- Alerts are in-app only.
alertstores a notice in each person's tray, for people who sign in to the business. For email or SMS, use an automation withsend_emailorsend_sms(Email and SMS templates). - Disabled isn't silent. A disabled device still records its events and runs its policies (tested). To silence it, remove its policy and take it out of its groups.
- Commands aren't retried. A hub that is offline or doesn't answer in 8 s is a failed action on that event, and nothing tries again.
Don'ts
- Don't send test events or commands yourself, and don't re-key a hub to test it. Read the event log after a real trigger.
- Don't save a rule
enabled: truebefore the person says yes. - Don't expect a reading to alert. Only events run rules.
- Don't put two sensors on one port, and don't put a hub's devices anywhere but the hub's zone.
- Don't hand events to a draft automation, and don't write
{{data.value}}in it. Use{{value}}. - Don't use
alertfor customers or outside numbers. It reaches only people who sign in to the business, and only in the app.