The viam:sensor-bundle module provides these models:
viam:sensor-bundle:stateful-sensor- A sensor component that holds a value you set viaDoCommand, serves it through the Readings API, and persists it to a file on disk so it survives restarts.viam:sensor-bundle:sensor-monitor- A sensor component that watches another sensor's readings against numeric trigger rules and firesDoCommandactions on other resources when a rule triggers or resolves.
API: rdk:component:sensor
A stateful sensor that holds an arbitrary value. Set the value with the set DoCommand; read it back through the Readings API. The value is kept in memory and persisted to a JSON file. On initialization the value is loaded from that file; if the file does not exist it is created. Writes are atomic (temp file + rename) so a crash mid-write cannot corrupt the state file.
{
"file_path": "<string>"
}| Name | Type | Required | Description |
|---|---|---|---|
file_path |
string | No | Where the value is persisted. Defaults to <resource-name>_state.json inside the module's data directory (the writable path Viam provides via VIAM_MODULE_DATA), falling back to the working directory when that is unset. |
{
"file_path": "/var/lib/viam/stateful-sensor.json"
}Returns the value most recently set (or the value loaded from file_path on startup). Before anything has been set, it returns an empty object.
{
"temperature": 72.5,
"unit": "F"
}set - Replace the value the sensor holds. The object provided under set becomes the sensor's value, is persisted to file_path, and is what Readings returns.
{
"set": {
"temperature": 72.5,
"unit": "F"
}
}Returns:
{"set": "ok"}API: rdk:component:sensor
Monitors the readings of another sensor and, when a numeric reading crosses a configured threshold, fires DoCommand actions on other resources. It evaluates a set of numeric trigger rules on every poll; the only dependency is the sensor to watch (plus whatever resources the actions target).
Actions. Each rule has an on_trigger and an on_resolve list. Every action names a resource (any component or service on the machine, by name) and a command (the DoCommand payload sent to it).
on_triggerfires when the rule transitions from not-triggered to triggered, and again at most once percooldown_secondswhile it stays triggered (with the defaultcooldown_seconds: 0, that's edge-only).on_resolvefires once when the reading clears the threshold.- Actions are best-effort: a failure is logged and never affects monitoring. Each named resource is automatically added as a dependency.
References and captures. String values in a command may embed {{...}} references, resolved just before the command is sent:
- Rule/reading context:
{{value}}(the current reading),{{key}},{{threshold}},{{operator}}. - Captured responses: set
"capture": "<name>"on an action to store itsDoCommandresponse, then reference its fields from later actions (includingon_resolve) as{{<name>.<field>}}.
A value that is exactly one reference keeps the referenced value's type; a reference embedded in a larger string is substituted as text. If a reference can't be resolved (e.g. nothing was captured), that action is skipped. A capture is overwritten on each cooldown re-fire (so resolve references the most recent response) and cleared once the rule resolves.
{
"sensor": "<string>",
"poll_interval_seconds": <number>,
"cooldown_seconds": <number>,
"timezone": "<string>",
"active_windows": [{ "days": ["<string>"], "start": "HH:MM", "end": "HH:MM" }],
"snooze_windows": [{ "days": ["<string>"], "start": "HH:MM", "end": "HH:MM" }],
"rules": [
{
"key": "<string>",
"operator": "<string>",
"threshold": <number>,
"on_trigger": [{ "resource": "<string>", "command": { }, "capture": "<string>" }],
"on_resolve": [{ "resource": "<string>", "command": { } }],
"active_windows": [{ "days": ["<string>"], "start": "HH:MM", "end": "HH:MM" }],
"snooze_windows": [{ "days": ["<string>"], "start": "HH:MM", "end": "HH:MM" }]
}
]
}| Name | Type | Required | Description |
|---|---|---|---|
sensor |
string | Yes | Name of the sensor dependency whose readings are monitored. |
rules |
array | Yes | One or more numeric trigger rules (see below). At least one rule is required. |
poll_interval_seconds |
number | No | How often the sensor is polled, in seconds. Defaults to 10. |
cooldown_seconds |
number | No | Minimum time between repeat on_trigger firings while a rule stays triggered. 0 (default) means fire only on the edge. |
active_windows |
array | No | Recurring weekly windows that rules are evaluated only inside, e.g. business hours. See Active and snooze windows. |
snooze_windows |
array | No | Recurring weekly windows during which no rule is evaluated, e.g. a nightly maintenance slot. Take precedence over active_windows. |
timezone |
string | No | IANA time zone that active and snooze windows are interpreted in, e.g. "America/New_York". Defaults to the machine's local time zone. |
Each entry in rules:
| Name | Type | Required | Description |
|---|---|---|---|
name |
string | No | Identifies the rule so it can be targeted by the snooze DoCommand. When set, must be unique across rules. Only named rules can be snoozed. |
key |
string | Yes | The reading key to watch, e.g. "temperature". Non-numeric or missing values are skipped. |
operator |
string | Yes | Comparison applied between the reading and threshold. One of >, >=, <, <=, ==, != (aliases: gt, gte, lt, lte, eq, ne). |
threshold |
number | Yes | The value the reading is compared against. |
on_trigger |
array | No | Actions to fire when the rule triggers (and on each cooldown window). Each is { "resource": <name>, "command": <DoCommand payload>, "capture": <name?> }. |
on_resolve |
array | No | Actions to fire when the rule clears. Same shape as on_trigger. |
active_windows |
array | No | Recurring windows this rule is evaluated only inside. Narrows the monitor-level active_windows; it cannot widen them. |
snooze_windows |
array | No | Recurring windows during which this rule is not evaluated, in addition to the monitor-level snooze_windows. |
Windows mute rules on a recurring weekly schedule. A muted rule behaves as if it had been snoozed with the snooze DoCommand: readings keep updating, but the rule is not evaluated and none of its actions fire. A condition that is still breaching when the rule is unmuted fires on the next poll. A rule that was already triggered when it was muted keeps that state, so its on_resolve fires once it is unmuted if the reading has cleared by then.
A rule is muted when any of these holds:
- The current time is inside any
snooze_windowsentry, at the monitor level or on the rule. - The monitor sets
active_windowsand the current time is inside none of them. - The rule sets
active_windowsand the current time is inside none of them.
Snooze windows therefore always win over active windows. Rule-level active windows can only narrow the monitor's.
Each window:
| Name | Type | Required | Description |
|---|---|---|---|
days |
string[] | No | Days the window starts on: mon, tue, wed, thu, fri, sat, sun (full names also accepted), or inclusive ranges like "mon-fri". Defaults to every day. |
start |
string | No | Time the window opens, "HH:MM" (24-hour). Defaults to "00:00". |
end |
string | No | Time the window closes (exclusive), "HH:MM". Defaults to "24:00" (end of day). |
Leave out both start and end for a whole-day window. If end is earlier than start, the window runs past midnight: {"days": ["fri"], "start": "22:00", "end": "06:00"} covers Friday 22:00 to Saturday 06:00. Day ranges may wrap around the week, e.g. "fri-mon".
To alert only during business hours (Mon–Fri 09:00–17:00), except during a Wednesday-morning maintenance slot:
{
"timezone": "America/New_York",
"active_windows": [{ "days": ["mon-fri"], "start": "09:00", "end": "17:00" }],
"snooze_windows": [{ "days": ["wed"], "start": "10:00", "end": "11:00" }]
}A low-bean alert that posts to Slack on trigger and adds a ✅ reaction to that same message on resolve, plus a fan toggled directly:
{
"sensor": "usage-sensor",
"poll_interval_seconds": 30,
"cooldown_seconds": 3600,
"rules": [
{
"key": "regular_grinds",
"operator": ">=",
"threshold": 20,
"on_trigger": [
{
"resource": "slack-notifier",
"command": { "command": "send", "text": "☕ Regular beans are low ({{value}}), please refill." },
"capture": "msg"
}
],
"on_resolve": [
{
"resource": "slack-notifier",
"command": { "command": "react", "name": "white_check_mark", "ts": "{{msg.ts}}", "channel": "{{msg.channel}}" }
}
]
},
{
"name": "too-hot",
"key": "temperature",
"operator": ">",
"threshold": 90,
"on_trigger": [{ "resource": "cooling-fan", "command": { "set": { "power": 1 } } }],
"on_resolve": [{ "resource": "cooling-fan", "command": { "set": { "power": 0 } } }]
}
]
}Returns the most recent readings from the monitored sensor, plus per rule a <key>_triggered boolean indicating whether it is currently firing and a <key>_snoozed boolean indicating whether its evaluation is currently suppressed, either by the snooze DoCommand or by its active and snooze windows.
{
"temperature": 95.0,
"humidity": 35.0,
"temperature_triggered": true,
"temperature_snoozed": false,
"humidity_triggered": false,
"humidity_snoozed": false
}check - Force an immediate poll of the sensor (instead of waiting for the next interval), evaluating all rules and firing any actions.
{"check": true}Returns:
{"check": "ok"}snooze - Suppress evaluation of a single named rule for a duration. While a rule is snoozed the sensor keeps polling and its Readings stay current, but that rule is not evaluated and its actions do not fire (other rules are unaffected). A condition still breaching when the snooze ends fires on the next poll. rule_name must match a rule's name; duration is a string with a unit of seconds (s), minutes (m), hours (h), or days (d), e.g. 30s, 90m, 24h, 5d.
{"snooze": {"rule_name": "too-hot", "duration": "30m"}}Returns the rule and the time the snooze expires (RFC 3339):
{"rule_name": "too-hot", "snoozed_until": "2026-07-11T15:52:04-04:00"}The current snooze state is also exposed in Readings as a <key>_snoozed boolean per rule.