PureLife Device Provisioning Protocol

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 Device is provisioned: how it receives its network configuration and the address of its Server, how the Provisioner verifies that the Device can reach the Server, and how the Device is paired with a Customer.

The protocol builds on the Unified Provisioning framework of Espressif (protocomm and network_provisioning). Everything that framework already defines (session establishment, security schemes, Wi-Fi configuration, Bluetooth LE transport) is used unchanged and is only referenced here. This document specifies what is added on top of it.

Contents

  1. Normative references
  2. Overview
  3. Transport
  4. Security
  5. Protocol version and capabilities
  6. Device states
  7. LED
  8. Provisioning flow
  9. Endpoints
  10. Server check
  11. Pairing
  12. Device label and QR code
  13. Message size limits
  14. Related specifications
  15. Protocol Buffers files

1. Normative references

Reference Location
Espressif Unified Provisioning https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/provisioning/provisioning.html
Espressif Protocol Communication (protocomm) https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/provisioning/protocomm.html
Espressif network_provisioning component https://components.espressif.com/components/espressif/network_provisioning
Espressif provisioning tool esp_prov https://github.com/espressif/esp-idf/tree/master/tools/esp_prov
Espressif provisioning library for Android https://github.com/espressif/esp-idf-provisioning-android
Espressif provisioning library for iOS https://github.com/espressif/esp-idf-provisioning-ios
Protocol Buffers https://protobuf.dev/

The Espressif components are licensed under Apache-2.0. Their protobuf definitions are not reproduced in this specification. Implementers take them from the component sources.

A Device does not have to be built with ESP-IDF. Any implementation that behaves as specified by the references above and by this document is compliant.

2. Overview

Provisioning happens over Bluetooth LE between the Provisioner and the Device. The Provisioner also talks to the Server over HTTPS to finish the pairing. The Device talks to the Server during the Server check and the pairing step.

flowchart LR
    P["Provisioner<br/>(app or tool)"]
    D["Device"]
    S["Server<br/>(PureLife Cloud)"]
    P <-->|"Bluetooth LE (protocomm)<br/>this specification"| D
    P -->|"HTTPS<br/>PureLife Cloud API (pairing)"| S
    D -->|"HTTP(S)<br/>Device Messaging Protocol"| S

The Espressif framework contributes these endpoints:

Endpoint Provided by Purpose
proto-ver network_provisioning Version and capability information, readable without a secure session.
prov-session protocomm Establishes the secure session.
prov-scan network_provisioning Wi-Fi scan.
prov-config network_provisioning Wi-Fi credentials, apply, connection status.
prov-ctrl network_provisioning Reset and re-provision of the Wi-Fi state machine.

This specification adds the following endpoints. All of them are custom endpoints in the sense of the Espressif framework and are only reachable inside the secure session.

Endpoint Purpose
pld-info Read Device identity and versions.
pld-config Set and read the pending PLD configuration (Server URL, NTP, DNS).
pld-check Run the Server check and read its result.
pld-ctrl Commit the configuration and finish provisioning.
pld-pair Obtain a pairing token from the Server.

3. Transport

4. Security

The security scheme is the protocomm security scheme announced in proto-ver (prov.sec_ver).

Scheme Mechanism Requirement
1 Curve25519 key exchange, AES-256-CTR, authenticated with a Proof of Possession (PoP) Production Devices MUST support Security 1 with PoP.
2 SRP6a (3072-bit group, SHA-512), AES-256-GCM, authenticated with username and password Devices MAY support Security 2. If they do, the credentials are printed on the label instead of the PoP.
0 No encryption, no authentication Devices MAY support Security 0.

Rules:

4.1 Proof of Possession

5. Protocol version and capabilities

The proto-ver endpoint returns a JSON document. The framework fills the prov object. The Device MUST add an object with the key pld:

{
  "prov": { "ver": "...", "sec_ver": 1, "sec_patch_ver": 0, "cap": ["wifi_scan"] },
  "pld":  { "ver": "1.0", "cap": ["ipv6"] }
}

The values inside prov are set by the Espressif component and are not specified here.

Key Meaning
pld.ver Version of the Device Provisioning Protocol implemented by the Device, MAJOR.MINOR. This document is version 1.0.
pld.cap Optional capabilities from the closed list below.

Defined capabilities:

Capability Meaning
ipv6 The Device accepts IPv6 literals as DNS server addresses.

The key pld and the contents of the pld object are fixed by this specification. A Device MUST NOT add other keys to the pld object or other capabilities than the ones listed. The prov object is defined by the Espressif component; the Provisioner does not depend on any other top-level key.

The Provisioner MUST refuse to continue if pld is missing or if pld.ver has a MAJOR version it does not support.

6. Device states

stateDiagram-v2
    direction LR
    [*] --> Provisioning : power on<br/>(delivery state)
    Provisioning --> Operating : Finish, or<br/>timer expiry after Commit
    Provisioning --> Off : timer expiry<br/>without Commit
    Off --> Provisioning : power cycle
    Operating --> Provisioning : Factory Reset<br/>(button 20 s)
State Behaviour
Provisioning The Device advertises over Bluetooth LE and accepts provisioning. The Device does not perform its normal function and does not communicate with the Server except for the Server check and the pairing step.
Operating Normal operation as defined by the Device Messaging Protocol. Bluetooth LE advertising is stopped.
Off The Device has switched itself off. If the hardware cannot power itself off, it MUST enter its lowest power state and stop all radio activity. A power cycle is required to leave this state.

Rules:

7. LED

The status LED shows the following states.

Situation LED
Provisioning, no Provisioner connected blue, blinking 1 Hz
Provisioning, secure session established blue, steady
Wi-Fi connection failed (after Apply, or in Operating) red, blinking 1 Hz
Wi-Fi connected, Server not reachable yellow, blinking 1 Hz
Connection to Server established green, steady for 5 seconds, then off
Operating, everything fine off
Server rejected Device (see Device Messaging Protocol) red, steady
Factory Reset pending (button held for 3 s, not yet 20 s) yellow, blinking 4 Hz
Factory Reset triggered (after the button was held for 20 s) red, 3 short flashes, then the Provisioning pattern
Off off

"Connection to Server established" applies whenever the Device establishes its connection to the Server, both during the Server check and in Operating after every reconnect.

8. Provisioning flow

The Provisioner performs the following steps in order. Steps marked (framework) use Espressif endpoints unchanged.

# Step Endpoint
1 Read the Device label: scan the QR code, or enter Device ID and PoP by hand. Obtain device name, PoP and security scheme.
2 Find the Device by its advertised name and connect.
3 Read version and capabilities. Refuse on unsupported pld.ver. (framework) proto-ver
4 Establish the secure session with the PoP. (framework) prov-session
5 Read Device identity. Verify that the Device ID matches the label. pld-info
6 Send the PLD configuration: Server URL and, if the network requires it, NTP and DNS servers. pld-config
7 Scan Wi-Fi networks. (framework, optional) prov-scan
8 Send Wi-Fi credentials, apply, poll the connection status. (framework) prov-config
9 Start the Server check and poll its result. pld-check
10 Commit the configuration. This is allowed even if step 8 or 9 failed. pld-ctrl
11 Start pairing, poll for the pairing token. pld-pair
12 Send the pairing token to the Server (Server API).
13 Finish. The Device enters Operating. pld-ctrl

Deviations:

9. Endpoints

All PLD endpoints follow the same pattern as the Espressif endpoints: each endpoint has one payload message with a msg type field and a oneof containing the command or response. Each response carries a Status.

Value Meaning
STATUS_UNSPECIFIED Not set. A response with this status is invalid.
STATUS_OK The command succeeded.
STATUS_INVALID_ARGUMENT A field is missing, malformed or out of range. detail names the field.
STATUS_INVALID_STATE The command is not allowed in the current state (for example Finish before Commit).
STATUS_NOT_SUPPORTED The Device does not support this command or capability.
STATUS_BUSY An operation is still running (for example a check or pairing).
STATUS_INTERNAL_ERROR Any other error inside the Device.

Every response message contains Status status = 1 and string detail = 2 (human readable, English, at most 64 bytes). detail MUST be empty when status is STATUS_OK and SHOULD name the cause otherwise. It is meant for logs and support, not for display to end users.

The .proto files are the reference for field names, numbers, enum values and the conventions used (see the header of pld_constants.proto). The tables in this document use the short forms of enum values where the prefix is obvious.

9.1 pld-info

Reads the identity of the Device. Read-only.

Message Direction Fields
GetInfoCommand Provisioner → Device
GetInfoResponse Device → Provisioner status, detail, device_id, manufacturer, model, hardware_revision, firmware_version
Field Constraints
device_id Device ID. 8 to 24 characters from A-Z, 0-9 and -. Unique per Manufacturer and stable for the lifetime of the Device. RECOMMENDED: the 12 hex digits of the primary MAC address in upper case without separators.
manufacturer Manufacturer identifier agreed with PureSec. 1 to 32 characters from a-z, 0-9 and -.
model Model name, 1 to 32 bytes UTF-8.
hardware_revision Free text, 1 to 32 bytes UTF-8.
firmware_version Free text, 1 to 32 bytes UTF-8. RECOMMENDED: semantic version.

The Provisioner MUST compare device_id with the Device ID from the label and abort if they differ.

9.2 pld-config

Sets and reads the pending PLD configuration. Values sent here are held in memory only. They are persisted by Commit (pld-ctrl).

Message Direction Fields
SetConfigCommand Provisioner → Device server_url (optional string), ntp_servers (StringList), dns_servers (StringList)
SetConfigResponse Device → Provisioner status, detail
GetConfigCommand Provisioner → Device
GetConfigResponse Device → Provisioner status, detail, config (the pending values), committed (bool)

Semantics of SetConfigCommand:

Field Constraints
server_url Absolute URL, scheme https or http, no userinfo, no query, no fragment, at most 128 bytes. A path prefix is allowed. A trailing slash is invalid (STATUS_INVALID_ARGUMENT), so that the URL can be compared byte for byte. Example: https://cloud.smart-altern.de. The Device appends the paths of the Device Messaging Protocol (/pld/v1/...) to it. With http the connection to the Server is unencrypted; the operator of the Server is responsible for the security of the network in that case.
ntp_servers 0 to 3 entries. Each entry a host name or IPv4 literal, at most 40 bytes.
dns_servers 0 to 3 entries. Each entry an IPv4 literal (at most 15 bytes), or an IPv6 literal (at most 45 bytes) if the Device announces the ipv6 capability.

Only server_url is required for a Commit. The lists are empty until the Provisioner sets them.

9.3 pld-ctrl

Message Direction Fields
CommitCommand Provisioner → Device
CommitResponse Device → Provisioner status, detail
FinishCommand Provisioner → Device
FinishResponse Device → Provisioner status, detail

Commit

Finish

9.4 pld-check

See section 10.

Message Direction Fields
StartCheckCommand Provisioner → Device
StartCheckResponse Device → Provisioner status, detail
GetCheckStatusCommand Provisioner → Device
GetCheckStatusResponse Device → Provisioner status, detail, state, stages

9.5 pld-pair

See section 11.

Message Direction Fields
StartPairingCommand Provisioner → Device
StartPairingResponse Device → Provisioner status, detail
GetPairingStatusCommand Provisioner → Device
GetPairingStatusResponse Device → Provisioner status, detail, state, error, http_status, token, expires_at

10. Server check

The Server check verifies from the Device's point of view that the Server is reachable with the pending configuration. It is asynchronous: the Provisioner starts it and polls the result.

Preconditions: the pending configuration MUST be complete and Wi-Fi MUST be connected. Otherwise StartCheckCommand answers STATUS_INVALID_STATE. A running check answers STATUS_BUSY.

The check runs the following stages in order and stops at the first failure. Later stages are reported as SKIPPED, as is a stage that does not apply.

Stage What is verified
WIFI The station interface is connected and has an IP address.
DNS The host of server_url resolves using the DNS servers in effect (configured, or from DHCP).
NTP The clock is synchronised with one of the NTP servers in effect (configured, from DHCP, or Manufacturer default).
TLS A TLS connection to the Server host is established and the certificate chain validates against the Device's store of public CAs. Reported as SKIPPED when server_url uses the scheme http.
SERVER Authentication (POST /pld/v1/auth) and bootstrap (GET /pld/v1/bootstrap) of the Device Messaging Protocol both succeed with HTTP status 200. As a side effect the Device is registered on the Server.
MQTT A connection to the broker named in the bootstrap response succeeds with the returned credentials (CONNECT, CONNACK with success), and is closed again normally. Nothing is published. This is the only stage that needs a port other than the Server's HTTP port, which is why it is tested here.

The overall check MUST finish within 60 seconds. Each stage MUST fail with CHECK_ERROR_TIMEOUT if it exceeds its share.

The Server check does not check for or install firmware updates; that happens in the start-up sequence after Finish. The test connection of stage MQTT is not an operating connection.

GetCheckStatusResponse:

Field Meaning
state CheckState: IDLE (never started), RUNNING, DONE.
stages One StageResult per stage, in order.

StageResult:

Field Meaning
stage Stage identifier.
result StageState: PENDING, RUNNING, OK, FAILED, SKIPPED.
error CheckError, see below. CHECK_ERROR_NONE unless result is STAGE_STATE_FAILED.
http_status HTTP status code, only set for CHECK_ERROR_HTTP_STATUS, 0 otherwise.
detail Human readable, at most 32 bytes, optional. For example the resolved address or the TLS error string of the platform.

CheckError:

Value Stage Meaning
CHECK_ERROR_NONE No error.
CHECK_ERROR_WIFI_NOT_CONNECTED WIFI No connection or no IP address.
CHECK_ERROR_DNS_FAILED DNS Name resolution failed.
CHECK_ERROR_NTP_UNREACHABLE NTP No NTP server in effect answered.
CHECK_ERROR_TLS_CONNECT_FAILED TLS TCP or TLS handshake failed for a reason other than certificate validation.
CHECK_ERROR_TLS_CERT_UNTRUSTED TLS Certificate chain does not validate against the CA store.
CHECK_ERROR_TLS_CERT_INVALID_TIME TLS Certificate not yet valid or expired according to the Device clock.
CHECK_ERROR_TLS_HOSTNAME_MISMATCH TLS Certificate does not match the host name.
CHECK_ERROR_HTTP_CONNECT_FAILED SERVER Request could not be sent or no response was received.
CHECK_ERROR_HTTP_STATUS SERVER Response received with a status other than 200. http_status contains it.
CHECK_ERROR_HTTP_INVALID_RESPONSE SERVER Response received but not a valid authentication or bootstrap response.
CHECK_ERROR_MQTT_CONNECT_FAILED MQTT TCP or TLS connection to the broker failed, or no CONNACK.
CHECK_ERROR_MQTT_NOT_AUTHORIZED MQTT The broker refused the credentials (CONNACK reason code other than success).
CHECK_ERROR_TIMEOUT any Stage exceeded its time budget.
CHECK_ERROR_INTERNAL any Any other error. detail SHOULD explain.

The Provisioner SHOULD present the failed stage and error to the user in plain language and SHOULD offer to commit anyway.

11. Pairing

Pairing links the Device to the Customer of the Provisioner's user. The Device proves to the Server that it is online, and the Provisioner proves to the Server that it has physical access to the Device (it holds the secure session that required the PoP).

sequenceDiagram
    participant P as Provisioner
    participant D as Device
    participant S as Server
    P->>D: StartPairingCommand
    D->>S: request pairing token (Device Messaging Protocol, HTTPS)
    S-->>D: token, expires_at
    P->>D: GetPairingStatusCommand (poll)
    D-->>P: DONE, token, expires_at
    P->>S: pair Device (PureLife Cloud API): device_id, token
    S-->>P: 200 OK
    P->>D: FinishCommand

Rules:

GetPairingStatusResponse:

Field Meaning
state PairState: IDLE, RUNNING, DONE, FAILED.
error PairError, see below. PAIR_ERROR_NONE unless state is PAIR_STATE_FAILED.
http_status Only set for PAIR_ERROR_SERVER_REJECTED.
token Only set when state is DONE.
expires_at Only set when state is DONE.

PairError:

Value Meaning
PAIR_ERROR_NONE No error.
PAIR_ERROR_SERVER_UNREACHABLE The Server could not be reached. Run the Server check for details.
PAIR_ERROR_SERVER_REJECTED The Server answered with an error status. http_status contains it: 401 after one failed re-authentication, 403 when the Device is blocked, 429 or 503 when the Server asks to retry later, 5xx otherwise.
PAIR_ERROR_TIMEOUT No answer within 30 seconds.
PAIR_ERROR_INTERNAL Any other error.

12. Device label and QR code

Every Device carries a label with, in human readable form:

  1. the Device ID,
  2. for Security 1 the PoP, for Security 2 username and password. Devices with Security 0 have no secret on the label.

The label SHOULD additionally carry a QR code as specified below. The Provisioner MUST support both ways: scanning the QR code and entering the Device ID and the secret by hand. From the Device ID the Provisioner derives the advertised name (PLD_<Device ID>).

The QR code contains a JSON document in the format used by the Espressif provisioning libraries, so that those libraries can parse it directly:

{"ver":"v1","name":"PLD_9923003A4BE3","pop":"K7fT2pQ9xL4m","transport":"ble","security":1}
Key Value
ver Always v1 (format version of the Espressif QR code).
name The advertised Bluetooth LE name, PLD_<Device ID>.
pop The PoP. Security 1 only.
transport Always ble.
security 0, 1 or 2.

For Security 2 the keys username and password are used instead of pop, as defined by the Espressif provisioning libraries. For Security 0 the key pop is omitted.

The Device ID is not a separate key. It is name without the PLD_ prefix. After the session is established the Provisioner compares it with pld-info.

13. Message size limits

The protocomm BLE transport limits one transfer to 512 bytes, including the overhead of the security scheme (16 bytes for the AES-GCM tag of Security 2). Every PLD request and every PLD response MUST therefore be at most 450 bytes after protobuf serialisation and before encryption.

The field limits in this document are chosen so that every message stays within that limit even when all fields have their maximum length. The largest messages are SetConfigCommand and GetConfigResponse with a full configuration (about 410 bytes). This relies on the rule that detail is empty on success; a 64-byte detail in a GetConfigResponse with a full configuration would exceed the limit. The Provisioner MAY split SetConfigCommand over several commands.

Some steps of this protocol are specified elsewhere:

Step Specified in
Server check, stage SERVER: authentication and bootstrap of the Device Device Messaging Protocol, sections 6 and 8
Pairing: the request that returns the pairing token Device Messaging Protocol, section 10
Pairing: redeeming the token (Provisioner only) PureLife Cloud API, operation Pair a sensor

15. Protocol Buffers files

The messages of the PLD endpoints are defined in proto/:

File Endpoint
pld_constants.proto shared enums
pld_info.proto pld-info
pld_config.proto pld-config
pld_ctrl.proto pld-ctrl
pld_check.proto pld-check
pld_pair.proto pld-pair

The framework's own definitions (session.proto, sec0.proto, sec1.proto, sec2.proto, constants.proto, wifi_config.proto, wifi_scan.proto, wifi_ctrl.proto) are taken from the protocomm and network_provisioning sources.