A Zigbee2MQTT external converter tool for reading, writing, scanning, and exploring manufacturer-specific cluster attributes on any Zigbee device. Originally built to poke around Aqara/Lumi devices, now configurable for any manufacturer.
- Edit the TARGET_ZIGBEE_MODEL, TARGET_CLUSTER, manufacturerCode constants, DEVICE_DEFINITION, and import statements below to match your device and cluster
- In Z2M go to Settings-Dev Console-External Converters
- Create a new converter and name it (e.g.) "zpp.mjs"
- Paste this code into the editor
- Save
- Restart Zigbee2MQTT
- Device appears as the model name defined below
All settings live at the top of the file. Edit these before starting Z2M.
The Zigbee model string your device reports. Find this on the device's "About" page in the Z2M frontend.
const TARGET_ZIGBEE_MODEL = 'lumi.plug.aeu002';The Z2M cluster name to target for all reads and writes. For Aqara devices this is manuSpecificLumi. For other manufacturers, check the Zigbee Cluster Library or your device's cluster list (see the Cluster Discovery feature below).
const TARGET_CLUSTER = 'manuSpecificLumi';The 16-bit manufacturer code included in read/write frames. 0x115F is Aqara/Lumi. Common codes include 0x1021 (Legrand), 0x1037 (Schneider), 0x100B (Philips), 0x117C (Ikea). The Zigbee Alliance maintains the full registry.
const manufacturerCode = 0x115f;An array of hex-string attribute IDs to automatically read every time Z2M starts. Leave empty to disable.
const AUTO_SCAN_ATTRIBUTES = ['0515', '0516', '0517'];Which endpoint the auto-scan reads from.
const AUTO_SCAN_ENDPOINT = 1;How many entries the rolling write log keeps before discarding the oldest.
const WRITE_HISTORY_MAX = 20;A lookup table mapping attribute IDs (4-digit hex keys) to human-readable names. These labels appear in all read/scan/snapshot output and populate the select_attribute dropdown in the frontend. You can gut this and start fresh for a non-Aqara device, or leave it empty:
const KNOWN_ATTRIBUTES = {};At the very top of the file (before global settings) is a commented-out imports section. Uncomment or add any imports your device's existing Z2M definition needs. These are standard import statements — the same ones you'd find at the top of any converter file in zigbee-herdsman-converters/src/devices/.
import * as m from 'zigbee-herdsman-converters/lib/modernExtend';
import fz from 'zigbee-herdsman-converters/converters/fromZigbee';
import tz from 'zigbee-herdsman-converters/converters/toZigbee';
import * as reporting from 'zigbee-herdsman-converters/lib/reporting';
import * as lumi from 'zigbee-herdsman-converters/lib/lumi';Only add the imports that the definition you're pasting actually uses. Leaving unused imports is harmless but unnecessary.
This object lets you paste pieces from your device's existing Z2M converter so the tool retains full device functionality (on/off, power reporting, etc.) alongside the exploration controls. Located after KNOWN_ATTRIBUTES and before the helpers section.
Supported fields — all optional:
| Field | Type | Merge Behavior |
|---|---|---|
extend |
Array | Passed to Z2M as-is (modern extend system) |
fromZigbee |
Array | Device converters run before the tool's report listener |
toZigbee |
Array | Device converters listed before the tool's controls |
exposes |
Array | Device exposes appear above the tool's controls in the UI |
configure |
Function | Runs before the tool's auto-scan handler |
onEvent |
Function | Runs before the tool's auto-scan handler |
meta |
Object | Shallow-merged (device meta wins on conflicts) |
Example using modern extends (recommended for most current devices):
import * as m from 'zigbee-herdsman-converters/lib/modernExtend';
import * as lumi from 'zigbee-herdsman-converters/lib/lumi';
const DEVICE_DEFINITION = {
extend: [
m.onOff(),
m.electricityMeter(),
lumi.lumiModernExtend.lumiPowerOnBehavior(),
],
};Example using legacy-style fromZigbee/toZigbee arrays:
import fz from 'zigbee-herdsman-converters/converters/fromZigbee';
import tz from 'zigbee-herdsman-converters/converters/toZigbee';
import * as e from 'zigbee-herdsman-converters/lib/exposes';
import * as reporting from 'zigbee-herdsman-converters/lib/reporting';
const DEVICE_DEFINITION = {
fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering],
toZigbee: [tz.on_off],
exposes: [e.presets.switch(), e.presets.power(), e.presets.energy()],
configure: async (device, coordinatorEndpoint, logger) => {
const ep = device.getEndpoint(1);
await reporting.bind(ep, coordinatorEndpoint, ['genOnOff', 'haElectricalMeasurement']);
await reporting.onOff(ep);
},
};To find your device's existing definition, search for its Zigbee model string in zigbee-herdsman-converters/src/devices/. Copy the relevant fields into DEVICE_DEFINITION and add the corresponding imports to the imports section at the top.
All features are accessed through the Z2M device page. Fields marked (set) accept input; fields marked (read) are output-only.
Field: endpoint (set)
Selects which device endpoint subsequent commands target. Defaults to 1. Devices like the Aqara T1M have a second endpoint for the RGB ring.
| Value | Use |
|---|---|
1 |
Main/default endpoint |
2 |
Secondary (e.g. T1M ring) |
| Any positive integer | Any valid endpoint on the device |
Fields: select_attribute (set), read_attribute (set), attribute_value (read), attribute_status (read)
Two ways to read:
select_attribute— dropdown of everything inKNOWN_ATTRIBUTES. Pick one and the read fires immediately.read_attribute— free-text hex input for any attribute ID (e.g.051a,0x0515).
The result appears in attribute_value with a status line in attribute_status. If the device returns UNSUPPORTED_ATTRIBUTE, the status shows "Not supported".
Fields: read_list (set), read_list_result (read)
Read multiple specific attributes in one operation. Provide a comma-separated list of hex IDs:
0515,0516,0517,0522,0530
Up to 64 attributes per call. Results are listed one per line in read_list_result with a summary count of found/errors.
This is useful when you already know which attributes matter and don't want to scan an entire range.
Fields: write_attribute (set), write_result (read)
Three formats:
| Format | Example | Behavior |
|---|---|---|
ATTR:VALUE |
0515:0a |
Auto-detect type from byte length (1B→uint8, 2B→uint16, 4B→uint32, else→buffer) |
ATTR:TYPE:VALUE |
0524:uint16:0014 |
Explicit type override |
ATTR:TYPE:VALUE |
0527:buf:0101030bff |
Force raw buffer |
Supported types: uint8, uint16, uint32, int8, int16, int32, buf, str.
After every write, the tool automatically reads the attribute back and includes the result in write_result. If the read-back fails (write-only attribute), it notes that too. Every write is appended to the write history log.
Fields: bulk_write (set), bulk_write_result (read)
Write multiple attributes in one go using the same format as single writes, comma-separated:
0515:0a,0516:ff,0517:uint8:01
Up to 32 writes per call. Each write gets its own read-back. Results are listed individually in bulk_write_result with a summary. All writes are appended to the write history.
Fields: scan_range (set), scan_result (read)
Sweep a contiguous range of attribute IDs:
0515-0530
Reads every ID from start to end inclusive. Max range is 128 attributes. There's a 50ms pause between each read to avoid flooding the Zigbee network. Results show each attribute's value (or error/unsupported status) and a summary of found/errors/empty.
Good starting strategy for an unknown device: scan 0000-007F, then 0100-017F, and so on in chunks.
Field: snapshot (set), snapshot_result (read), snapshot_export (read)
A workflow for detecting what changes when you interact with a device through its physical controls or other Zigbee commands.
snapshot:0515-0530
Reads the range and stores all values in Z2M state. snapshot_result shows what was captured.
compare
Re-reads the same range on the same endpoint and diffs against the stored snapshot. Changed attributes are shown with their old and new values. Unchanged attributes are counted but not listed.
Typical workflow: snapshot, change something on the device (via its app, physical button, etc.), then compare to see which attributes moved.
export
Serializes the current snapshot to a JSON string in snapshot_export. Copy this value to save it externally — snapshots normally vanish on Z2M restart.
The JSON includes the cluster name, manufacturer code, endpoint, timestamp, attribute range, and all captured values.
import:{"cluster":"manuSpecificLumi","manufacturerCode":4447,...}
Paste a previously exported JSON string (prefixed with import:) to restore a snapshot. You can then run compare against it.
clear
Discards the stored snapshot.
Fields: discover_clusters (set), cluster_list (read)
Lists the input and output clusters registered on each endpoint of the device. Useful for figuring out which cluster name to set in TARGET_CLUSTER before you start reading attributes.
| Value | Behavior |
|---|---|
all |
List clusters on every endpoint |
1 |
List clusters on endpoint 1 only |
2 |
List clusters on endpoint 2 only |
Output includes cluster names (when available from Z2M's cluster definitions) and numeric IDs.
Field: raw_hex (set, toggle)
When OFF (default), values display in a friendly format with decimal, hex, and byte breakdown:
1300 (0x514, bytes: [0x05, 0x14])
When ON, values display as raw hex only:
0514
Affects all output: reads, scans, snapshots, write read-backs, and bulk operations. Useful when you're working at the byte level and the friendly formatting is noise.
Fields: last_report (read), report_log_display (read), clear_report_log (set)
Passively captures unsolicited attribute reports and read responses that the device sends on the target cluster. This is how you discover which attributes the device pushes on its own — for example, when you toggle a switch, change a color, or adjust a setting through the manufacturer's app.
Reports are timestamped and kept in a rolling log of the last 50 entries. last_report shows the most recent one. The full log is visible in report_log_display.
Send any value to clear_report_log to reset.
The listener only captures reports on the cluster defined in TARGET_CLUSTER. Reports on other clusters are not intercepted.
Fields: write_history_log (read), clear_write_history (set)
Every write (single or bulk) is timestamped and appended to a rolling log. The log holds up to WRITE_HISTORY_MAX entries (default 20). This gives you a quick reference of what you've tried without scrolling through Z2M's system log.
Send any value to clear_write_history to reset.
All fields are accessible via MQTT publish/subscribe if you prefer scripting over the Z2M frontend.
To set a value, publish to zigbee2mqtt/<device>/set with a JSON payload:
{"read_attribute": "0515"}
{"write_attribute": "0515:uint8:0a"}
{"scan_range": "0500-0535"}
{"read_list": "0515,0516,0517"}
{"bulk_write": "0515:0a,0516:ff"}
{"snapshot": "snapshot:0515-0530"}
{"snapshot": "compare"}
{"snapshot": "export"}
{"discover_clusters": "all"}
{"endpoint": 2}
{"raw_hex": true}Results appear in the device's state, published to zigbee2mqtt/<device>:
{
"attribute_value": "10 (0xA)",
"attribute_status": "✓ EP1 0x0515 (Min Brightness)",
"scan_result": "EP1 scan 0x0500-0x0535: 12 found ...",
"write_result": "✓ EP1 Wrote uint8 to 0x0515 → read-back: 10 (0xA)",
"cluster_list": "EP1:\n Input clusters (5): ..."
}This means you can script exploration workflows with any MQTT client, Home Assistant automations, or shell scripts using mosquitto_pub/mosquitto_sub.
Start with cluster discovery. Before scanning attributes, send discover_clusters: all to see what's on the device. Look for manufacturer-specific clusters (usually high cluster IDs like 0xFCC0, 0xFF01, etc.) and set TARGET_CLUSTER accordingly.
Scan in chunks. Don't try 0000-FFFF. Start with ranges you think are likely (often 0x0000-0x00FF or 0x0500-0x0600 for Aqara) and narrow from there.
Use snapshot/compare to reverse engineer. Take a snapshot, change one thing on the device, compare. Repeat. This is the fastest way to map attributes to functions.
Export snapshots for different device states. Take a snapshot with a device in "state A", export it, change to "state B", take another snapshot and export. You now have two baselines you can import and compare against at any time.
Watch the report log while using physical controls. The report listener catches attributes the device pushes on its own. Press buttons, toggle switches, or change settings on the device itself, and watch last_report update — those are the attributes you want to investigate.
Raw hex mode for binary protocols. Some attributes encode structured data in buffers (segment colors, effect definitions, etc.). Raw hex mode strips the friendly formatting so you can focus on the byte patterns.