Capability Definitions (v1)
Capability definitions describe what a device model can do in IOTMER:
- Which capabilities exist (
switch,metering, …) - Which channels exist (relays, metering channels, sensors, …)
- Which datapoints exist (fields shown in the UI / carried over MQTT)
- How datapoints behave (read/write, widgets, alarms, scaling)
- Optional command hints (
supported_commands) — parameter contracts may expand in later versions
This page is the human-facing companion to the normative Capability Definitions Spec v1. For templates, definitions are edited under Device templates → Capability definitions; see Device Templates.
Quickstart
Canonical shape — product and greenfield integrations use a single JSON object envelope with exactly these top-level keys:
{
"spec_version": "1.0",
"capability_definitions": []
}
For a valid saved definition, capability_definitions must contain at least one CapabilityDefinition (the empty array above only illustrates structure). Fill in real definitions below.
The capabilities array on the template (parallel string tags such as ["switch","metering"]) must align with the capability values you declare inside capability_definitions.
Canonical storage shape
Capability definitions must be modeled and persisted as this object envelope — { spec_version, capability_definitions }. No other layout is described here as “correct” for product semantics.
Console vs REST API
| Surface | What to follow |
|---|---|
| This page (product) | Only the envelope above. Use it when authoring definitions by hand or exporting portable JSON. |
| Console template editor | If the empty template still shows a bare [] or another placeholder, trust save-time validation for what the UI accepts until the editor default matches the envelope. Prefer aligning the console default with this envelope so docs and UI stay consistent. |
| Automation / SDKs | Request and response bodies follow CapabilityDefinitionsPayload in OpenAPI (REST API overview). That schema lists wire-level variants accepted by the backend—verify against live OpenAPI + backend behavior, not only this page. |
Required top-level keys
| Key | Type | Meaning |
|---|---|---|
spec_version | string (non-empty) | Opaque marker for clients: which interpretation rules apply. v1 validation typically checks presence, not semver formatting. |
capability_definitions | array (min length 1) | Each element is a CapabilityDefinition. |
Forward compatibility
Any unknown JSON keys anywhere inside the envelope must be preserved end-to-end (pass-through). IOTMER must not strip unknown keys.
CapabilityDefinition
Required keys
| Key | Type | Description |
|---|---|---|
capability | string enum | One of: switch, metering, sensor, input |
channels | array | Min length 1 |
datapoints | array | Min length 1 |
Optional keys
| Key | Type | Description |
|---|---|---|
supported_commands | string array | Allowed tokens: set, countdown, reset_energy. Primarily UI guidance for extra controls beyond defaults. |
Notes (v1): For switch, the set command is implicit even if omitted from supported_commands.
Channel
Represents an addressable MQTT channel identity (channel_id in topics).
Required keys
| Key | Type | Constraints |
|---|---|---|
id | string | snake_case, unique within the same CapabilityDefinition. Regex: ^[a-z][a-z0-9_]{0,31}$ |
label | string | Human-facing UI label, max length 64 |
Optional keys
| Key | Type | Rules |
|---|---|---|
linked_relay | string | Metering-only. Must reference a switch capability channel id within the same template. |
Datapoint
Datapoints are the atomic UI/MQTT fields (voltage, relay state, faults, …).
Required keys (all datapoints)
| Key | Type | Values / rules |
|---|---|---|
key | string | snake_case, unique within the same CapabilityDefinition. Regex: ^[a-z][a-z0-9_]{0,31}$ |
type | enum | boolean, float, integer, enum, fault |
mode | enum | ro (read-only), rw (read-write), wo (write-only) |
widget | enum | toggle, metric, gauge, bar, indicator, alarm_badge, enum_select, text |
scope | enum | per_channel — values per channel (MQTT segments usually include /{channel_id}). device — device-wide (segments usually omit /{channel_id}). |
Widget compatibility matrix (normative)
IOTMER rejects invalid combinations.
| type | mode | Allowed widgets |
|---|---|---|
boolean | rw | Must be toggle |
boolean | ro | Must be indicator |
float | ro | Must be one of metric, gauge, bar |
float | rw | Must be metric |
integer | ro | Must be one of metric, gauge, bar |
integer | rw | Must be metric |
enum | rw | Must be enum_select |
enum | ro | Must be text |
fault | (any) | Must be ro; widget must be alarm_badge |
Writeback (boolean RW toggles only)
Optional key:
| Key | Values | Meaning |
|---|---|---|
writeback | optimistic | confirmed | optimistic: UI updates immediately after issuing a command. confirmed: UI waits until the device publishes updated state / acknowledgement path is satisfied. |
Rules:
- Allowed only when
type="boolean"andmode="rw". - Reject:
mode="ro"withwritebackpresent; non-boolean datapoint withwritebackpresent.
Default if omitted: optimistic.
Numeric datapoints (float / integer)
Required
| Key | Type | Notes |
|---|---|---|
unit | string | Examples: V, A, W, kWh, °C, % |
Optional
| Key | Type | Default | Notes |
|---|---|---|---|
scale | number | 1 | Display: display_value = raw_value / scale. Should reject scale == 0. |
chart | boolean | false | If true: product wording — eligible for time-series pipelines. |
group | string | — | UI grouping (e.g. electrical, energy, environment). |
aggregation | string array | — | Elements: avg, max, min, sum. Valid only when chart=true. |
Alarm object (numeric only)
Optional example:
{
"condition": "gt",
"value": 16,
"severity": "critical"
}
| Field | Type |
|---|---|
condition | gt, lt, gte, lte |
value | number — threshold on display scale (after scale is applied) |
severity | warning, critical |
Reject: alarm object on type=fault.
Enum datapoints
Required
| Key | Type | Constraints |
|---|---|---|
enum_values | string array | Min 2, max 32 elements |
Fault datapoints
Required
| Key | Type | Description |
|---|---|---|
fault_bits | object | Keys: bit positions as strings "0" … "15". Values: fault codes as snake_case strings. |
Example:
{
"0": "over_voltage",
"1": "under_voltage",
"2": "over_current",
"3": "over_temperature"
}
Reject: invalid bit keys outside 0–15.
Platform baseline rules
Switch minimum standard
If capability="switch", the definition must include at least one datapoint:
key="state"type="boolean"mode="rw"widget="toggle"scope="per_channel"
Metering linkage rule
If linked_relay is present:
- It must appear only under metering channels.
- It must reference an existing switch channel
id. - Reject templates that define metering links without a
switchcapability.
default_config (template-level overrides)
default_config is JSON merged into effective device configuration.
Recognized keys include:
| Key | Type | Notes |
|---|---|---|
relay_count | integer | Required if switch exists; must equal switch channel count. |
energy_channel_count | integer | Required if metering exists; must equal metering channel count. |
relay_default_state | "on" | "off" | Default "off". |
timezone | string (IANA) | Default "UTC". |
ota_channel | "stable" | "beta" | Default "stable". |
max_offline_queue | integer | Range 10–1000, default 100. |
telemetry_interval_sec | integer | 10–3600, default 60. |
heartbeat_interval_sec | integer | 10–300, default 30. |
report_on_change | boolean | Default true. |
thresholds | object | Optional advisory tuning (see below). |
thresholds object (v1 advisory)
Example keys:
{
"over_current_a": 16,
"over_voltage_v": 260,
"under_voltage_v": 180,
"over_temp_c": 85
}
Greenfield stance: v1 treats thresholds as device tuning hints unless/until strict mapping to fault bits is standardized (stricter validation may arrive in v2).
MQTT mapping
Topic forms (same layout as console ACL examples — workspace slug first, then device key):
{workspace_slug}/{device_key}/{segment}
{workspace_slug}/{device_key}/{segment}/{channel_id}
Segment intent
| Segment | Typical direction | channel_id usage |
|---|---|---|
telemetry | device → cloud | Depends on datapoint scope |
state | device → cloud | Depends on datapoint scope |
cmd | cloud → device | Typically per channel |
cmd/ack | device → cloud | Typically per channel |
event | device → cloud | Channel-less topic; channel may appear in payload |
config | cloud → device | Channel-less |
config/ack | device → cloud | Channel-less |
presence | device → cloud | Channel-less |
heartbeat | device → cloud | Channel-less |
Scope rules (normative)
scope=per_channeldatapoints participate undertelemetry/{channel_id}and/orstate/{channel_id}(and related cmd paths as applicable).scope=devicedatapoints participate undertelemetryand/orstatewithout/{channel_id}.
Diagram: envelope vs scope
flowchart LR
subgraph env[Envelope]
SV[spec_version]
CD[capability_definitions array]
end
subgraph scope[Datapoint scope]
PC[per_channel → segments with channel_id]
DV[device → segments without channel_id]
end
Diagram: numeric alarm path
flowchart LR
RAW[raw_value] --> SCALE["÷ scale"]
SCALE --> DISP[display_value]
DISP --> CMP["compare vs alarm.value"]
Payload conventions (examples)
telemetry (typical numeric raw integers)
{
"voltage": 2200,
"current": 153,
"power": 3360,
"energy": 12400,
"fault": 0,
"ts": 1714050000
}
state (relay)
{ "state": true, "fault": 0, "ts": 1714050000 }
cmd examples
{ "cmd": "set", "state": true }
{ "cmd": "countdown", "seconds": 3600 }
event payload rule
Because event topics are channel-less:
- If the event is channel-specific → payload must include
"channel": "<channel_id>". - If the event is device-wide → payload must not include
channel.
Example:
{
"key": "relay_changed",
"channel": "relay_1",
"state": true,
"triggered_by": "user",
"ts": 1714050000
}
Mobile / UI “reported” normalization (recommended)
Mobile clients should normalize readings into:
{
"reported": {
"{capability}": {
"{channel_id_or_device}": {
"{datapoint_key}": 0
}
}
}
}
Convention:
- For
scope=per_channel:{channel_id_or_device}is the real channel id (relay_1,ch_1, …). - For
scope=device: use reserved bucket keydeviceinstead of a channel id.
Validation summary (checklist)
IOTMER rejects (non-exhaustive checklist for integrators):
- Invalid enums (
capability,type,mode,widget,scope, …) - Empty
channels/ emptycapability_definitions - Duplicate channel
ids or duplicate datapointkeys within a capability - Invalid widget matrix combinations
- Invalid fault bit keys (
0–15strings only) aggregationwithoutchart=true- Illegal
writebackplacement (non-boolean or non-RW) - Inconsistent
relay_count/energy_channel_countvs channel lengths - Metering
linked_relaywithoutswitchcapability or unresolved relay id
Size limits
Envelope JSON must not exceed 1 MiB (recommended enforcement at template save paths).
See also
- Device Templates — where definitions are edited in the console
- MQTT Overview — broker topics and ACL context
- REST API overview —
CapabilityDefinitionsPayload/ template APIs for programmatic clients (wire shapes vs this product envelope)