{"id":48037933,"url":"https://github.com/absent42/zigbee-peek-and-poke","last_synced_at":"2026-04-04T14:00:55.588Z","repository":{"id":339588209,"uuid":"1162139149","full_name":"absent42/Zigbee-Peek-and-Poke","owner":"absent42","description":"Zigbee2MQTT external converter tool for reading, writing, scanning, and exploring manufacturer-specific cluster attributes on any Zigbee device","archived":false,"fork":false,"pushed_at":"2026-03-30T16:46:49.000Z","size":63,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-03-30T18:26:57.092Z","etag":null,"topics":["zigbee","zigbee-cluster-library","zigbee-hacking","zigbee-protocol","zigbee-sniffer","zigbee2mqtt"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/absent42.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-19T23:02:21.000Z","updated_at":"2026-03-30T16:46:53.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/absent42/Zigbee-Peek-and-Poke","commit_stats":null,"previous_names":["absent42/zigbee-peek-and-poke"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/absent42/Zigbee-Peek-and-Poke","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/absent42%2FZigbee-Peek-and-Poke","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/absent42%2FZigbee-Peek-and-Poke/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/absent42%2FZigbee-Peek-and-Poke/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/absent42%2FZigbee-Peek-and-Poke/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/absent42","download_url":"https://codeload.github.com/absent42/Zigbee-Peek-and-Poke/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/absent42%2FZigbee-Peek-and-Poke/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31402277,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T10:20:44.708Z","status":"ssl_error","status_checked_at":"2026-04-04T10:20:06.846Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["zigbee","zigbee-cluster-library","zigbee-hacking","zigbee-protocol","zigbee-sniffer","zigbee2mqtt"],"created_at":"2026-04-04T14:00:32.037Z","updated_at":"2026-04-04T14:00:55.463Z","avatar_url":"https://github.com/absent42.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Zigbee Peek and Poke (ZPP) v4\n\nA 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.\n\n\n## Installation\n\n 1. Edit the TARGET_ZIGBEE_MODEL, TARGET_CLUSTER, manufacturerCode constants, DEVICE_DEFINITION, and import statements below to match your device and cluster\n 2. In Z2M go to Settings-Dev Console-External Converters\n 3. Create a new converter and name it (e.g.) \"zpp.mjs\"\n 4. Paste this code into the editor\n5. Save\n6. Restart Zigbee2MQTT\n7. Device appears as the model name defined below\n\n\n## Configuration\n\nAll settings live at the top of the file. Edit these before starting Z2M.\n\n### `TARGET_ZIGBEE_MODEL`\n\nThe Zigbee model string your device reports. Find this on the device's \"About\" page in the Z2M frontend.\n\n```js\nconst TARGET_ZIGBEE_MODEL = 'lumi.plug.aeu002';\n```\n\n### `TARGET_CLUSTER`\n\nThe 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).\n\n```js\nconst TARGET_CLUSTER = 'manuSpecificLumi';\n```\n\n### `manufacturerCode`\n\nThe 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.\n\n```js\nconst manufacturerCode = 0x115f;\n```\n\n### `AUTO_SCAN_ATTRIBUTES`\n\nAn array of hex-string attribute IDs to automatically read every time Z2M starts. Leave empty to disable.\n\n```js\nconst AUTO_SCAN_ATTRIBUTES = ['0515', '0516', '0517'];\n```\n\n### `AUTO_SCAN_ENDPOINT`\n\nWhich endpoint the auto-scan reads from.\n\n```js\nconst AUTO_SCAN_ENDPOINT = 1;\n```\n\n### `WRITE_HISTORY_MAX`\n\nHow many entries the rolling write log keeps before discarding the oldest.\n\n```js\nconst WRITE_HISTORY_MAX = 20;\n```\n\n### `KNOWN_ATTRIBUTES`\n\nA 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:\n\n```js\nconst KNOWN_ATTRIBUTES = {};\n```\n\n### Z2M Library Imports\n\nAt 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/`.\n\n```js\nimport * as m from 'zigbee-herdsman-converters/lib/modernExtend';\nimport fz from 'zigbee-herdsman-converters/converters/fromZigbee';\nimport tz from 'zigbee-herdsman-converters/converters/toZigbee';\nimport * as reporting from 'zigbee-herdsman-converters/lib/reporting';\nimport * as lumi from 'zigbee-herdsman-converters/lib/lumi';\n```\n\nOnly add the imports that the definition you're pasting actually uses. Leaving unused imports is harmless but unnecessary.\n\n### `DEVICE_DEFINITION`\n\nThis 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.\n\nSupported fields — all optional:\n\n| Field | Type | Merge Behavior |\n|-------|------|---------------|\n| `extend` | Array | Passed to Z2M as-is (modern extend system) |\n| `fromZigbee` | Array | Device converters run before the tool's report listener |\n| `toZigbee` | Array | Device converters listed before the tool's controls |\n| `exposes` | Array | Device exposes appear above the tool's controls in the UI |\n| `configure` | Function | Runs before the tool's auto-scan handler |\n| `onEvent` | Function | Runs before the tool's auto-scan handler |\n| `meta` | Object | Shallow-merged (device meta wins on conflicts) |\n\nExample using modern extends (recommended for most current devices):\n\n```js\nimport * as m from 'zigbee-herdsman-converters/lib/modernExtend';\nimport * as lumi from 'zigbee-herdsman-converters/lib/lumi';\n\nconst DEVICE_DEFINITION = {\n    extend: [\n        m.onOff(),\n        m.electricityMeter(),\n        lumi.lumiModernExtend.lumiPowerOnBehavior(),\n    ],\n};\n```\n\nExample using legacy-style fromZigbee/toZigbee arrays:\n\n```js\nimport fz from 'zigbee-herdsman-converters/converters/fromZigbee';\nimport tz from 'zigbee-herdsman-converters/converters/toZigbee';\nimport * as e from 'zigbee-herdsman-converters/lib/exposes';\nimport * as reporting from 'zigbee-herdsman-converters/lib/reporting';\n\nconst DEVICE_DEFINITION = {\n    fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering],\n    toZigbee: [tz.on_off],\n    exposes: [e.presets.switch(), e.presets.power(), e.presets.energy()],\n    configure: async (device, coordinatorEndpoint, logger) =\u003e {\n        const ep = device.getEndpoint(1);\n        await reporting.bind(ep, coordinatorEndpoint, ['genOnOff', 'haElectricalMeasurement']);\n        await reporting.onOff(ep);\n    },\n};\n```\n\nTo 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.\n\n\n## Features\n\nAll features are accessed through the Z2M device page. Fields marked **(set)** accept input; fields marked **(read)** are output-only.\n\n---\n\n### Endpoint Selection\n\n**Field:** `endpoint` (set)\n\nSelects which device endpoint subsequent commands target. Defaults to 1. Devices like the Aqara T1M have a second endpoint for the RGB ring.\n\n| Value | Use |\n|-------|-----|\n| `1` | Main/default endpoint |\n| `2` | Secondary (e.g. T1M ring) |\n| Any positive integer | Any valid endpoint on the device |\n\n---\n\n### Read Single Attribute\n\n**Fields:** `select_attribute` (set), `read_attribute` (set), `attribute_value` (read), `attribute_status` (read)\n\nTwo ways to read:\n\n- **`select_attribute`** — dropdown of everything in `KNOWN_ATTRIBUTES`. Pick one and the read fires immediately.\n- **`read_attribute`** — free-text hex input for any attribute ID (e.g. `051a`, `0x0515`).\n\nThe result appears in `attribute_value` with a status line in `attribute_status`. If the device returns `UNSUPPORTED_ATTRIBUTE`, the status shows \"Not supported\".\n\n---\n\n### Batch Read\n\n**Fields:** `read_list` (set), `read_list_result` (read)\n\nRead multiple specific attributes in one operation. Provide a comma-separated list of hex IDs:\n\n```\n0515,0516,0517,0522,0530\n```\n\nUp to 64 attributes per call. Results are listed one per line in `read_list_result` with a summary count of found/errors.\n\nThis is useful when you already know which attributes matter and don't want to scan an entire range.\n\n---\n\n### Write Single Attribute\n\n**Fields:** `write_attribute` (set), `write_result` (read)\n\nThree formats:\n\n| Format | Example | Behavior |\n|--------|---------|----------|\n| `ATTR:VALUE` | `0515:0a` | Auto-detect type from byte length (1B→uint8, 2B→uint16, 4B→uint32, else→buffer) |\n| `ATTR:TYPE:VALUE` | `0524:uint16:0014` | Explicit type override |\n| `ATTR:TYPE:VALUE` | `0527:buf:0101030bff` | Force raw buffer |\n\nSupported types: `uint8`, `uint16`, `uint32`, `int8`, `int16`, `int32`, `buf`, `str`.\n\nAfter 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.\n\n---\n\n### Bulk Write\n\n**Fields:** `bulk_write` (set), `bulk_write_result` (read)\n\nWrite multiple attributes in one go using the same format as single writes, comma-separated:\n\n```\n0515:0a,0516:ff,0517:uint8:01\n```\n\nUp 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.\n\n---\n\n### Range Scan\n\n**Fields:** `scan_range` (set), `scan_result` (read)\n\nSweep a contiguous range of attribute IDs:\n\n```\n0515-0530\n```\n\nReads 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.\n\nGood starting strategy for an unknown device: scan `0000-007F`, then `0100-017F`, and so on in chunks.\n\n---\n\n### Snapshot and Compare\n\n**Field:** `snapshot` (set), `snapshot_result` (read), `snapshot_export` (read)\n\nA workflow for detecting what changes when you interact with a device through its physical controls or other Zigbee commands.\n\n#### Take a Snapshot\n\n```\nsnapshot:0515-0530\n```\n\nReads the range and stores all values in Z2M state. `snapshot_result` shows what was captured.\n\n#### Compare\n\n```\ncompare\n```\n\nRe-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.\n\nTypical workflow: snapshot, change something on the device (via its app, physical button, etc.), then compare to see which attributes moved.\n\n#### Export\n\n```\nexport\n```\n\nSerializes the current snapshot to a JSON string in `snapshot_export`. Copy this value to save it externally — snapshots normally vanish on Z2M restart.\n\nThe JSON includes the cluster name, manufacturer code, endpoint, timestamp, attribute range, and all captured values.\n\n#### Import\n\n```\nimport:{\"cluster\":\"manuSpecificLumi\",\"manufacturerCode\":4447,...}\n```\n\nPaste a previously exported JSON string (prefixed with `import:`) to restore a snapshot. You can then run `compare` against it.\n\n#### Clear\n\n```\nclear\n```\n\nDiscards the stored snapshot.\n\n---\n\n### Cluster Discovery\n\n**Fields:** `discover_clusters` (set), `cluster_list` (read)\n\nLists 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.\n\n| Value | Behavior |\n|-------|----------|\n| `all` | List clusters on every endpoint |\n| `1` | List clusters on endpoint 1 only |\n| `2` | List clusters on endpoint 2 only |\n\nOutput includes cluster names (when available from Z2M's cluster definitions) and numeric IDs.\n\n---\n\n### Raw Hex Display\n\n**Field:** `raw_hex` (set, toggle)\n\nWhen **OFF** (default), values display in a friendly format with decimal, hex, and byte breakdown:\n\n```\n1300 (0x514, bytes: [0x05, 0x14])\n```\n\nWhen **ON**, values display as raw hex only:\n\n```\n0514\n```\n\nAffects 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.\n\n---\n\n### Report Listener\n\n**Fields:** `last_report` (read), `report_log_display` (read), `clear_report_log` (set)\n\nPassively 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.\n\nReports 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`.\n\nSend any value to `clear_report_log` to reset.\n\nThe listener only captures reports on the cluster defined in `TARGET_CLUSTER`. Reports on other clusters are not intercepted.\n\n---\n\n### Write History\n\n**Fields:** `write_history_log` (read), `clear_write_history` (set)\n\nEvery 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.\n\nSend any value to `clear_write_history` to reset.\n\n---\n\n\n## MQTT API\n\nAll fields are accessible via MQTT publish/subscribe if you prefer scripting over the Z2M frontend.\n\nTo set a value, publish to `zigbee2mqtt/\u003cdevice\u003e/set` with a JSON payload:\n\n```json\n{\"read_attribute\": \"0515\"}\n{\"write_attribute\": \"0515:uint8:0a\"}\n{\"scan_range\": \"0500-0535\"}\n{\"read_list\": \"0515,0516,0517\"}\n{\"bulk_write\": \"0515:0a,0516:ff\"}\n{\"snapshot\": \"snapshot:0515-0530\"}\n{\"snapshot\": \"compare\"}\n{\"snapshot\": \"export\"}\n{\"discover_clusters\": \"all\"}\n{\"endpoint\": 2}\n{\"raw_hex\": true}\n```\n\nResults appear in the device's state, published to `zigbee2mqtt/\u003cdevice\u003e`:\n\n```json\n{\n  \"attribute_value\": \"10 (0xA)\",\n  \"attribute_status\": \"✓ EP1 0x0515 (Min Brightness)\",\n  \"scan_result\": \"EP1 scan 0x0500-0x0535: 12 found ...\",\n  \"write_result\": \"✓ EP1 Wrote uint8 to 0x0515 → read-back: 10 (0xA)\",\n  \"cluster_list\": \"EP1:\\n  Input clusters (5): ...\"\n}\n```\n\nThis means you can script exploration workflows with any MQTT client, Home Assistant automations, or shell scripts using `mosquitto_pub`/`mosquitto_sub`.\n\n\n## Tips\n\n**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.\n\n**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.\n\n**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.\n\n**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.\n\n**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.\n\n**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.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabsent42%2Fzigbee-peek-and-poke","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fabsent42%2Fzigbee-peek-and-poke","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabsent42%2Fzigbee-peek-and-poke/lists"}