Local control
Local control puts an HTTP server on the node so a client on the same network can read and write parameters without going through the cloud. Two benefits: it works when the internet is down, and it is much faster than a cloud round trip — which is very visible when someone taps a light switch in an app.
Enabling it
esp_rmaker_local_ctrl_service_enable();
Call it after esp_rmaker_node_init() and before esp_rmaker_start(). Disable it with esp_rmaker_local_ctrl_service_disable().
The node advertises itself over mDNS as _esp_rmaker_ctrl._tcp, with the node ID as both hostname and instance name, so clients discover it without knowing its IP. The advertisement carries two TXT records: node_id, and cap — a comma-separated list of the active endpoint sets (local_ctrl and/or ch_resp), so a client can tell what a node offers from the browse result alone. Local control and challenge-response are independently enable-able endpoint sets on this one service.
The service is also exposed in the node's data model as a service named Local Control with read-only Type and POP parameters (plus Username with security 2), so an authorised cloud client can learn the proof-of-possession it needs to connect locally.
Security versions
Chosen at build time. This is the most important decision on this page.
| Version | Kconfig | What it does |
|---|---|---|
| 1 | CONFIG_ESP_RMAKER_LOCAL_CTRL_SEC_VERSION_1 | ESP-IDF protocomm Security 1 — an authenticated key exchange using a PoP. The PoP is randomly generated (or supplied by you) and stored in NVS, then exposed as a read-only parameter on the service. Set CONFIG_ESP_RMAKER_LOCAL_CTRL_SEC1_POP=n to run without a PoP — the version endpoint then advertises the no_pop capability. |
| 2 | CONFIG_ESP_RMAKER_LOCAL_CTRL_SEC_VERSION_2 | Default and recommended. SRP6a + AES-GCM. The PoP is reused as the SRP password; salt and verifier are derived at runtime and cached. The SRP6a username is fixed to wifiprov, for parity with ESP-IDF unified provisioning. |
Security 0 (no authentication) is not supported — the session is always authenticated.
Proof of possession
By default the SDK generates a random PoP of CONFIG_ESP_RMAKER_LOCAL_CTRL_POP_LENGTH characters (default 8, range 8–32, lowercase hex) and persists it in NVS. It is counted in characters rather than random bytes because it is the value printed on a device or shown in a QR payload; at 4 bits of entropy per character, the default 8 carries 32 bits.
To reuse the PoP that was printed on the device label and used for provisioning — the usual product choice, since it means one secret per device instead of two:
esp_rmaker_local_ctrl_set_pop("abcd1234"); /* before enabling the service */
esp_rmaker_local_ctrl_service_enable();
Pass NULL to clear a previously set custom PoP. This must be called before enabling the service.
Configuration
| Option | Default | Notes |
|---|---|---|
CONFIG_ESP_RMAKER_LOCAL_CTRL_SEC_VERSION_* | version 2 | See above. |
CONFIG_ESP_RMAKER_LOCAL_CTRL_SEC1_POP | y | SEC1 only: require a PoP. Disable to run SEC1 without one. |
CONFIG_ESP_RMAKER_LOCAL_CTRL_POP_LENGTH | 8 | Characters; range 8–32. Only when a PoP is in use. |
CONFIG_ESP_RMAKER_LOCAL_CTRL_HTTP_PORT | 8080 | The single HTTP port for all local endpoints, challenge-response included. |
CONFIG_ESP_RMAKER_LOCAL_CTRL_STACK_SIZE | 6344 | HTTP server task stack; range 4096–8192. Raise it if you see stack overflows during local sessions. |
What a client can do
Local control adds three data endpoints, all secured by the protocomm session:
| Endpoint | Contents |
|---|---|
get_config | The full node configuration JSON — same format as what the node publishes to the cloud. Protobuf request/response, fragmented so it fits small-MTU transports. |
get_params | Current parameter values. Same protobuf framing as get_config. |
set_params | Raw JSON write — the same body as a cloud parameter update. |
Writes arrive at your ordinary write callback with ctx->src == ESP_RMAKER_REQ_SRC_LOCAL. There is nothing extra to implement — but if your device needs to treat local and cloud commands differently (an access-control decision, a different UI indication), that field is where you branch.
For the endpoint list, session flow, fragmentation, and payload shapes, see Firmware specifications → Local Control Endpoint Protocol.
Testing it
The SDK ships an interactive client:
python3 tools/local_ctrl_cli/local_ctrl_cli.py --pop <pop> <node_id>
It discovers the node's _esp_rmaker_ctrl._tcp service over mDNS, reads the available capabilities from the cap TXT record, connects, and gives you a lc> prompt — offering the local-control and challenge-response commands the node actually advertises. See Tools reference.
On-network challenge-response
A second endpoint set on the same service. It answers a different question: how does a node that is already on the network get associated with a user account, when there was no provisioning session to do it in?
esp_rmaker_chal_resp_service_enable();
This registers the same ch_resp endpoint used by provisioning on the local endpoints service instance — starting the instance if local control has not already done so — and adds ch_resp to the advertised cap TXT record. Disable it with esp_rmaker_chal_resp_service_disable(), and check it with esp_rmaker_chal_resp_service_is_enabled(). The endpoint is independent of local control: any of the four enable/disable combinations works, and both endpoint sets share the same protocomm session security (SEC1 or SEC2) and the same HTTP port.
Two Kconfig options control what the examples enable at runtime:
| Option | Default | What the examples do with it |
|---|---|---|
CONFIG_ESP_RMAKER_LOCAL_CTRL_CHAL_RESP_ENABLE | y on POSIX, n on ESP-IDF | Enable ch_resp alongside local control, on RMAKER_EVENT_LOCAL_CTRL_STARTED. |
CONFIG_ESP_RMAKER_ON_NETWORK_CHAL_RESP_ENABLE | n | Start the service with only the ch_resp endpoint — no local control — for nodes that are already on the network and never run provisioning. |
Neither option changes what the SDK can do — both endpoint sets are always served by the same instance; the options only decide what the application enables.
Once a user is successfully mapped, a client can send a DisableChalResp command over the wire to shut the endpoint down — a worthwhile hardening step. That disable persists in NVS across reboots: later enable attempts are refused, and only a factory reset clears it.
See User-node association for how the association flow fits together.
Related
- Firmware specifications → Local Control Endpoint Protocol — endpoints, discovery, session flow, framing
- Firmware specifications → Optional Services → Local Control — the service the node reports to the cloud
- Firmware specifications → Provisioning — the challenge-response protocol
- Product overview → Local control
- Tools reference