Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 41 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
The `viam:sensor-bundle` module provides these models:

1. **`viam:sensor-bundle:stateful-sensor`** - A sensor component that holds a value you set via `DoCommand`, serves it through the Readings API, and persists it to a file on disk so it survives restarts.
2. **`viam:sensor-bundle:sensor-monitor`** - A sensor component that watches another sensor's readings against numeric trigger rules and sends a notification via a generic service's `DoCommand` when a threshold is crossed.
2. **`viam:sensor-bundle:sensor-monitor`** - A sensor component that watches another sensor's readings against numeric trigger rules and fires `DoCommand` actions on other resources when a rule triggers or resolves.

---

Expand Down Expand Up @@ -69,30 +69,35 @@ Returns:

**API:** `rdk:component:sensor`

Monitors the readings of another sensor and fires notifications when a numeric reading crosses a configured threshold. It takes two dependencies — the sensor to watch and a generic service to notify — and evaluates a set of numeric trigger rules on every poll.
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).

When a rule fires, the monitor calls `DoCommand` on the notifier with the payload:
**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).

```json
{"command": "send", "text": "<notification message>"}
```
- `on_trigger` fires when the rule transitions from not-triggered to triggered, and again at most once per `cooldown_seconds` while it stays triggered (with the default `cooldown_seconds: 0`, that's edge-only).
- `on_resolve` fires 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:

Notifications are **edge-triggered**: a message is sent when a rule transitions from not-triggered to triggered. While the rule stays triggered no further message is sent, unless `cooldown_seconds` is set, in which case the message repeats at most once per cooldown window. When the reading clears the threshold and crosses it again, a new notification is sent.
- Rule/reading context: `${value}` (the current reading), `${key}`, `${threshold}`, `${operator}`.
- Captured responses: set `"capture": "<name>"` on an action to store its `DoCommand` response, then reference its fields from later actions (including `on_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.

### Configuration

```json
{
"sensor": "<string>",
"notifier": "<string>",
"poll_interval_seconds": <number>,
"cooldown_seconds": <number>,
"rules": [
{
"key": "<string>",
"operator": "<string>",
"threshold": <number>,
"message": "<string>"
"on_trigger": [{ "resource": "<string>", "command": { }, "capture": "<string>" }],
"on_resolve": [{ "resource": "<string>", "command": { } }]
}
]
}
Expand All @@ -101,10 +106,9 @@ Notifications are **edge-triggered**: a message is sent when a rule transitions
| Name | Type | Required | Description |
| ----------------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sensor` | string | **Yes** | Name of the sensor dependency whose readings are monitored. |
| `notifier` | string | **Yes** | Name of the generic service dependency that receives notification `DoCommand`s. |
| `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 notifications while a rule stays triggered. `0` (default) means no repeat until the rule clears and fires again. |
| `cooldown_seconds` | number | No | Minimum time between repeat `on_trigger` firings while a rule stays triggered. `0` (default) means fire only on the edge. |

Each entry in `rules`:

Expand All @@ -113,27 +117,43 @@ Each entry in `rules`:
| `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. |
| `message` | string | No | Notification template. Supports placeholders `{key}`, `{value}`, `{threshold}`, `{operator}`. If omitted, a message like `temperature is 95 (> 90)` is used. |
| `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`. |

### Example Configuration

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:

```json
{
"sensor": "outdoor-temp",
"notifier": "slack-notifier",
"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}" }
}
]
},
{
"key": "temperature",
"operator": ">",
"threshold": 90,
"message": "High temperature alert: {key} is {value} (limit {threshold})"
},
{
"key": "humidity",
"operator": "<",
"threshold": 20
"on_trigger": [{ "resource": "cooling-fan", "command": { "set": { "power": 1 } } }],
"on_resolve": [{ "resource": "cooling-fan", "command": { "set": { "power": 0 } } }]
}
]
}
Expand All @@ -154,7 +174,7 @@ Returns the most recent readings from the monitored sensor, plus a `<key>_trigge

### DoCommand

**`check`** - Force an immediate poll of the sensor (instead of waiting for the next interval), evaluating all rules and sending any notifications.
**`check`** - Force an immediate poll of the sensor (instead of waiting for the next interval), evaluating all rules and firing any actions.

```json
{"check": true}
Expand Down
Loading