Repository navigation
Zigbee
ArdPortal's Zigbee extension connects the same AppConfig controls to a Zigbee network through the chip's native radio. It includes joining/rejoining, standard and composite endpoints, bidirectional values, persistent settings, gateway definition export, diagnostics and firmware reception. The application still reads sensors and operates hardware.
This guide describes the current development branch. It does not imply these features are present in older release ZIPs. Native compilation and host tests use Arduino-ESP32 3.3.12; device tests on ESP32-C5 with Zigbee2MQTT include control exchanges and complete OTA both with and without Wi-Fi. See validation and troubleshooting for the exact scope.
| Build | Configuration | Management and integration |
|---|---|---|
| Wi-Fi portal + Zigbee |
ARDPORTAL_ENABLE_ZIGBEE=1; Wi-Fi/WWW remain enabled |
Web configuration, Console, gateway downloads and shared control values |
| Zigbee without device MQTT | Above plus ARDPORTAL_ENABLE_MQTT=0 and ARDPORTAL_ENABLE_HA=0
|
Zigbee2MQTT can still publish through its own broker; the ESP does not connect to MQTT |
| Zigbee + MQTT/HA | Enable Zigbee and keep MQTT/HA | Both transports use AppConfig; configure their gateways and namespaces separately |
| Zigbee without WWW | ARDPORTAL_ENABLE_WWW=0 |
C++ configuration/export API; no HTTP, web Console or HTTP OTA; Wi-Fi/AP can remain available |
| Zigbee only |
ARDPORTAL_ENABLE_ZIGBEE=1, ARDPORTAL_ENABLE_WIFI=0
|
Wi-Fi/AP/WWW/MQTT off; AppConfig, ArdFS, Zigbee OTA and C++ exports remain available |
Zigbee is opt-in. Converter and quirk export default on when Zigbee and dynamic pages are enabled. Their download buttons additionally require WWW. All flags and dependencies.
This optional module uses the ESP32-C5/C6/H2 native radio and the Zigbee SDK bundled with Arduino-ESP32. It is disabled by default. The implementation provides network management, diagnostics and automatic standard-cluster mapping for supported AppConfig controls. Dynamic form definitions are not transmitted to gateway applications.
After losing all active links, a device with saved network identity schedules a
rejoin after 30 seconds. If that operation exhausts its retry or timeout limits,
another rejoin is scheduled after 30 seconds. Recovery preserves network data
and runs cooperatively in loop(); HTTP remains available. Unpaired devices
also retry pairing after each unsuccessful series, with the same 30-second pause,
until joining succeeds. The coordinator must allow joining. Stop pairing cancels
both active attempts and scheduled retries, including during the pause. A factory
reset does not schedule automatic retries in the current session.
#define ARDPORTAL_ENABLE_ZIGBEE 1
#define ARDPORTAL_ZIGBEE_ROUTER 0 // 0 = End Device, 1 = Router
#include <ArdPortal.h>Supported native targets are ESP32-C5, ESP32-C6 and ESP32-H2. Select the actual chip’s
board in Arduino IDE. Fallback model identity is ArdPortal-C5, ArdPortal-C6 or ArdPortal-H2;
manufacturer stays ArdPortal. Use the matching chip firmware for Zigbee OTA.
The C5/C6 partition tables in the examples are for 4 MiB flash or larger, with the
same flash layout. For CLI commands below, replace esp32c6 with esp32c5 for C5.
ESP32-C3 remains unsupported for native Zigbee; ESP32-H2 automatically disables Wi-Fi/AP, WWW and MQTT.
Select Zigbee ED in Arduino IDE for End Device, or Zigbee ZCZR for Router.
A role/mode mismatch is a compile error. The same role must be used in all
translation units. A single ArdPortal instance owns the native Zigbee stack;
do not also start Arduino's Zigbee singleton or define another
esp_zb_app_signal_handler in the sketch.
Use the ZigbeePortal example on a board with at least 4 MiB flash. Keep its
partitions.csv with the sketch: it supplies two 1792 KiB OTA slots, 320 KiB
ArdFS, dedicated zb_storage and zb_fct, and a coredump partition. The default
Arduino Zigbee table has 1280 KiB app slots, too small for the full ArdPortal build.
In Arduino IDE select Minimal SPIFFS (1.9MB APP with OTA) to avoid its smaller
size check; the sketch-local CSV overrides the actual partition layout. For CLI:
arduino-cli compile --fqbn esp32:esp32:esp32c6:ZigbeeMode=ed,PartitionScheme=zigbee \
--build-property upload.maximum_size=1835008 examples/ZigbeePortalFor Router use ZigbeeMode=zczr,PartitionScheme=zigbee_zczr and set
ARDPORTAL_ZIGBEE_ROUTER=1 before the include (or via the compiler build flags).
Changing partition layout requires a serial firmware upload, not an OTA update.
Do not reuse flash contents from another layout without deliberately migrating
or clearing them.
ESP32-C5/C6 share radio resources between Wi-Fi and Zigbee. Router operation needs continuous reception. Espressif marks Wi-Fi STA + Zigbee Router as unstable and concurrent Wi-Fi SoftAP + Router as unsupported. Configure station Wi-Fi before relying on Router operation; a fallback AP is not a supported simultaneous Router mode. Test the actual workload and coordinator. This End Device implementation is always awake, with RX-on-when-idle explicitly enabled so incoming commands do not wait for the next end-device poll. It does not implement battery sleep while serving a Wi-Fi portal. The 3-second keep-alive remains a parent-link maintenance setting; it is not an intentional application command delay. See Espressif coexistence documentation for C6 and C5.
Select Zigbee ED board mode and keep the partition table from ZigbeeClimate beside the sketch. Enable coordinator joining before the first boot. This example needs no sensor, actuator callback or device MQTT connection to accept and store setting values:
#define ARDPORTAL_ENABLE_ZIGBEE 1
#define ARDPORTAL_ZIGBEE_ROUTER 0
#define ARDPORTAL_ENABLE_MQTT 0
#define ARDPORTAL_ENABLE_HA 0
#include <ArdPortal.h>
ArdPortal portal;
void setup() {
Serial.begin(115200);
portal.setFirmwareVersion("1.0");
portal.onAppConfigPageRegistrationFinished([](bool ok) {
Serial.println(ok ? "Controls registered" : "Control registration failed");
});
portal.onZigbeeOperationFinished([](ArdPortal::ZigbeeOperation,
bool ok, const String& error) {
if (!ok) Serial.println(error);
});
if (!portal.startAppConfigPageRegistration(F(R"JSON({
"id":"settings","name":"Settings","fields":[
{"id":"relay","type":"switch","name":"Relay","default":false},
{"id":"target","type":"slider","name":"Target","min":0,"max":100,"step":1,"default":50}
]
})JSON"))) Serial.println("Registration not accepted");
if (!portal.begin()) Serial.println("Portal startup failed");
}
void loop() {
portal.loop();
}A successful registration callback means the page is registered, not that the radio has joined. Register all initial pages before endpoint startup; chain the callback when registering multiple pages. See two-page registration and the ZigbeeClimate/ZigbeePortal examples.
After configuration is ready, portal.setAppConfigValue("target", 65) changes the shared setting and schedules ArdFS persistence according to persist. portal.setAppConfigStateValue("target", 65) changes it only in RAM and reports the current value. Zigbee writes follow the same validation and persist policy; onAppConfigValueChanged reports ChangeSource::Zigbee. A switch setting needs no command callback to change its value. Actions such as cover movement or lock/unlock require the application command handler and actual state reporting. See each control's C++ examples.
Open Zigbee in the portal navigation, or /zigbee directly. It shows the
compiled role, network state, saved pairing, channel, PAN ID, attempts and errors.
- Pairing starts automatically and retries until joined. Enable joining on the coordinator; no Start pairing control is required. Stop cancels network steering and scheduled retries. It does not erase an existing identity or undo a join which has already completed.
- Rejoin saved network preserves Zigbee identity and credentials.
- Reset pairing and pair again clears only Zigbee network data and then starts joining. It preserves Wi-Fi, PortalConfig and AppConfig.
- Factory reset from the main portal also clears Zigbee before restarting; it does not begin another pairing attempt before restart. Ordinary restart, OTA restart and storage formatting do not clear Zigbee pairing.
At boot, a new device automatically attempts pairing. A previously paired device
attempts rejoin. Defaults per series: 60-second timeout, 15-second attempt timeout,
3-second retry spacing, up to 3 attempts. After a failed series, another
starts after 30 seconds, with no overall limit. Each series delivers its result
through the operation callback. Initialization and the stack scheduler run in a
separate task; callbacks are delivered from portal.loop(). Keep calling it.
| Method | Meaning |
|---|---|
startZigbeePairing() |
Accept a new-network join; returns false if unavailable, busy or already paired. |
stopZigbeePairing() |
Accept pairing cancellation; stops retries without clearing saved data. |
rejoinZigbee() |
Accept rejoin of a known network; rejects a device without saved pairing. |
resetZigbeePairing() |
Accept Zigbee-only reset followed by new pairing. |
setZigbeeOptions(options) |
Validate and set limits before startup or the next request; returns false for invalid limits. Timings: 1–600000 ms; attempts: 1–20. |
zigbeeState() |
Read cached Starting, Idle, Pairing, Rejoining, Resetting, Connected or Failed state. |
onZigbeeStateChanged(callback) |
void(ZigbeeState) for state transitions from the Arduino loop. |
portal.zigbee().setAutoConnect(bool) |
Set automatic connection policy; use stopZigbeePairing() as well to cancel an operation already in progress. |
portal.zigbee() status accessors |
Read channel, PAN, saved network, busy/error and retry information; see status API. |
onZigbeeOperationFinished(callback) |
void(ZigbeeOperation, bool success, const String& error) once per accepted operation, including automatic boot operations. |
portal.onZigbeeOperationFinished([](ArdPortal::ZigbeeOperation operation,
bool success, const String& error) {
// Operations: Pair, Rejoin, ResetPairing, FactoryReset.
// Copy error if needed after this callback. Do not block here.
});
ArdPortal::ZigbeeOptions options;
options.timeoutMs = 60000;
options.attemptTimeoutMs = 15000;
options.retryMs = 3000;
options.maxAttempts = 3;
portal.setZigbeeOptions(options);
portal.begin();true from a command means accepted, not connected. Rejected requests do not
produce a completion callback. Stop reports success=false with Pairing stopped.
Full factory reset cancels a pending network operation and reports that cancellation
before completing its Zigbee reset. A reset error is diagnostic; it does not cancel
the portal's restart. A timeout does not erase network credentials; a late native
join result may still update connection state without repeating the callback.
The Zigbee connection status appears after the IP address in the shared header on every portal page. The Zigbee page retains network details and pairing controls without duplicating that connection indicator.
The Zigbee tab in Console opens /api/zigbee/console, a dedicated WebSocket with its own
session, cursor and 16-record history. It does not use /api/console (MQTT/Messages)
or /api/events (AppConfig). It works with MQTT and HA disabled. Enable ARDPORTAL_ENABLE_CONSOLE to show the console; enable ARDPORTAL_ENABLE_CONSOLE_MESSAGES separately for Messages. The Zigbee network page only polls network/operation status.
The console displays lifecycle diagnostics and decoded Zigbee payloads as JSON.
When ARDPORTAL_ENABLE_CONSOLE_MESSAGES is enabled, pairing/rejoin attempts,
successful pairing or rejoin, link loss, retry scheduling and operation failures
also appear in Messages. Frame payloads and SDK signal traffic remain in the
dedicated Zigbee tab. This forwarding does not replace application callbacks.
Timestamps follow the same rules as Messages: device uptime before the clock is
valid, then UTC record timestamps displayed in the browser's local timezone and
selected language. Existing uptime records retain their original timestamps.
Incoming application frames include a metadata record (source, endpoints, profile,
cluster, LQI and APS status), followed by decoded ZCL records associated with the
transaction sequence. Frames containing multiple attributes produce one record
per attribute instead of allocating a complete JSON tree.
RX payload={"src":0,"fromEp":1,"ep":2,"profile":260,"cluster":6,"LQI":112,"status":0}
RX ep=2 seq=134 payload={"cmd":"default_response","to":"report_attributes","status":"SUCCESS"}
RX applied ep=2 payload={"cluster":6,"attr":0,"type":16,"value":true}
TX queued ep=3 payload={"cmd":"report_attributes","cluster":13,"attr":85,"type":57,"value":20.5}
The decoder covers standard read/write/report attributes and their responses,
reporting configuration, attribute discovery and supported On/Off, level, color,
cover, lock and warning commands. Lock PINs are redacted. Unsupported profiles,
commands or scalar formats are explicitly marked decoded:false; truncated
frames are identified without reading beyond the received buffer. Raw hexadecimal
previews are not displayed.
Values use native cluster units (a thermostat setpoint of 2050 means 20.50 °C).
TX queued means the SDK accepted the report for transmission, not a coordinator
acknowledgement. A received default response with SUCCESS confirms the matching
ZCL operation. The MQTT JSON assembled by Zigbee2MQTT is not available on the ESP.
This is application/stack diagnostics, not an over-the-air sniffer. Arbitrary
SDK-generated TX frames (such as its automatic read responses) and routed MAC
traffic are not intercepted. History and queues are bounded; older records can
be discarded under load. Log records are emitted without dynamic allocation in
SDK callbacks, then transferred to the WebSocket from loop().
HTTP resources: GET /api/zigbee/status, POST /api/zigbee/pair,
POST /api/zigbee/stop, POST /api/zigbee/rejoin, and
POST /api/zigbee/reset with body confirm=RESET-ZIGBEE.
Endpoint 1 is reserved for the network-management configuration tool; native application endpoints are described below.
With Zigbee and dynamic pages enabled, initial AppConfig fields are automatically mapped to standard ZCL endpoints. No MQTT topics or HA discovery settings are needed. The implementation works with MQTT and HA disabled.
| AppConfig type | Standard mapping | Supported features |
|---|---|---|
switch |
On/Off | Read/write boolean state |
slider |
Analog Output | Numeric setting, min/max/resolution |
select |
Multistate Output | Write an option's one-based index; report selected index |
text, textType: "number"
|
Analog Input | Read-only numeric measurement |
text, textType: "temperature"
|
Temperature Measurement | Celsius in signed hundredths |
text, textType: "humidity"
|
Relative Humidity Measurement | Percent in hundredths |
Other measurement text subtypes |
Standard measurement clusters | Pressure, illuminance, flow, CO₂, PM2.5, wind speed, AC voltage/current/power; see text subtypes |
binary_sensor |
Binary Input | Read-only boolean measurement |
light |
On/Off, Level Control, Color Control | Power, brightness and RGB via standard XY or hue/saturation |
climate |
Thermostat, optional Fan Control | Current/target Celsius temperature, independent low/high targets in heat_cool, standard system/fan modes, heating/cooling state |
cover |
Window Covering | Open/close/stop, lift percentage and tilt percentage |
lock |
Door Lock | Lock/unlock commands and actual lock state |
siren |
IAS Warning Device | Start/stop warning, duration and four standard volume levels |
update |
OTA Upgrade client | Receive and validate a matching Zigbee firmware image, then schedule portal restart |
Plain text (omitted textType or textType: "text") does not create a Zigbee
endpoint. Other supported text subtypes select a standard measurement explicitly,
including when their initial reading is null. HA metadata does not select the
Zigbee subtype. Other unsupported control types retain their portal/HA behavior.
Register initial pages in setup() or chain their registration-finished callbacks.
ArdPortal waits for ongoing registrations and one second without new fields, then
stages one field per loop before starting Zigbee. Standard endpoints are frozen
before stack startup: fields added afterwards need a device restart to appear in
Zigbee. Keep initial registration order stable to retain endpoint numbers. Endpoint
1 is reserved for network management; mapped fields start at endpoint 2. The native
mapping limit is 239 application endpoints. A limit/allocation failure stops Zigbee
startup and records a diagnostic; it does not delete AppConfig fields.
For a focused slider + thermostat example, use File → Examples → ArdPortal → ZigbeeClimate (sketch). It disables MQTT/HA, registers one page asynchronously, simulates room temperature with a RAM-only slider, persists thermostat target/mode edits, and reports Celsius current temperature/heating activity without saving telemetry. Keep its partition table and use the ESP32-C5/C6/H2 role/board settings described above. No sensor or relay hardware is required; generic Analog Output slider display depends on the gateway.
The included ZigbeePortal example registers native controls from flash and shows
application callbacks. Preserve the enabled control types used by the example.
For real sensors, call setAppConfigStateValue() with current readings. For settings,
setAppConfigValue() updates the common state and schedules ArdFS persistence.
Changes from the portal, MQTT or application API update Zigbee attributes as well.
Zigbee commands invoke onAppConfigValueChanged() or onAppConfigCommand() with
ChangeSource::Zigbee, from the Arduino loop. The application still drives hardware.
Lock/unlock and cover open/close/stop invoke the matching action callback; accepted
commands do not fabricate a successful physical movement. Report actual state with
setAppConfigStateValue(). Percentage settings use existing value-change callbacks.
Zigbee writes obey persist; persist: false remains RAM-only. Flash commits do not
execute an already accepted Zigbee command a second time.
Reports are sent after joining and when values change. A changed field marks only its mapped endpoint; unrelated telemetry does not restart a complete publication pass. Work is bounded to one application command and one field's attributes per loop, with network heap/DMA reserves and a retry delay on transport/memory pressure. Startup attribute buffers and labels are freed after the SDK has copied them. The dedicated Zigbee console includes endpoint registration, accepted/rejected commands, queued attribute reports and OTA lifecycle diagnostics.
Standard protocol support does not guarantee an identical control or automatic
exposure by every gateway. In particular, generic Analog/Binary/Multistate clusters
may need gateway support or a device converter. Option names are not currently
published as a Multistate StateText array. Custom replacement controls must retain
the property/action names used by the presets to receive corresponding commands.
visibleWhen and page dependency rules do not affect Zigbee: all supported
controls remain registered and visible in the gateway, including fields hidden
in ArdPortal. Zigbee commands for these fields remain accepted, subject to value
validation and read-only restrictions. This does not create a dynamic Zigbee form.
“Composite” means one ArdPortal field is split into several independent standard
endpoints. A native light/climate/cover can also use several clusters, but is not
split by this generic decomposition. Endpoint counts below are for the built-in
presets; replacing controls can change them. No supplied converter is needed for
the library’s transport, but gateway UI support for generic clusters is not guaranteed.
| Control | Current Zigbee mapping | Scope / exclusions |
|---|---|---|
| slider | Native / generic Analog Output | Value writes/reports, min/max/step; float32 and gateway support limits. |
| text | Native measurements; plain text unmapped | number, temperature, humidity, pressure, illuminance, flow, CO₂, PM2.5, wind speed, AC voltage/current/power. Plain text has no endpoint. |
| select | Native / generic Multistate Output | Option index writes/reports; names/translations not sent as StateText. |
| edit | Not mapped | No editable-string endpoint. |
| switch | Native | On/off writes and reports. |
| climate | Native, multiple clusters | Target/current °C, standard modes and fan modes; heating/cooling bits. No presets, dual targets or fan/stage activity bits. |
| binary_sensor | Native / generic Binary Input | Read-only boolean; no automatic IAS/occupancy semantic classification. |
| date | Not mapped | No calendar-date setting endpoint. |
| time | Not mapped | No time-of-day setting endpoint. |
| datetime | Not mapped | No arbitrary timestamp setting endpoint. |
| button | Generic, one momentary endpoint | ON invokes PRESS; reports idle OFF. May appear as a switch. |
| scene | Generic, one momentary endpoint | ON invokes the scene callback; reports idle OFF. No Scenes scene table. |
| notify | Not mapped | No text notification transport. |
| infrared | Not mapped | No arbitrary IR/JSON action transport. |
| event | Not mapped | No application gesture/event stream. |
| device_trigger | Not mapped | No Zigbee mapping for MQTT automation-trigger metadata. |
| tag | Not mapped | No tag scan/identifier transport. |
| device_tracker | Generic, one status endpoint | Read-only 1=not_home, 2=home; no location/zone/GPS data. |
| light | Native, multiple clusters | On/off, brightness, RGB via XY or hue/saturation; quantization, no CCT/effects. |
| fan | Composite — 4 endpoints | Power, speed percentage, oscillation, direction. Separate generic controls; no native Fan Control endpoint. |
| cover | Native, multiple clusters | OPEN/CLOSE/STOP, position/tilt; no separate movement-state report or absolute encoder commands. |
| valve | Composite — 4 endpoints | Open/close action endpoints, position, discrete state; no STOP. |
| lock | Native | LOCK/UNLOCK and state; no OPEN latch action or PIN management. |
| alarm_control_panel | Composite — 6 endpoints | Five arming/disarm/trigger action endpoints plus discrete state; no IAS ACE/PIN/zone handling. |
| humidifier | Composite — 4 endpoints | Power, target humidity, mode, current humidity; no unified humidifier profile. |
| siren | Native, partial | Start/stop warning, duration, four volume levels; alarm tone only, no application-state reporting. |
| vacuum | Composite — 8 endpoints | Six action endpoints, fan selection and activity; custom JSON command is not mapped. |
| lawn_mower | Composite — 4 endpoints | START/PAUSE/DOCK and activity; no navigation or battery telemetry. |
| water_heater | Composite — 3 endpoints | Target temperature, operation mode, current temperature; no unified heater profile. |
| update | Native OTA client | One owner; matching image reception/validation and deferred restart. Display firmware version is Basic SWBuildID; OTA ordering uses a separate integer image version. |
ArdPortal now decomposes fan, valve, humidifier, water_heater,
alarm_control_panel, vacuum, lawn_mower, button and scene into separate
standard endpoints. device_tracker reports its two preset presence states through
Multistate Input. The native mappings use standard clusters; optional generated gateway files add interpretation, labels and option names.
| Control | Current separate endpoints / scope |
|---|---|
| fan | On/Off + Analog Output + On/Off + Multistate Output. Power, exact requested percentage (0–100), oscillation and forward/reverse direction are separate endpoints. Percentage is float32; direction is option 1=forward, 2=reverse. |
| valve | Two momentary On/Off actions + Analog Output + Multistate Input. Open/close actions are activated with ON, position is writable 0–100, and discrete state is reported: 1=closed, 2=opening, 3=open, 4=closing. |
| humidifier | On/Off + Analog Output + Multistate Output + Relative Humidity Measurement. Power, target humidity (0–100%), mode (1=normal, 2=eco in the preset), and measured current humidity are separate endpoints. |
| water_heater | Analog Output + Multistate Output + Temperature Measurement. Target temperature, selected operation mode and measured current temperature are separate endpoints. Preset mode indices: 1=off, 2=eco, 3=electric, 4=performance, 5=heat_pump, 6=high_demand. Temperatures are Celsius. |
| alarm_control_panel | Five momentary On/Off actions + Multistate Input. ON activates ARM_HOME, ARM_AWAY, ARM_NIGHT, DISARM or TRIGGER on its dedicated endpoint. Reported panel state: 1=disarmed, 2=armed_home, 3=armed_away, 4=armed_night, 5=pending, 6=arming, 7=triggered. |
| vacuum | Six momentary On/Off actions + Multistate Output + Multistate Input. ON activates start, stop, pause, return_to_base, locate or clean_spot. Fan speed is selected by ordinal (preset: 1=quiet, 2=normal, 3=turbo). Activity: 1=idle, 2=cleaning, 3=docked, 4=paused, 5=returning, 6=error. |
| lawn_mower | Three momentary On/Off actions + Multistate Input. ON invokes START, PAUSE or DOCK on its own endpoint. Activity: 1=docked, 2=mowing, 3=paused, 4=error. |
| button | Momentary On/Off. Sending ON invokes the preset PRESS action through onAppConfigCommand. OFF does not execute the action. After processing, the endpoint reports OFF again. |
| scene | Momentary On/Off. Sending ON invokes the preset ON scene action through onAppConfigCommand. The endpoint returns to OFF after processing. |
| device_tracker | Multistate Input. The preset presence state is read-only: 1=not_home, 2=home. The application updates it through the existing state setter. |
An action endpoint accepts ON and reports idle OFF after processing. It is not a physical switch and OFF does not undo the action. Generic option/status values use one-based indices; their names are listed on the control pages. Unsupported custom text/JSON actions (for example vacuum’s custom command) are left unmapped.
Each endpoint carries its function in standard Basic ProductLabel, and generic Analog/Multistate endpoints also have Description. Gateways may ignore those labels or fail to expose those clusters. Use the generated converter/quirk on supported gateways. Generic ZHA, Zigbee2MQTT or a commercial bridge is not guaranteed to expose every function without that definition. They remain parts of one physical Zigbee device.
Registration and publication advance one endpoint per loop, including composite fields; retry/OOM behavior and selective invalidation are retained. Multiple endpoints share one AppConfig value and callback route. The shared native endpoint limit is 239, so composite fields use more slots than scalar fields.
Standard Zigbee descriptors expose endpoints, device types and clusters; they do
not transmit the ArdPortal navigation tree, dynamic forms or a UI sorting rule.
Current endpoint numbering follows page/field registration order, then the
normalized preset’s control order. This is not the portal’s page order sorting.
Read-only current-measurement and status endpoints follow the field’s controls.
Keep registration and control order stable to retain endpoint numbers.
ArdPortal publishes endpoint function labels; it does not encode page membership
or order as custom attributes. A gateway chooses grouping, naming and sorting.
Zigbee Groups group endpoints for addressing/multicast control, not navigation
pages. Recreating the portal’s exact pages/order would require explicit gateway
support or manually configuring its dashboard. Zigbee endpoint model,
Groups semantics.
- Light supports RGB through XY and hue/saturation; RGB intensity is folded into the ZCL level. Incoming color/level commands normalize RGB to full intensity and use the brightness property for dimming. Color temperature and effects are not advertised.
- Climate supports representable Celsius values and standard modes listed in the field. Unknown fan modes are not translated into invented Zigbee modes.
- Cover percentages are inverted between the portal (100% open) and ZCL (0% open). Absolute encoder-position commands are not implemented.
- Lock supports lock/unlock; the portal's separate
OPENlatch action and PIN-management functions are not exposed. - Siren maps standard warning modes to the preset
alarmtone. Its discrete volume is rounded to the preset's 0.1 step; the application must honor duration and report the resulting state.
The following is an assessment of richer future mappings. The composite endpoints described above are implemented; the richer device-specific mappings below are not. The cluster proposals are engineering inferences from standard cluster semantics and the installed ESP Zigbee SDK. Combining clusters can transport the properties without making the gateway recognize one complete device automatically. A Zigbee2MQTT converter or equivalent ZHA device handler may be needed to combine values, commands and events into the intended UI. This does not change the current support status on each control page.
References to a converter in this future-extension table concern a richer combined gateway UI or custom payload semantics. They are not a dependency of the currently implemented generic endpoints listed above.
Repeated instances of the same cluster with the same role need separate endpoints; a single endpoint cannot hold multiple independent Multistate Output instances. Action transport must preserve repeated identical commands, rather than relying only on a changed state attribute. Each field needs an explicit value/command mapping and tests for the gateway role, reporting, storage and callback behavior.
| ArdPortal control | Feasibility | Proposed clusters/model | Remaining work / limits |
|---|---|---|---|
| fan | Current generic mapping; richer option feasible; composite + converter | Fan Control + On/Off + Analog Output + Binary Output + Multistate Output | Fan Control provides native off/low/medium/high/auto modes. Analog Output can preserve the exact requested percentage, Binary Output can carry oscillation, and Multistate Output can carry direction. Those properties need explicit gateway semantics; Fan Control alone cannot preserve the full ArdPortal fan. |
| valve | Current generic mapping; richer option feasible; composite + converter | On/Off + Analog Output + Multistate Input | On/Off can cover binary open/close, Analog Output can carry 0–100 position, and Multistate Input can report open/opening/closed/closing. A converter must combine them into a valve and keep action versus position-write behavior consistent. Level Control would usually suggest a dimmer, not automatically a valve. |
| humidifier | Current generic mapping; richer option feasible; composite + converter | On/Off + Relative Humidity Measurement + Analog Output + Multistate Output | These can carry power, current humidity, target humidity and normal/eco mode. The Dehumidification Control cluster describes removal of humidity; it should not be presented as a faithful native humidifier mapping. A gateway converter must combine the separate standard values into a humidifier. |
| water_heater | Current generic mapping; richer option feasible; composite + converter | Thermostat + Temperature Measurement + Multistate Output | Thermostat can provide a heating target/heat-off control, Temperature Measurement can report the reading, and Multistate Output can carry eco/electric/performance/heat-pump/high-demand selection. The ArdPortal modes need a documented mapping; the clusters do not automatically identify a complete HA water-heater entity. |
| alarm_control_panel | Current generic mapping; native extension; gateway role matters | IAS ACE; optionally IAS Zone and IAS Warning Device | IAS ACE supports disarm and day/home, night/sleep and all/away arming, panel status and emergency/panic/fire commands. A panel-side implementation needs the appropriate server commands, status responses/notifications and authorization behavior. IAS Zone/Warning Device are only needed when actual zones/sirens are exposed; they are not prerequisites for every panel. Do not assume keypad support in a gateway also supports an ArdPortal panel server. |
| scene | Current generic mapping; native extension; scene identity required | Scenes + Groups | Give each application scene a stable scene ID and group ID, handle Recall Scene and route it to the application callback. Sending scene activations from the device instead uses a client role/binding. Groups support multicast when used. Full scene-table add/store/remove behavior is extra work; a callback-only ArdPortal scene does not already implement it. |
| button | Current momentary endpoint; richer command mapping possible | On/Off client commands or Scenes client/server commands | A device-originated press can send a standard bound command, but this differs from HA pressing a button to invoke a device action. The latter needs a documented incoming command/scene-ID mapping to PRESS and repeatable command handling. No combination automatically advertises a generic arbitrary-action button to every gateway. |
| event | Partial standard mapping; custom gestures need converter | On/Off or Scenes client commands; optionally Multistate Input + event counter | Known actions can be sent as commands to bound peers/coordinator. Arbitrary press/double-press/long-press event names need a device converter and a repeat/event-sequence mechanism. A state attribute alone loses consecutive identical events; it is not equivalent to an event stream. |
| device_trigger | Partial; host automation mapping required | On/Off or Scenes client commands | The device can emit standard commands; a gateway converter can expose them as automation triggers. HA MQTT device-automation type/subtype metadata cannot be sent unchanged as a standard Zigbee trigger definition, and all arbitrary triggers do not have native equivalents. |
| device_tracker | Current generic mapping; further partial presence only; converter required | Binary Input or Multistate Input | Home/not-home or a fixed status list can be encoded, with the gateway assigning presence semantics. Occupancy Sensing is appropriate only when the field really measures occupancy, not arbitrary home/zone membership. Named locations, GPS coordinates and zone semantics need a custom model/converter; RSSI Location is not a drop-in HA device tracker. |
| vacuum | Current generic mapping; further partial composite; custom commands remain custom | Multistate Output + Multistate Input; optional On/Off and Power Configuration | Discrete action selection, fan-speed selection and reported activity can be carried on separate cluster instances/endpoints, with optional battery telemetry. Repeated actions need command/sequence semantics. The arbitrary JSON custom-command route and vacuum-specific behavior are not covered by those standard clusters; a device converter is necessary. |
| lawn_mower | Current generic endpoints; richer device UI/battery possible | Multistate Output + Multistate Input; optional Power Configuration | START/PAUSE/DOCK requests and reported activity can use discrete values, with repeat handling and separate endpoints as needed. A converter must create the mower meaning/UI. Standard generic clusters do not add navigation, zones, schedules or a universally recognized lawn-mower device. |
| edit | Custom attribute/protocol required | Writable ZCL character-string attribute | ZCL can carry a string, but there is no portable generic editable-text control from combining the existing On/Off/Analog/Multistate clusters. A manufacturer-specific attribute/model and gateway converter could preserve this value; bounds and read/write behavior must be defined. |
| notify | Custom command/protocol required | Character-string payload in a custom ZCL command | A notification message can be transported over Zigbee, but standard measurement/control clusters do not define the ArdPortal notify service. Delivery semantics, size limits, repeat handling and gateway presentation require a custom protocol/converter. |
| infrared | Custom protocol required for the current JSON interface | Custom commands/attributes; optional tunneling in a compatible stack/gateway | Generic standard clusters cannot preserve the arbitrary IR-emitter JSON API or automatically make an IR controller. Predefined IR actions could be exposed as scenes/buttons, which is only a fixed subset. Full code transfer requires a documented device protocol, fragmentation limits and a gateway converter. |
| date | Custom field model required | ZCL Date-typed attribute | Date is a standard ZCL data type, not a universally exposed calendar-setting cluster. A custom writable attribute can encode the calendar date with explicit conversion/range limits, but combining standard clusters does not recreate the existing date picker. Gateway support is required. |
| time | Custom field model required | ZCL Time-of-Day-typed attribute | Time-of-day is a ZCL data type usable in a custom writable attribute. The Time cluster represents device/network time and is not an interchangeable container for arbitrary independent application time settings. There is no automatic generic time picker from a cluster combination. |
| datetime | Partial for device clock; custom model for arbitrary fields | Time cluster for the device clock; custom UTC-time attribute for an application timestamp | The Time cluster can exchange actual device/network clock information. Independent AppConfig timestamps need their own attributes and a converter. ZCL UTC time has a fixed epoch/range and does not preserve the original offset or fractional-second representation. It cannot reproduce every accepted ArdPortal timestamp unchanged. |
| tag | Custom attribute/command required | Character/octet-string attribute or custom scan command | A tag identifier can be transmitted, but existing standard generic clusters do not describe arbitrary NFC/RFID scans as HA tags. Repeatable scans require event semantics; a converter must interpret the identifier and emit the host tag event. |
Plain text
|
Custom read-only model only | Character-string attribute | No generic standard text-display control; this remains intentionally unmapped by default. |
The installed Fan Control model supplies discrete modes, not an exact percentage or independent oscillation/direction fields; see Fan Control. The SDK exposes generic Analog/Multistate building blocks, but its Arduino Multistate documentation explicitly warns about missing generic ZHA support. Native transport should not be described as automatic UI compatibility.
The strongest direct standard candidates are Scenes/Groups and IAS ACE. Their client/server roles and command semantics still require implementation. The SDK cluster inventory is a protocol building-block reference, not proof that a gateway supports every resulting ArdPortal device.
Only one update field can own the device's OTA client. This mapping also requires
ARDPORTAL_ENABLE_OTA=1 and
ARDPORTAL_ENABLE_CONTROL_UPDATE=1. The coordinator must
serve a standard Zigbee OTA container containing a single Upgrade Image subelement
(tag 0) with the .bin compiled for the actual ESP32-C5, C6 or H2 target. A plain HTTP .bin/.bin.gz upload is a different flow.
Set these defines before including ArdPortal.h to match the coordinator's images:
#define ARDPORTAL_ZIGBEE_OTA_MANUFACTURER 0xffff
#define ARDPORTAL_ZIGBEE_OTA_IMAGE_TYPE 1
#define ARDPORTAL_ZIGBEE_OTA_FILE_VERSION 2ULDefaults are 0xffff, 1 and 1UL. The manufacturer value is an OTA image identifier;
use the value assigned to your product when applicable. Increment the application's
file version for each new image; it is independent of the ArdPortal library version.
The OTA client queries the coordinator at address 0, endpoint 1 after joining and sets
a ten-minute query interval. The INSTALL action requests a matching image through
this client when available, including when triggered from the portal.
The receiver handles fragmented subelement headers, checks length against the OTA
slot, validates firmware through ESP-IDF before selecting the new boot partition,
and aborts on errors or a 60-second receive timeout. Successful completion schedules
the usual FirmwareUpdate restart, including onBeforeRestart() and storage flushing.
The SDK callback does not call ESP.restart() directly. HTTP uploads and new AppConfig
writes are blocked while the native OTA receiver is active. The generic update
field's version strings are application metadata; they do not override these OTA
image identifiers. Native reception updates in_progress automatically in RAM,
without writing progress markers to ArdFS.
Complete OTA reception, validation, the before-restart callback, version-2 boot and Zigbee rejoin were confirmed on ESP32-C5 with Zigbee2MQTT, both without Wi-Fi and with Wi-Fi/WWW enabled. These are hardware tests, not certification of all roles, chips or coordinators.
- Build the application for the same target, role and partition layout as the installed firmware. Preserve the Zigbee network partitions and two sufficiently large app slots.
- Increment
ARDPORTAL_ZIGBEE_OTA_FILE_VERSIONand optionally set the human-readable version withsetFirmwareVersion("1.1")beforebegin(). Neither value is the ArdPortal library version. - Package the application
.binin a standard Zigbee OTA container with matching manufacturer/image type and the new file version. Do not send a merged flash image,.bin.gzor raw.binto this client. - Make the image available through the coordinator's OTA provider. Installing the ArdPortal converter does not host an image or configure a provider. Follow your gateway's OTA documentation.
- Start the update through the gateway, or the mapped INSTALL action when an image is available. Keep stable USB/power and radio placement for the full transfer. Zigbee transfer is substantially slower than HTTP; the tested images took over two hours.
- Confirm the gateway's successful completion, new image version and device rejoin.
TX queuedor 100% transfer alone does not prove the new firmware booted. Verify application operation after restart.
While native OTA is active, application control processing is suspended. Commands issued then are not a valid control-latency test; wait for completion. The existing firmware remains selected when reception or image validation fails. Never change the partition layout through this OTA path.
Converter export is enabled by default (ARDPORTAL_ENABLE_ZIGBEE_CONVERTER=1).
Set it to 0 before including ArdPortal.h to disable it
(Defines). The Zigbee page
then shows Download Zigbee2MQTT converter after endpoint registration and
stack initialization. The GET route is /api/zigbee/converter.mjs.
The download filename includes the layout signature, so converters for different
layouts can coexist in Zigbee2MQTT. The downloaded .mjs module is generated from the actual native endpoint
registry, one endpoint per HTTP fragment. The file is not retained in RAM.
Memory pressure postpones generation; normal HTTP timeout still applies.
The export includes control IDs, endpoint numbers, labels, numeric ranges,
select options, discrete states, climate modes and fan modes.
With Zigbee and dynamic pages enabled, the Zigbee model identifier is ArdPortal- followed
by a deterministic 64-bit layout signature. The signature covers the exported
metadata and schema version, excluding live values and the device IEEE address.
All devices with identical exported layouts use the same model and converter,
including across supported chips and roles. Changed endpoint order, options,
ranges or labels produces a different model. Regenerate the converter and
refresh/re-interview an already paired device after changing its layout. The
signature is a layout identifier, not an authentication mechanism.
Install the downloaded module in Zigbee2MQTT's Settings → Dev console →
External converters, or in its external_converters directory. Current
Zigbee2MQTT installations may require enable_external_js to be enabled; see
the official external converter instructions.
This is the gateway-level Dev console, not the Dev console of an individual device. Save the downloaded layout-specific file, check gateway logs for loading errors and refresh the device interview when its model/endpoints changed. Existing devices with the same layout reuse the installed converter; firmware updates do not replace the gateway file automatically.
ArdPortal does not install or change anything on the gateway. The generator is tested with Zigbee2MQTT 2.14.2 / zigbee-herdsman-converters 26.115.1.
The converter adds named select options and Multistate status sensors,
respects climate ranges and modes, supports wind-speed measurements, and
scopes commands and standard state reports to each endpoint. Action endpoints
of the same AppConfig control are grouped into one command choice. Selecting
an option immediately sends the native command to its dedicated endpoint,
without an Execute button. The converter clears the transient selection after
sending, so the same action can be invoked again. WindFront may render short
choices as option buttons and longer choices as a dropdown; the converter does
not control this frontend layout. A standalone action remains a single-option
command (a button in HA Discovery). Composite
controls are still represented by separate standard features; this export
does not reconstruct dependent forms or promise a combined fan, vacuum, valve,
humidifier or water-heater entity. Plain text and unmapped controls are not added. The converter advertises OTA capability for the mapped update client but does not supply an image provider. Climate exports independent heating/cooling targets when heat_cool is configured; other modes use the active single target.
The download is refused while endpoint registration is incomplete or failed. Adding fields after the Zigbee registry is frozen requires a firmware restart before downloading a new converter.
Endpoint descriptors use compact report flags and retain labels only until the SDK has copied them. Converter export reconstructs labels from field definitions, so downloading after startup preserves the same metadata used for layout identity. Composite definitions are normalized in place in their temporary parsed object; registration and command handling do not retain a second deep copy. Diagnostic history reuses its existing buffers and writes traffic records without intermediate timestamp/message Strings. Queue sizes and supported controls are unchanged. The common converter asset is compacted as a whole, independently of the project's control selection.
A quirk is a Python device handler used by ZHA in Home Assistant. It adds entity definitions and adjusts cluster interpretation; it is separate from a Zigbee2MQTT JavaScript converter.
ARDPORTAL_ENABLE_ZHA_QUIRK defaults to 1 when Zigbee and dynamic pages
are available. The Download ZHA quirk button appears after registration
and stack startup. The GET route is /api/zigbee/zha-quirk.py; its filename
contains the layout signature. Set the define to 0 to omit this exporter.
The ZHA and Z2M exports use the same ArdPortal-<signature> model, including
when only one exporter is enabled. No IEEE address is embedded. One quirk
therefore matches all devices with identical exported control layouts.
Place the downloaded .py file in a custom quirks directory on Home Assistant,
configure ZHA's custom quirk loading, and restart Home Assistant. For example:
zha:
custom_quirks_path: /config/custom_zha_quirksSee the official ZHA device handlers documentation.
The exporter uses the public zhaquirks.builder API and is tested with
zha-quirks 2.3.0, zigpy 2.3.0 and zha 2.3.1. It is not installed automatically.
Older API generations are not supported by this generated module. After a
layout change, download the matching quirk and refresh the device interview
so ZHA sees the new model and endpoints.
- Sliders become number entities with the application's minimum, maximum and step.
- Multistate Output becomes a select; Multistate Input becomes a named state sensor. ZHA displays underscores as spaces; colliding display names receive a numeric suffix, while writes still use the original one-based option number.
- Binary Input becomes a binary sensor. Action endpoints become command buttons; the application's action callback still determines whether an action is accepted.
- Typed text measurements become sensors in application units, with invalid wire readings converted to unavailable values. Ordinary text has no Zigbee endpoint.
- Standard switches, lights, covers, locks, sirens and thermostats use ZHA's native
entities with the registered labels. Thermostat temperature limits are supplied
locally by the quirk; ZHA chooses its temperature step and available modes from
its native thermostat implementation. The native mapping supplies independent heating/cooling setpoints for
heat_cool; the ZHA entity chooses how to present the supported modes and setpoints. - Composite fan, valve, humidifier, alarm, vacuum, mower and water-heater controls remain separate entities. The quirk does not reconstruct pages or visibleWhen rules.
- Zigbee OTA image providers and unsupported controls are not added by this file.
The file is streamed one endpoint at a time using the shared export machinery, without retaining a full Python program in device RAM.
The converter labels standard-device child features with the registered control
name, because frontends may render these children without a parent heading.
A switch's state therefore uses its AppConfig name; climate properties use
control name / property label. Measurements and other simple features also
retain their AppConfig labels. Unique MQTT property suffixes and converter scopes retain the endpoint route.
The exported feature descriptions omit endpoint annotations, so the frontend
shows the control labels without (Endpoint: ...) suffixes.
A climate control is exported as one climate expose containing its properties.
WindFront renders these properties separately, even though HA Discovery can
create one climate entity. The converter cannot require a particular frontend
card layout. After updating an exporter, download and replace the .mjs file
in Z2M; updating firmware alone does not update the installed converter.
If the saved converter contains the new labels but the device still displays the
previous names, restart Zigbee2MQTT from Settings → Tools → Restart Zigbee2MQTT,
then reload its web interface. This was required when replacing the converter
with the same filename in Zigbee2MQTT 2.14.2. Removing or pairing the device again
is not required for a label-only converter update.
ArdPortal adds a Time client cluster to management endpoint 1, without controls, exposed functions or AppConfig fields. After connecting it probes the coordinator (address 0, endpoint 1) for UTC time and TimeStatus. Only a successful standard response from that endpoint with a master/synchronized status updates the system clock. The Zigbee epoch is converted to Unix UTC; the existing configured time zone continues to determine local time.
A successful synchronization is repeated after six hours. A missing, unsupported or invalid response times out after ten seconds and is retried after one hour; reconnecting triggers a fresh probe. Probing is asynchronous and does not block HTTP, UART or control processing. The gateway must provide the standard Time server on endpoint 1; pairing alone does not guarantee this support. Results appear in diagnostics, without adding a visible Time control. Existing Wi-Fi NTP synchronization remains enabled when configured.
The following asynchronous methods export the same converter formats as the Zigbee download buttons, without HTTP, Wi-Fi or MQTT:
-
startZigbee2MqttConverterExport(writer, finished)requiresARDPORTAL_ENABLE_ZIGBEE_CONVERTER=1. -
startZhaQuirkExport(writer, finished)requiresARDPORTAL_ENABLE_ZHA_QUIRK=1. -
zigbeeConverterExportReady()checks successful frozen endpoint registration and native stack readiness. Connection to a Zigbee network is not required. -
zigbeeConverterExportBusy()checks the API job, independently of HTTP downloads. -
cancelZigbeeConverterExport()requests cancellation; the result arrives fromloop().
Both export features require Zigbee and dynamic pages. Their API and shared writer/completion types disappear if both features are disabled. HTTP download routes, state and streaming methods additionally require WWW. The layout/model signature remains identical with and without WWW and even when both exporters are disabled, so a previously installed converter still recognizes devices with the same layout.
writer(const char* data, size_t bytes) receives at most 256 bytes per loop pass.
The pointer is borrowed and valid only during that call. Return true after
accepting all bytes. Return false without consuming any bytes to retry the
same fragment later. For destinations supporting partial writes, copy a whole
fragment into your own queue and drain that queue separately; do not partially
write a fragment and return false. A blocked consumer or memory shortage lasting
30 seconds without progress fails the export. The library keeps one metadata row
and a bounded output fragment, not the entire file.
finished(bool success, const String& error) runs once for an accepted job.
A rejected start returns false and does not call it. Cancellation, layout changes,
restart/OTA admission conflicts and stalled output fail an active job. Discard any
partial file on failure. A successful callback means every byte was accepted by
your writer; it does not confirm your destination has flushed or durably saved it.
Continue calling portal.loop(), and keep callbacks nonblocking.
// Call after zigbeeConverterExportReady(), for example from loop().
// This sink queues each whole fragment for your application's own transport.
bool started = portal.startZigbee2MqttConverterExport(
[](const char* data, size_t bytes) -> bool {
return enqueueWholeFragment(data, bytes); // Copies bytes; false consumes none.
},
[](bool success, const String& error) {
// Finalize your .mjs output on success, or discard partial output on failure.
}
);Use startZhaQuirkExport() for Python .py output. The application owns the
file/transport, filenames and final flushing. This API does not mount an additional
filesystem or publish automatically through MQTT. ARDPORTAL_ENABLE_WIFI=0 and
ARDPORTAL_ENABLE_WWW=0 do not disable this export API.
On ESP32-H2, ArdPortal automatically disables Wi-Fi/AP, WWW and MQTT. Both End Device and Router roles are supported. HTTP OTA requires WWW; Zigbee OTA can operate without WWW or Wi-Fi.
Call portal.setFirmwareVersion("1.2.3") before begin() to set the shared application display version (default 1.0, at most 16 UTF-8 bytes). Home Assistant MQTT Discovery uses device sw_version; Zigbee Basic SWBuildID exposes the same text. Gateways cache device metadata, so an existing device may need a new attribute read/interview. The layout model excludes this version, live values and IEEE identity: another device with the same layout can reuse the installed converter or quirk. The integer OTA file version remains separate. Version API.
Host tests cover mappings, SDK command validation, queue ordering, reporting retries, scheduler capacity, OTA parsing/errors and restart sequencing. Hardware testing used ESP32-C5, Arduino-ESP32 3.3.12 and Zigbee2MQTT 2.14.2. Settings, telemetry and action routing were exercised with simulated application actuators. Actions confirm callback acceptance, not operation of real hardware. Native gaps and unsupported commands remain documented per control. ZHA exports have API-level tests; the C5/Zigbee2MQTT hardware results do not certify ZHA or commercial bridges, C6/H2, Router operation or every feature-flag combination.
Full OTA without Wi-Fi passed on 15 October 2026 (approximately 2 h 16 min); full OTA with Wi-Fi/WWW passed on 16 October (approximately 2 h 27 min). Earlier runs had an actual reception timeout and an SDK MAC assertion; a later run passed without reproducing them, so their triggering cause remains unproven. Moving USB/power contacts or changing antenna placement can affect a test, but was not established as the cause in those logs. The hardware validation report distinguishes passing runs, interrupted attempts and failures.
| Symptom | Check |
|---|---|
| Compile error selecting ED/ZCZR | Match ARDPORTAL_ZIGBEE_ROUTER to the board's Zigbee mode; use a native supported chip. |
| Firmware too large | Keep the example's partition table and correct upload size check; disable unused modules with documented flags. |
| Registration/endpoint error | Inspect Messages or Serial for field/stage/reason, free heap8 and largest block. Composite controls consume multiple endpoints; do not remove fields as a memory-error workaround. |
| Pair/rejoin fails | Open coordinator joining for a new identity, check stable power and range; rejoin preserves the network, reset pairing erases it. Automatic retries continue after the pause. |
| Wrong labels or missing properties | Verify the actual model/layout, install the matching export, restart/reload the gateway as needed and refresh the interview after endpoint changes. Do not routinely erase pairing for label-only changes. |
| Command accepted but hardware unchanged | Check the value/action callback and report the resulting hardware state. Test after OTA, not while commands are suspended. |
| A hidden portal field still appears remotely | Expected: visibleWhen affects portal/HA presentation, not Zigbee endpoint existence or command acceptance. |
| OTA stops or device restarts | Preserve serial and gateway logs, last block/progress, power/reset evidence, Wi-Fi status and heap. Check image identity/layout, then distinguish reception timeout, image validation and SDK assertion. Do not treat a cached Connected label as proof of a working link. |
Use the dedicated Console Zigbee tab when WWW is enabled, or ARDPORTAL_ZIGBEE_DEBUG_SERIAL=1 otherwise. Diagnostics are bounded application/stack logs, not an RF capture or proof of broker delivery. No gateway installation or modification is performed automatically by ArdPortal.
ArdPortal · ArdFS · ArdJSON — Source · Releases · MIT license