ha-paneld version
0.9.7 (build 776). The identifier involved has been published since v0.9.5 (commit 9a5d3c8b, "anchor MQTT discovery identity") and is unchanged in v0.9.8-rc1.
Panel hardware
Sonoff NSPanel Pro (86P / 120P, PX30 / rk3326)
What happened?
publishDiscovery() sends two device identifiers (v0.9.7, MqttBridge.kt lines 3822-3823):
val aid = config.androidId
val ids = if (aid.isNotBlank()) """["ha-paneld-$panel","ha-paneld-aid-$aid"]""" else """["ha-paneld-$panel"]"""
config.androidId is Settings.Secure.ANDROID_ID (also used as serial_number). The comment above it states the intent — "HA merges a device on ANY matching identifier, so a later panel_id change re-attaches to the SAME HA device instead of minting a duplicate."
That's exactly what makes it hazardous when ANDROID_ID isn't unique. On Android 8.1 the app-visible ANDROID_ID is per-signing-key but derived from a per-device seed, and on panels flashed from the same factory image that seed is cloned — so every panel hands ha-paneld the same value, and Home Assistant merges them all into one device.
Measured across my 16 NSPanel Pro units:
nsp1 7581153b255d0538 nsp9 7581153b255d0538
nsp2 7581153b255d0538 nsp10 7581153b255d0538
nsp3 7581153b255d0538 nsp11 5c39f66682346472
nsp4 7581153b255d0538 nsp12 7581153b255d0538
nsp5 7581153b255d0538 nsp13 7581153b255d0538
nsp6 7581153b255d0538 nsp14 e637ddadb4031a59
nsp7 7581153b255d0538 nsp15 7581153b255d0538
nsp8 fe3b887884df3ce7 nsp16 94e5ab44eec84a4
12 of 16 share one value. (The value ha-paneld publishes is bc9265382fac70fe rather than 7581153b255d0538 — SSAID is per-signing-key — but it derives from the same cloned seed, so it is equally identical across those 12.) The Home Assistant device registry matches that split exactly: the MQTT integration has 5 paneld-* devices instead of 16, and the one named paneld-nsp3 carries 216 entities belonging to 12 different panels. Its identifier list:
["mqtt","ha-paneld-aid-bc9265382fac70fe"] ← published identically by all 12
["mqtt","ha-paneld-ha_paneld_nsp1"]
["mqtt","ha-paneld-ha_paneld_nsp2"]
["mqtt","ha-paneld-ha_paneld_nsp3"] … nsp4, nsp5, nsp6, nsp7, nsp9, nsp10, nsp12, nsp13, nsp15
["mqtt","ha-paneld-px30_evb_70fe"] ← legacy auto-generated panel id
Only nsp8, nsp11, nsp14 and nsp16 — the four with distinct values — have their own device.
Consequences:
- Every entity from 11 of those panels is named after nsp3:
update.ha_paneld_nsp1_ha_companion has friendly name "paneld-nsp3 HA Companion". Settings → Updates shows the same panel name a dozen times over, which is how I found this.
- Per-panel areas, device pages, device automations and any
device_id-based targeting are impossible for those 11 panels. suggested_area only ever applied to the first registration.
- It reaches the default panel id too: that's derived from model + the last four hex of ANDROID_ID (
px30_evb_70fe above), so a cloned fleet collides there as well unless explicit panel ids are set.
There's no workaround from the user side. Deleting the merged device in Home Assistant doesn't help — the panels re-announce the same identifier on the next connect and re-merge immediately. ANDROID_ID isn't practically changeable on Android 8.1 (per-signing-key SSAID). It needs a change in ha-paneld.
Possible fixes, roughly in order of preference:
- Mint a random id once on first run and persist it in ha-paneld's own settings, and publish that as the second identifier. It's genuinely unique, survives a
panel_id change (the original goal), and is immune to image cloning. It would need excluding from fleet restore/export the way other device-scoped keys already are, or restored panels would clone it again.
- Use
ro.serialno, which is unique on this hardware — I checked three panels and each has a distinct serial, including two that share the cloned ANDROID_ID. ha-paneld has root on these panels, though I don't know how dependable that property is across the whole supported hardware range.
- Drop
ha-paneld-aid-<id> from identifiers and keep ANDROID_ID only as serial_number, where it's informational and can't merge anything. That gives up the re-attach-on-rename property, which is what the pre-0.9.5 behaviour did anyway.
A validity check would also help regardless of which route is taken: if the value is blank, a well-known constant, or otherwise not unique, don't publish it as an identifier.
Whatever lands will need a note that existing merged devices must be deleted in Home Assistant by hand once panels stop publishing the shared identifier, so each one re-registers on its own.
Steps to reproduce
- Two or more panels flashed from the same factory image, reporting the same value for
adb shell settings get secure android_id.
- Give each a distinct
panel_id and friendly name; point them at the same broker.
- Settings → Devices & services → MQTT: a single device holds every panel's entities, named after whichever registered first.
Diagnostic dump (/diag)
One of the merged panels:
ha-paneld diagnostics — 0.9.7 (build 776)
[captured] 2026-09-15T10:31:18.509-06:00 uptime=2d4h
[panel]
ha-paneld=0.9.7 (build 776)
Android=8.1.0 (API 27)
Firmware=3.8.0
Device=rockchip px30_evb (px30_evb)
CPU=4 cores · arm64-v8a · rk30board
RAM=1.9 GB total · 1.2 GB free
Storage=7.8 GB eMMC · 2.2 GB free (data)
Display=750×1334 px · logical 320 dpi
System WebView=com.android.webview 138.0.7204.63
HA Companion=not installed
MQTT state=connected · TCP/IPv4 · last-ok 29s ago · last-auth 101437s ago · auth-ok · family Automatic (next IPv4)
Security mode=Relaxed
Keep panel responsive=on · power locks held
Prevent idle dim=on · timeout never
Android dashboard lock=off
LED=none
Nav actions (a11y)=yes
Navbar=Swipe reveal
Zigbee=vendor-native · running · Repeater
Relays=none
Network ADB=active (5555) · external — not persisted by ha-paneld
Audio playback=idle
App database=6.3 MB used · 7.3 MB on disk · schema 14
Product version=NSPanel120P_3.8.0
Local-state sync=449s ago · brightness 222→226 · 569s ago · brightness 218→222 · 869s ago · brightness 223→218 · 989s ago · brightness 219→223 · 1229s ago · brightness 215→219 · 1709s ago · brightness 211→215 · 2129s ago · brightness 207→211 · 2369s ago · brightness 202→207
State convergence=43 channels · 0 dirty · 0 in-flight · 15 unknown · ack 905/0 · pending none
[build] fingerprint=rockchip/px30_evb/px30_evb:8.1.0/OPM8.190605.003/092718:userdebug/test-keys
board=rk30sdk product=px30_evb hardware=rk30board abis=arm64-v8a,armeabi-v7a,armeabi
[boot-security] verified=green flash=locked vbmeta=unknown build=userdebug debuggable=yes
[env] selinux=0 su=true write_settings=true a11y=true daemon=true shizuku=manager_missing evdev=stopped/none ledjni=false
[display-sizing] android_base_logical_dpi=240 current_logical_dpi=320 override_dpi=320 font_scale=1.0 profile_recommended_dpi=250 profile_recommended_font_scale=1.0
[renderer] mode=builtin state=checking outcome=unobserved fault=none detail=none recovery=none transport=http address_family=automatic observed_age=49m process_age=1d4h package_updated=1d4h theme=follow/light
[ha-network] state=healthy cause=none responsiveness=healthy measuring=true socket=live window=5m probes=30 round_trips=30 p50=34 p95=123 max=137 jitter=27 loss=0.0% consecutive=0 server_errors=0 auth_errors=0 last_reply_age=4734
[ha-path-probe] state=healthy family=ipv4 bursts=6 sent=30 received=30 loss=0.0% p50=23 p95=58 max=64 jitter=19 dead_bursts=0 interval=600s last_burst_age=155448
[camera] state=absent outcome=camera-unavailable fault=none detail=not_enumerated recovery=none clients=0 last_frame=never failures=0 indication=none stream_clients=0 stream_port=off encoder=none encode=none delivered=none requested_fps=none
[zigbee-health] state=unknown layout=unknown package=- joined=true role=Repeater gateway_cpu=4 guard_cpu=0 restarts_10m=0 containment=none recursive_watchdog=false
[storage-health] state=healthy pressure_state=healthy usable_bytes=2176237568 total_bytes=3606069248 used_percent=39.6506994643271 database_bytes=54165504 wal_bytes=524288 sidecar_bytes=163840 page_size_bytes=4096 page_count=13224 freelist_count=2 schema_version=14 quick_check=ok auto_vacuum=incremental checked_at=1789474827801 failure=none failure_operation=none
[power-safety] state=safe keep_awake=true wake_lock=true wifi_lock_required=true wifi_lock=true prevent_idle_dim=true screen_timeout_ms=2147483647 interactive=true power_source=ac plugged_mask=1 stay_on_mask=15 stay_on_effective=true device_idle=false doze_exempt=true screen_off=su_blpower screen_off_selected=unknown screen_off_reason="not yet exercised; no screen-off has occurred since this controller was constructed" reasons=none
[sysfs] leds=[] backlight=[backlight] devfreq=[dmc, ff400000.gpu]
[labels] ls: /sys/class/leds/*/: No such file or directory ls: /dev/ledjni: No such file or directory u:object_r:sysfs:s0 /sys/class/backlight/backlight/
[hardware]
inputs=[rk8xx_pwrkey (event0 cpufreq dmcfreq keychord ), chsc_cap_touch (event1 cpufreq dmcfreq ), adc-keys (event2 cpufreq dmcfreq keychord ), gpio_keys (event3 cpufreq dmcfreq ), lightsensor-level (event4 ), proximity (event5 )]
i2c=[0-0020:rk809, 1-002e:chsc_cap_touch, 1-003c:tp, 2-0046:ls_stk3a5x, 2-0046-1:ps_stk3a5x, 5-0038:px30-aht20, i2c-0:rk3x-i2c, i2c-1:rk3x-i2c, i2c-2:rk3x-i2c, i2c-5:gpio_i2c@3]
iio=[]
thermal=[cooling_device0:thermal-cpufreq-0, cooling_device1:thermal-devfreq-0, cooling_device2:thermal-devfreq-1, thermal_zone0:soc-thermal, thermal_zone1:gpu-thermal]
relays=[st_relay/mode, st_relay/relay1, st_relay/relay2, st_relay/relay3, st_relay/relay4]
[packages] android=not installed minimal=not installed
[vendor-tame] known=7 installed=7 active=0 disabled=7
[capabilities] Root (su)=ok | Verified app update / screenshot / display=ok | Screen brightness=ok | Screen on/off=ok | Reboot / reload / launcher=ok
Home Assistant version
2026.9.2
Relevant logs
Nothing logged on either side — MQTT discovery is working as written; the identifier just isn't unique.
ha-paneld version
0.9.7 (build 776). The identifier involved has been published since v0.9.5 (commit
9a5d3c8b, "anchor MQTT discovery identity") and is unchanged in v0.9.8-rc1.Panel hardware
Sonoff NSPanel Pro (86P / 120P, PX30 / rk3326)
What happened?
publishDiscovery()sends two device identifiers (v0.9.7,MqttBridge.ktlines 3822-3823):config.androidIdisSettings.Secure.ANDROID_ID(also used asserial_number). The comment above it states the intent — "HA merges a device on ANY matching identifier, so a later panel_id change re-attaches to the SAME HA device instead of minting a duplicate."That's exactly what makes it hazardous when ANDROID_ID isn't unique. On Android 8.1 the app-visible ANDROID_ID is per-signing-key but derived from a per-device seed, and on panels flashed from the same factory image that seed is cloned — so every panel hands ha-paneld the same value, and Home Assistant merges them all into one device.
Measured across my 16 NSPanel Pro units:
12 of 16 share one value. (The value ha-paneld publishes is
bc9265382fac70ferather than7581153b255d0538— SSAID is per-signing-key — but it derives from the same cloned seed, so it is equally identical across those 12.) The Home Assistant device registry matches that split exactly: the MQTT integration has 5paneld-*devices instead of 16, and the one namedpaneld-nsp3carries 216 entities belonging to 12 different panels. Its identifier list:Only nsp8, nsp11, nsp14 and nsp16 — the four with distinct values — have their own device.
Consequences:
update.ha_paneld_nsp1_ha_companionhas friendly name "paneld-nsp3 HA Companion". Settings → Updates shows the same panel name a dozen times over, which is how I found this.device_id-based targeting are impossible for those 11 panels.suggested_areaonly ever applied to the first registration.px30_evb_70feabove), so a cloned fleet collides there as well unless explicit panel ids are set.There's no workaround from the user side. Deleting the merged device in Home Assistant doesn't help — the panels re-announce the same identifier on the next connect and re-merge immediately. ANDROID_ID isn't practically changeable on Android 8.1 (per-signing-key SSAID). It needs a change in ha-paneld.
Possible fixes, roughly in order of preference:
panel_idchange (the original goal), and is immune to image cloning. It would need excluding from fleet restore/export the way other device-scoped keys already are, or restored panels would clone it again.ro.serialno, which is unique on this hardware — I checked three panels and each has a distinct serial, including two that share the cloned ANDROID_ID. ha-paneld has root on these panels, though I don't know how dependable that property is across the whole supported hardware range.ha-paneld-aid-<id>fromidentifiersand keep ANDROID_ID only asserial_number, where it's informational and can't merge anything. That gives up the re-attach-on-rename property, which is what the pre-0.9.5 behaviour did anyway.A validity check would also help regardless of which route is taken: if the value is blank, a well-known constant, or otherwise not unique, don't publish it as an identifier.
Whatever lands will need a note that existing merged devices must be deleted in Home Assistant by hand once panels stop publishing the shared identifier, so each one re-registers on its own.
Steps to reproduce
adb shell settings get secure android_id.panel_idand friendly name; point them at the same broker.Diagnostic dump (
/diag)One of the merged panels:
Home Assistant version
2026.9.2
Relevant logs
Nothing logged on either side — MQTT discovery is working as written; the identifier just isn't unique.