Part of: PureLife Device Protocol Version: 1.0 (Draft 0.1, 2026-09-17)
Warning
This specification is currently in draft status. The definitions, procedures and data structures described herein are subject to change without notice.
The interfaces documented here are not yet implemented and are provided for design and planning purposes only.
This document specifies how a provisioned Device communicates with its Server during normal operation. It has two transports:
| Transport | Used for | Message formats |
|---|---|---|
| HTTP, with or without TLS | Authentication, bootstrap, pairing token | Reference, HTTP part (OpenAPI 3.1) |
| MQTT, with or without TLS | Status, configuration, room, logging, telemetry, commands, events | Reference, MQTT part (AsyncAPI 3.0) |
The PureLife Device Protocol Reference (OpenAPI and AsyncAPI) is the reference for message formats. This document holds the normative text that does not fit into them: the trust model, certificate requirements, the assertion format, and the behaviour rules for the Device.
All HTTP operations are relative to the Server URL the Device received
during provisioning (field server_url of endpoint pld-config of the
Device Provisioning Protocol). The Server URL
is an origin with an optional path prefix and without trailing slash, for
example https://cloud.smart-altern.de. In URL templates below it is written
{serverUrl}. Neither the Device nor the Server normalises the Server URL:
the Device uses it byte for byte as provisioned (the Provisioner already
rejects a trailing slash), and the Server compares the aud claim byte for
byte with the URL it is configured with.
Every path of this protocol starts with /pld/v1. The v1 is the MAJOR
version of the Device Messaging Protocol. A new MAJOR version gets a new
path prefix. MINOR versions add fields and operations without changing the
prefix.
POST {serverUrl}/pld/v1/auth
PUT {serverUrl}/pld/v1/firmware
GET {serverUrl}/pld/v1/bootstrap
POST {serverUrl}/pld/v1/pairing-token
Both transports work with and without TLS. The scheme of the provisioned
Server URL (https or http) and of mqtt.url from the bootstrap
response (mqtts or mqtt) decides. The Device MUST support both.
Three parties hold key material:
| Party | Holds | Never leaves the party |
|---|---|---|
| Manufacturer | Manufacturer CA key pair | CA private key |
| Device | Device key pair and Device certificate | Device private key |
| Server | CA certificates of all known Manufacturers, Device public keys after first contact | – |
The chain of trust:
The PoP of the Device Provisioning Protocol plays no role here.
| Property | Requirement |
|---|---|
| Format | X.509 v3, self-signed |
| Key | EC, curve P-256 (prime256v1) |
| Signature algorithm | ecdsa-with-SHA256 |
| Subject | O = Manufacturer identifier (as used in pld-info), CN free |
| Validity | Chosen by the Manufacturer. RECOMMENDED: at least 20 years. |
| Extensions | basicConstraints CA:TRUE (critical), keyUsage keyCertSign, cRLSign (critical) |
A Manufacturer MAY use an intermediate CA. In that case the intermediate certificate is part of the chain the Device presents (see section 6.2), and only the root is embedded in the Server.
A Manufacturer MAY have several CA certificates at the same time, for example during a key rotation. The Server accepts a Device certificate if it chains to any CA certificate registered for that Manufacturer.
| Property | Requirement |
|---|---|
| Format | X.509 v3 |
| Issuer | A Manufacturer CA (or intermediate) of the Manufacturer named in the subject |
| Key | EC, curve P-256 (prime256v1) |
| Signature algorithm | ecdsa-with-SHA256 |
| Subject | CN = Device ID, O = Manufacturer identifier. Both exactly as reported in pld-info. |
| Serial number | Unique per CA |
| Validity | notBefore = issue date, notAfter = 99991231235959Z (no well-defined expiration, RFC 5280 section 4.1.2.5) |
| Extensions | basicConstraints CA:FALSE, keyUsage digitalSignature (critical), extendedKeyUsage clientAuth |
| Size | DER encoding at most 2048 bytes |
The Server does not use CRLs or OCSP. A Device is revoked by blocking it on the Server.
POST {serverUrl}/pld/v1/auth exchanges a signed assertion for an access
token. The operation is unauthenticated at the HTTP level; the assertion
carries the authentication. Request and response formats are defined in
Reference, HTTP part.
{ "assertion": "<compact JWS>" }
The body MUST NOT exceed 8192 bytes; a larger body is answered with 413.
The assertion is a JWT (RFC 7519) in compact JWS serialisation (RFC 7515), modelled on the client assertion of RFC 7523. It is signed with the Device private key and carries the Device certificate chain in its header. Header parameters, claims and their constraints are defined in the Reference, HTTP part, operation Authenticate a Device.
Rules:
x5c) and that the Device holds the
private key (signature). The device claim carries the identity data
of pld-info, so that it is signed as well.iat and exp are in seconds, as required by RFC 7519. All other
timestamps in the PureLife Device Protocol are milliseconds.jti.assertion_expired or assertion_not_yet_valid.The Server performs these checks in order and rejects at the first failure with the listed error code:
| # | Check | Error code | HTTP |
|---|---|---|---|
| 1 | Body is JSON with a string field assertion |
invalid_request |
400 |
| 2 | Assertion parses as compact JWS, header has alg = ES256 and a well-formed x5c |
invalid_assertion |
400 |
| 3 | Claims iss, sub, aud, iat, exp, jti, device present and well-formed, iss = sub |
invalid_assertion |
400 |
| 4 | device.manufacturer is a known Manufacturer |
unknown_manufacturer |
401 |
| 5 | Certificate chain in x5c validates against a CA certificate of that Manufacturer, leaf meets section 4, leaf CN = iss, leaf O = device.manufacturer |
invalid_certificate |
401 |
| 6 | JWS signature verifies with the leaf public key | invalid_signature |
401 |
| 7 | aud equals the Server's own URL byte for byte (no normalisation) |
audience_mismatch |
401 |
| 8 | iat ≤ now + 300 s, exp > now − 300 s, exp − iat ≤ 300 s |
assertion_expired / assertion_not_yet_valid |
401 |
| 9 | jti not seen before within the validity window |
assertion_replayed |
401 |
| 10 | Device is not blocked | device_blocked |
403 |
| 11 | Device unknown, or known with the same public key | key_mismatch |
409 |
On success:
device claim.A Device that fails check 11 stays usable with its stored key. An administrator can accept the new key on the Server.
{ "access_token": "<opaque>", "token_type": "Bearer", "expires_in": 3600 }
expires_in is the lifetime in seconds from the moment of issue. The
value is a Server setting; the default is 3600. The Device MUST NOT
assume a fixed lifetime.
Authorization: Bearer <access_token>.expires_in has elapsed or when any request answersGET {serverUrl}/pld/v1/bootstrap returns everything the Device needs
to start operating: the Server time, the pairing state and the MQTT
connection data. Fields: Reference, HTTP part.
The request carries no data about the Device. The Server identifies the
Device by the access token and already holds its identity, public key and
the values of the device claim from the authentication
(section 6.2). The device object in the response is an
echo of that identity for the Device to verify.
Rules:
mqtt object immediately after receiving it
and MUST use the most recently persisted values, unchanged, for every
MQTT connection: client identifier, credentials, keep-alive, will delay
and session expiry.device.paired is informational. The Device behaves the same whether
it is paired or not.Every Device supports firmware updates over HTTP. The update check runs
in the start-up sequence before the bootstrap, so a Device only
connects to the broker with the firmware the Server expects. It also runs
on the command firmware-check and at least once every 24 hours while
the Device is operating (authenticating first if the access token has
expired; a bootstrap is not part of that periodic check).
PUT {serverUrl}/pld/v1/firmware, with the access token. The Device
reports its installed components, the Server answers with the updates to
install. Fields: Reference, HTTP part, operation
Report firmware and get updates.
Rules:
id, for example mcu, radar or ble. The component mcu is
mandatory: it is the main controller firmware, and its version is the
firmware version of the Device. Which other components exist is up to
the Manufacturer.lastUpdate), so that the Server can react to
failures instead of offering the same update again and again.updates, in installation order, or with an
empty list when the Device is up to date. Every entry carries a signed
download URL with its expiry, the size and the SHA-256 of the file.GET of the signed URL, without any Authorization header,
before expiresAt. The scheme follows section 1.1.
The Server supports HTTP Range requests; the Device MAY resume an
interrupted download.lastUpdate with result failed and a
reason. An update has failed when the download, the verification or
the writing fails, when the new firmware does not boot (crash, watchdog,
or the start-up sequence cannot be started), or when the new firmware
has not completed one successful authentication within 30 minutes after
the restart despite repeated attempts. A Server that is unreachable for
that long causes a rollback as well; the Server then offers the update
again at the next check, which is harmless.An update can also be triggered from the Server at any time with the
command firmware-check (section 17.7).
POST {serverUrl}/pld/v1/pairing-token creates a pairing token. The
Device requests it when the Provisioner sends StartPairingCommand (Device
Provisioning Protocol, section 11). The operation requires an access token
and has no request body. Response format:
Reference, HTTP part.
{ "token": "<43 characters base64url>", "expiresAt": 1758096600000 }
Rules:
expiresAt (Unix
milliseconds) is authoritative; the lifetime is a Server setting.token and expiresAt to the Provisioner unchanged.
It MUST NOT log the token and MUST discard it when the provisioning
session ends.Redeeming the token is not part of this protocol. The Provisioner sends
the token and the Device ID to the PureLife Cloud API (operation Pair a
sensor), authenticated as a user; the Server pairs the Device with that
user's Customer. A Device that is paired with another Customer is moved
without further confirmation. The Device is not informed actively; it
sees the result in device.paired on its next bootstrap.
POST /pld/v1/auth.PUT /pld/v1/firmware. If the Server returns updates: install them,
restart, start again at step 1 (section 9).GET /pld/v1/bootstrap, persist the MQTT credentials.The Device authenticates only when it needs an access token. An expired token by itself is no reason to authenticate, and a running MQTT session is never touched by an authentication.
| Trigger | Action |
|---|---|
| Start-up | Full start-up sequence (section 11.1). |
| An HTTP request is due (firmware check, pairing token) and the token has expired, or the request answers 401 | Authenticate, then send the request. No bootstrap; the MQTT session stays up. |
| Broker rejects the connection as not authorized | Start-up sequence from step 3: authenticate, check firmware, bootstrap, connect. Do not retry the old credentials. |
Network or Server changed by a command (wifi, server) |
Start-up sequence from step 3 with the new values (sections 17.3 and 17.4). |
The Device MUST NOT authenticate more often than necessary. In particular it MUST NOT authenticate before every request.
Every non-2xx response carries an error body in the form of the Google
JSON Style Guide: error.code repeats the HTTP status, error.message is
human readable, and error.errors[0].reason is the machine-readable
reason. The reasons are listed in section 6.3
and in the OpenAPI document.
{ "error": { "code": 401, "message": "signature verification failed", "errors": [ { "reason": "invalid_signature" } ] } }
| HTTP status | Device behaviour |
|---|---|
| 400 | Implementation error. Log, then retry with backoff. |
401 (auth) |
Log the error code. Retry with backoff. For assertion_expired and assertion_not_yet_valid re-synchronise the clock first. |
| 401 (other operations) | Authenticate again. |
403 device_blocked |
Stop. Retry not more often than once per hour. Show the LED state Server rejected Device. |
409 key_mismatch |
Stop. Retry not more often than once per hour. Show the LED state Server rejected Device. |
| 413 | Implementation error (body too large). Log, do not retry until firmware changes. |
| 429, 503 | Wait for the duration in the Retry-After header, at least 10 seconds, then retry. |
| 5xx (other), network or TLS error | Retry with backoff. Show the LED state Server not reachable. |
Backoff: exponential, starting at 5 seconds, doubling, capped at 5 minutes, with a random jitter of ±20 % on every step.
In addition to the LED states of the Device Provisioning Protocol:
| Situation | LED |
|---|---|
| Server rejected Device (403, 409) | red, steady |
| Firmware update in progress (download or installation) | green, blinking 1 Hz |
Fall detected (capability fall, fall.ledEnabled is true) |
red, blinking 2 Hz, as long as the Device reports the fall |
system.ledEnabled = false switches the LED off in Operating
altogether; the fall pattern is then not shown either.
The Device has a speaker for system sounds. Which sounds exist and how
they sound is up to the Manufacturer, with one exception: a detected fall
is signalled by a sound (capability fall). Two switches in the
configuration control this:
| Setting | Effect |
|---|---|
system.soundEnabled = false |
No sound at all in Operating, including the fall sound. |
fall.soundEnabled = false |
No sound on a detected fall; other system sounds stay. |
Production process, per Device:
CN = Device ID and O = Manufacturer identifier).Once, per Manufacturer:
Example with OpenSSL (Manufacturer CA, then one Device certificate from a CSR generated on the Device):
# Manufacturer CA, once
openssl ecparam -name prime256v1 -genkey -noout -out ca.key
openssl req -x509 -new -key ca.key -sha256 -days 7300 \
-subj "/O=example-manufacturer/CN=Example Manufacturer Device CA" \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,keyCertSign,cRLSign" \
-out ca.crt
# Device certificate, per Device, from the CSR the Device produced
openssl x509 -req -in device.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-sha256 -not_after 99991231235959Z \
-extfile <(printf "basicConstraints=CA:FALSE\nkeyUsage=critical,digitalSignature\nextendedKeyUsage=clientAuth\n") \
-out device.crt
Message formats are defined in Reference, MQTT part.
The AsyncAPI document is written from the Device's point of view: send
operations are published by the Device, receive operations are
subscribed by the Device.
All topics have the form
pld/v1/{clientId}/{category}[/{sub}]
| Segment | Meaning |
|---|---|
pld |
Namespace of the PureLife Device Protocol. |
v1 |
MAJOR version of the Device Messaging Protocol. Same rule as the HTTP path prefix. |
{clientId} |
mqtt.clientId from the bootstrap response: 1 to 64 characters from a-z, 0-9 and -, so that it is always a valid single topic segment. |
{category} |
One of the categories below. |
| Topic | Direction | QoS | Retained | Purpose |
|---|---|---|---|---|
status |
Device → Server (and broker via Last Will) | 1 | yes | Connection state, section 14 |
log |
Device → Server | 0 | no | Log batches, section 16 |
config/desired |
Server → Device | 1 | yes | Desired configuration, section 15 |
config/reported |
Device → Server | 1 | yes | Configuration in use, section 15 |
room/desired |
Server → Device | 1 | yes | Desired room configuration, section 19 |
room/reported |
Device → Server | 1 | yes | Room configuration in use, section 19 |
event/presence |
Device → Server | 1 | no | Presence state, section 20.1 |
event/fall |
Device → Server | 1 | no | Fall detection, section 20.2 |
telemetry |
Device → Server | 0 | no | Periodic health values, section 18 |
cmd/{command} |
Server → Device | 1 | no | Commands, section 17 |
ack/{command} |
Device → Server | 1 | no | Command acknowledgements, section 17 |
Rules:
pld/v1/{clientId}/. The broker enforces this with access control
rules: the Device may publish to status, log, event/#, telemetry,
config/reported, room/reported and ack/#, and may subscribe to
config/desired, room/desired and cmd/#.cmd/#.application/json on every publish.mqtts the Device validates the broker
certificate against its store of public CA certificates; with mqtt the
connection is unencrypted (see section 1.1).config/desired, room/desired (capability room) and
cmd/#,status (online),config/reported and, with capability room,
room/reported.pld/v1/{clientId}/status holds the connection state of the Device as a
retained message.
pld/v1/{clientId}/status, QoS 1, retained, payload
{"state":"offline"}, with the Will Delay Interval from the bootstrap
response. A connection without this will is not compliant; the Server
relies on it to detect a lost Device. The broker publishes the will when
the connection is lost and the Device has not reconnected with the same
client identifier within the delay. The will carries no timestamp; the
Server uses its receive time.online
message (retained, QoS 1) with timestamp, protocolVersion,
firmwareVersion, uptime, capabilities and, with capability room,
the room limits object (section 19.2).{"state":"offline"} itself and then disconnect normally; the
broker does not send the will after a normal disconnect.A silent connection loss becomes visible on the Server after at most 1.5 × keep-alive plus the will delay. With the defaults that is 50 seconds. The broker MAY additionally track connections on its own; that is Server-internal and not part of this protocol.
capabilities in the online message declares what the Device does.
The Server uses it to decide which events to expect, which configuration
groups to send, and what to show in the user interface. Defined values:
| Capability | Meaning |
|---|---|
log |
The Device publishes log batches (section 16). |
presence |
The Device detects presence and sends presence events; configuration group presence applies. |
fall |
The Device detects falls and sends fall events; configuration group fall applies. |
room |
The Device has a detection area with regions; the room configuration (section 19) applies and the online message carries the room limits object. |
targets |
Presence events carry the tracked targets with coordinates (section 20.1); fall events carry the position of the fallen person. |
posture |
Targets carry a body posture. Requires targets. |
confidence |
Targets and fall events carry a confidence value. |
This list is closed: a Device MUST NOT announce any other value. A Device without a capability MUST NOT send the corresponding events and MUST ignore the corresponding configuration group.
Configuration follows the desired / reported pattern.
config/desired, retained, QoS 1, always as a complete document
with a revision that increases with every change.config/reported, retained, QoS 1,
as a complete document with the same revision. Values it rejects keep
their previous value and are listed in errors as objects with
location (dotted path of the field) and message (short reason).config/reported after every connection,
so that the Server can detect a Device that lost its configuration
(revision 0 means factory defaults).config/reported. There is no read request.Capabilities and groups:
system) and
the groups whose capability the Device announced in its last online
message. A Device without the fall capability never receives a fall
group.desired. It does not list them in errors.desired with the groups adjusted (new groups with
default values) and an increased revision.| Group | Applies |
|---|---|
system |
always |
presence |
capability presence |
fall |
capability fall |
Parameters, defaults and ranges of every group: Reference, MQTT part,
schema ConfigDesired. A parameter that is absent keeps its default. The
behaviour behind the parameters is specified where it applies: telemetry
in section 18, LED and sound in
sections 11.4 and 11.5, presence and fall reporting in
section 20.
A Device with the capability log publishes log batches on
pld/v1/{clientId}/log, QoS 0, not retained.
error triggers an immediate publish of the batch.error, warn, info, debug.
Entries below system.logLevel are not sent. Default level info.timestamp (Unix milliseconds) and level,
an optional tag naming the component, and message of at most 256 bytes.The Server sends commands on pld/v1/{clientId}/cmd/{command} (QoS 1,
not retained). The Device answers on pld/v1/{clientId}/ack/{command}
(QoS 1, not retained). Message formats:
Reference, MQTT part.
| Command | Purpose |
|---|---|
wifi |
Change the Wi-Fi network, with test and fallback. |
server |
Change the Server (and optionally NTP and DNS servers), with test before switching. |
reboot |
Restart the Device. |
wifi-scan |
Report the visible Wi-Fi networks. |
firmware-check |
Check for firmware updates now and install them. |
{ "id": "3cb44b34-5421-4c7d-bb15-2f40422b3947", "issuedAt": 1758096000000, "expiresAt": 1758096300000, "params": { } }
Every command carries an id assigned by the Server, the times it was
issued and expires, and a command-specific params object (empty if the
command has no parameters). Fields: Reference, MQTT part,
schema CommandEnvelope. The command name is the topic, not part of the
payload.
The lifetime defaults to 5 minutes and is at most 1 hour. The Server also sets the MQTT 5 Message Expiry Interval to the remaining lifetime, so that the broker drops commands that expire while the Device is offline.
The Device:
expiresAt has passed (expired);id it has already seen (it keeps at least the
last 16 ids);wifi, server, reboot, wifi-scan and
firmware-check at a time and rejects others with busy while one is
running;unknown_command.Every command is acknowledged in two steps: first accepted or
rejected, then, for accepted commands, done or failed.
{ "id": "3cb44b34-...", "timestamp": 1758096001000, "status": "accepted" }
{ "id": "3cb44b34-...", "timestamp": 1758096045000, "status": "done" }
| Status | Meaning |
|---|---|
accepted |
Received, parameters valid, execution starts. Sent within 5 seconds of receipt. |
rejected |
Not executed. reason is one of invalid_argument, expired, busy, unknown_command, not_supported, internal. detail MAY explain. |
done |
Executed successfully. result carries command-specific data where defined. |
failed |
Execution failed. For wifi this means the previous network is active again. stage and reason use the values of the Server check of the Device Provisioning Protocol (WIFI, DNS, NTP, TLS, SERVER, MQTT and the CheckError names), httpStatus where applicable. |
Rules:
accepted.
If it does not arrive, the Server treats the command as failed with an
unknown outcome.wifi, server,
reboot), the Device sends the final acknowledgement after it has
reconnected. It keeps the command id in memory for wifi and server
and in persistent storage for reboot.status
offline (retained) and disconnects normally, so that the status topic
stays accurate and no Last Will is triggered.wifiparams: ssid (1 to 32 bytes), passphrase (empty for an open network,
otherwise 8 to 63 bytes), optional bssid (AA:BB:CC:DD:EE:FF), optional
channel (1 to 14).
accepted.status offline, disconnect normally.WIFI, DNS, NTP,
TLS, SERVER, MQTT of the Server check with the committed
configuration (Server URL, DNS and NTP servers stay unchanged). SERVER
includes authentication and bootstrap, so the MQTT credentials are
rotated; MQTT is a test connection to the broker. Time budget:
60 seconds.status online is published,
send done.failed with
stage and reason. If the previous network cannot be reached either,
the Device stays in its normal reconnect behaviour on the previous
network; the acknowledgement follows when it is back.serverparams: serverUrl, optional ntpServers, optional dnsServers,
with the constraints and partial-update semantics of SetConfigCommand of
the Device Provisioning Protocol.
accepted.DNS,
NTP, TLS, SERVER, MQTT of the Server check against the new
values. Time budget: 60 seconds. SERVER includes authentication and
bootstrap at the new Server; as a side effect the Device is registered
there (unpaired) and receives that Server's MQTT connection data.
MQTT is a test connection to the new broker with those data, opened
next to the current session and closed again.done to the current
broker, publish status offline, disconnect normally, and run the
start-up sequence from step 3 against the new Server (authentication,
firmware check, bootstrap, connect).failed with stage and reason. Nothing is changed and
the Device stays connected to the current broker.The previous Server marks the Device as inactive when it receives done.
rebootparams: optional delay in seconds (0 to 60, default 0).
accepted.status offline, disconnect normally, wait delay, restart.status online, then done for the
stored command id.wifi-scanparams: none.
accepted.done with result.networks: at most 20 entries, strongest first, each
with ssid, bssid, rssi (dBm), channel and auth (open, psk,
enterprise, other). Networks with hidden SSID are omitted.firmware-checkparams: none.
accepted.PUT /pld/v1/firmware as in section 9.1.done with result.updates: the components and target versions the
Server returned, possibly empty. On an HTTP or network error failed
with stage and reason as for wifi and server.status offline, disconnect normally,
install as in section 9.2, restart, and run the
start-up sequence. The result becomes visible to the Server through the
next firmware check.Every Device publishes periodic health values on
pld/v1/{clientId}/telemetry, QoS 0, not retained: uptime, Wi-Fi
signal and access point, reconnect counters, clock state, free memory
and, if the Device has a sensor, its temperature. Fields:
Reference, MQTT part, schema Telemetry.
Telemetry describes the Device, not what it measures. Measurement data goes into events, occurrences into the log. Telemetry therefore carries no personal data.
Rules:
system.telemetryEnabled is true.status and config/reported), then every
system.telemetryInterval seconds. Each interval gets a random jitter
of ±10 %, so that many Devices do not send in lockstep after a power
outage.wifi.reconnects, mqtt.reconnects) start at zero on
boot and are never reset otherwise. memory.minFree is the lowest free
memory observed since boot.clock.offset is calculated from the bootstrap: the Device notes its own
time when sending the request (t0) and when receiving the response
(t1) and computes offset = t1 − (serverTime + (t1 − t0) / 2). A
positive value means the Device clock is ahead. The value is kept until
the next bootstrap. It shows clock drift and failed NTP synchronisation
before they break authentication (tolerance 300 s).
A Device with the capability room monitors a detection area around
itself that can contain regions. The room configuration follows the
same desired / reported pattern as section 15, on its own topics and
with its own revision:
| Topic | Direction | QoS | Retained |
|---|---|---|---|
pld/v1/{clientId}/room/desired |
Server → Device | 1 | yes |
pld/v1/{clientId}/room/reported |
Device → Server | 1 | yes |
Message formats: Reference, MQTT part.
All coordinates are integers in centimetres, relative to the Device:
x runs to the right (positive) and to the left (negative).y runs to the front (positive) and to the back (negative).z runs upwards from the floor. The floor is z = 0, the Device is at
z = sensorHeight.What "front", "left" and "right" mean on the housing is defined by the Manufacturer in the documentation of the Device (for example by a marking or the position of the LED). The Server shows it to the installer.
With the capability room the online message (section 14) carries a
room object that describes what the Device supports: the maximum number
of regions, whether z is evaluated, the supported mountings with the
largest extent per direction and the allowed mounting height, and the
allowed range of the durations. Fields: Reference, MQTT part,
schema RoomLimits. The Server uses it to validate and to render the
room editor.
"room": {
"maxRegions": 8,
"zAxis": true,
"mountings": {
"wall": { "left": 300, "right": 300, "front": 400, "back": 0, "sensorHeight": { "min": 150, "max": 330 } },
"ceiling": { "left": 300, "right": 300, "front": 200, "back": 200, "sensorHeight": { "min": 200, "max": 330 } }
},
"duration": { "min": 1, "max": 300 }
}
The room document consists of the mounting, the mounting height, the
detection area, the durations of the area and the regions. Fields:
Reference, MQTT part, schemas RoomDesired and
RoomReported.
{
"revision": 3,
"mounting": "wall",
"sensorHeight": 220,
"area": { "x": { "min": -200, "max": 200 }, "y": { "min": 0, "max": 300 }, "z": { "min": 0, "max": 180 } },
"enterDuration": 120,
"exitDuration": 120,
"regions": [
{
"id": "a7f07121-e084-45c8-a61f-4a5c80a13a24",
"kind": "bed",
"x": { "min": -120, "max": -20 },
"y": { "min": 60, "max": 260 },
"z": { "min": 0, "max": 80 },
"presenceEnabled": true,
"fallEnabled": false,
"enterDuration": 60,
"exitDuration": 120
}
]
}
A room document is valid when all of the following hold:
mounting is one of the mountings the Device announced, and
sensorHeight lies within the range announced for it.x.min ≤ -30, x.max ≥ 30, y.max ≥ 30, and y.min ≤ -30 for
ceiling. For wall, y.min is 0. No side exceeds the announced
limit: -x.min ≤ left, x.max ≤ right, y.max ≤ front, -y.min ≤ back.z is present in the area and in every region exactly when the Device
announced zAxis; then 0 ≤ z.min < z.max ≤ sensorHeight.min < max on every axis and
lies entirely inside the area. Regions do not overlap; overlap is judged
on the x/y rectangle, independent of z. There are at most
maxRegions regions.duration range. A region
without its own durations uses those of the area.Region ids are assigned by the Server and referenced by the Device in events. The kind of a region has no effect the Server prescribes; what the Device makes of it is up to the Manufacturer.
room/desired retained, always as a complete
document, only to Devices with the capability room, and only with
values inside the limits the Device announced.room/reported with the previous values,
the revision of the rejected document and errors naming the fields
(location such as regions[2].x.max, message such as outside area). A partially applied room configuration never exists.room/reported.room/reported after applying a desired document
and after every connection. revision 0 means no room configuration
has been received yet; the Device then monitors nothing beyond its
default area, which the Manufacturer defines.Events report what the Device detects. They are state reports, not transitions: the Device sends the current state, the Server derives "entered" or "left" by comparing consecutive reports. A lost report therefore costs no transition.
| Topic | Capability | QoS | Retained | Buffered while offline |
|---|---|---|---|---|
pld/v1/{clientId}/event/presence |
presence |
1 | no | no |
pld/v1/{clientId}/event/fall |
fall |
1 | no | yes |
Message formats: Reference, MQTT part. Every event
carries timestamp, the time of the observation on the Device in Unix
milliseconds. Region identifiers refer to the room configuration in use
(section 19); a Device without the capability room sends no region data.
Optional parts of the events are tied to capabilities and are then always present:
| Capability | Adds |
|---|---|
targets |
count and targets[] in presence events, position in fall events |
posture |
posture on every target (requires targets) |
confidence |
confidence on every target and on every fall event |
A presence event states whether anyone is in the detection area, which
regions are occupied and, with the capability targets, the tracked
targets with position, posture and confidence. Fields:
Reference, MQTT part, schema PresenceEvent.
{
"timestamp": 1758096300000,
"present": true,
"count": 1,
"regions": [ { "id": "a7f07121-e084-45c8-a61f-4a5c80a13a24", "present": true } ],
"targets": [ { "id": 3, "type": "person", "x": -40, "y": 120, "z": 90, "posture": "sitting", "confidence": 0.82 } ]
}
Rules:
present and the region states are debounced: they change only
after the enterDuration or exitDuration of the area or region has
elapsed. regions lists every region with presenceEnabled.present changes,
when the set of occupied regions changes, or when the posture of any
target changes. Otherwise it sends one at the latest every
presence.interval seconds. A change of position alone does not
trigger an event.presence.enabled is false.presence.interval seconds.A fall event states the current state of one fall: when it started, how
sure the Device is, where the person is and, once it is over, when and
why it ended. Fields: Reference, MQTT part,
schema FallEvent.
{
"timestamp": 1758096310000,
"id": "6f1c2a3b-9d4e-4f50-8a6b-7c8d9e0f1a2b",
"state": "confirmed",
"startedAt": 1758096300000,
"confidence": 0.91,
"position": { "x": 60, "y": 210, "z": 20 },
"regionId": "0f4a2c9e-6b1d-4e0a-9c3f-2d7e8b5a1c44"
}
All events of one fall share id (assigned by the Device) and
startedAt. endedAt is present only in the final states canceled and
finished, reason only with finished.
States:
stateDiagram-v2
direction LR
[*] --> suspected
suspected --> confirmed
suspected --> canceled
confirmed --> finished
canceled --> [*]
finished --> [*]
Rules:
suspected is sent as soon as the Device suspects a fall. confirmed
follows when the Device is sure, canceled when it is not. finished
ends a confirmed fall.suspected or confirmed, the Device repeats the
event every 10 seconds with the current timestamp, confidence and
position, so that the Server can tell an ongoing fall from a lost
connection.fall.enabled is false. A fall whose position
lies in a region with fallEnabled false is not reported.suspected, confirmed, canceled, finished)
and publishes them after reconnecting, in order, with their original
values. The 10-second repetitions are not buffered; they resume after
reconnecting for a fall that is still ongoing. The buffer MUST hold at
least 50 state changes. When it is full, the Device drops the oldest
buffered events, but never an event of a fall that has not yet reached
canceled or finished. Fall events carry no message expiry.