fix: recognize all-same-hex-digit placeholder serials (#189)

_is_placeholder_serial only caught the ARTIK051_DONGLE_REF family's
'Nothing(SVC)' sentinel. The DA_WM_A51_20_COMMON (ARTIK051) laundry
board family reports a different one instead -- every character the
same repeated hex digit -- which passed through as a real, non-unique
serial. Two different physical units (a washer and a dryer) both
reporting the literal serialNum 'FFFFFFFFFFFFFFF' collided on the
config-entry unique_id, so the second device's config flow aborted as
already configured. Widen the check (both the config_flow.py and
coordinator.py copies) to also catch that sentinel shape.
This commit is contained in:
Marc Billow
2026-07-31 01:59:49 +00:00
parent 822c6814b6
commit e642db462d
4 changed files with 93 additions and 7 deletions
+16 -1
View File
@@ -215,8 +215,23 @@ def _is_placeholder_serial(serial: str) -> bool:
serial` check here (and the equivalent one in coordinator.py's
`_run_discovery`) doesn't catch it, and two such units get the same
config-entry unique_id / entity unique_ids and collide (issue #83).
Issue #189: the DA_WM_A51_20_COMMON (ARTIK051) laundry board family
reports a flash-unset sentinel instead -- every character the same
repeated hex digit (a washer and a dryer, two different physical
units, both reported the literal serialNum 'FFFFFFFFFFFFFFF') -- which
the 'nothing' check above doesn't catch either, so the second unit's
config flow aborted as already configured.
"""
return serial.strip().lower().startswith('nothing')
s = serial.strip()
if s.lower().startswith('nothing'):
return True
upper = s.upper()
return (
len(upper) >= 8
and len(set(upper)) == 1
and upper[0] in '0123456789ABCDEF'
)
def _probe_and_validate(host: str, ca_cert_pem: str, ca_key_pem: str) -> dict:
+23 -5
View File
@@ -92,12 +92,30 @@ def _is_placeholder_serial(serial: str) -> bool:
serial` check below doesn't catch it, and `device_serial` feeds both
the HA device-registry identifier and every entity's unique_id
(entity.py), so two such units on the same install silently collide
and the second one's entities get dropped (issue #83). Mirrors the
identical helper in config_flow.py's `_probe_and_validate` -- kept
separate rather than imported to avoid pulling the config-flow module
into the runtime coordinator's import graph for a two-line check.
and the second one's entities get dropped (issue #83).
Issue #189: the DA_WM_A51_20_COMMON (ARTIK051) laundry board family
reports a flash-unset sentinel instead -- every character the same
repeated hex digit (a washer and a dryer, two different physical
units, both reported the literal serialNum 'FFFFFFFFFFFFFFF') -- which
the 'nothing' check above doesn't catch either, so two such units
collided on the config-entry unique_id and the second couldn't be
added at all.
Mirrors the identical helper in config_flow.py's `_probe_and_validate`
-- kept separate rather than imported to avoid pulling the config-flow
module into the runtime coordinator's import graph for a two-line
check.
"""
return serial.strip().lower().startswith('nothing')
s = serial.strip()
if s.lower().startswith('nothing'):
return True
upper = s.upper()
return (
len(upper) >= 8
and len(set(upper)) == 1
and upper[0] in '0123456789ABCDEF'
)
class LocalThingsCoordinator(DataUpdateCoordinator[dict[str, Any]]):
+15
View File
@@ -539,3 +539,18 @@ def test_is_placeholder_serial_accepts_real_serials():
from custom_components.localthings.config_flow import _is_placeholder_serial
assert _is_placeholder_serial('0A1B2C3D4E5F') is False
assert _is_placeholder_serial('') is False
def test_is_placeholder_serial_catches_all_same_hex_digit():
"""Issue #189: the DA_WM_A51_20_COMMON (ARTIK051) laundry board family
reports a flash-unset sentinel instead of 'Nothing(SVC)' -- every
character the same repeated hex digit. A washer and a dryer, two
different physical units, both reported the literal serialNum
'FFFFFFFFFFFFFFF', colliding on the config-entry unique_id."""
from custom_components.localthings.config_flow import _is_placeholder_serial
assert _is_placeholder_serial('FFFFFFFFFFFFFFF') is True
assert _is_placeholder_serial('ffffffffffffffff') is True
assert _is_placeholder_serial('00000000') is True
# Too short to be the flash-unset sentinel -- a real serial could
# plausibly repeat one hex digit seven times by chance.
assert _is_placeholder_serial('FFFFFFF') is False
+39 -1
View File
@@ -15,7 +15,7 @@ from custom_components.localthings.const import (
SUMMARY_INTERVAL_S,
)
from custom_components.localthings.coordinator import (
LocalThingsCoordinator, _local_source_port,
LocalThingsCoordinator, _is_placeholder_serial, _local_source_port,
)
from custom_components.localthings.registry.capabilities.common import (
remote_control_enabled,
@@ -163,6 +163,44 @@ def test_run_discovery_falls_back_to_host_for_placeholder_serial(
assert coordinator.device_serial == mock_entry.data[CONF_HOST]
def test_run_discovery_falls_back_to_host_for_all_f_placeholder_serial(
hass: HomeAssistant, mock_entry
) -> None:
"""Issue #189: the DA_WM_A51_20_COMMON (ARTIK051) laundry board family
reports a flash-unset sentinel instead of 'Nothing(SVC)' -- every
character the same repeated hex digit. A washer and a dryer, two
different physical units, both reported the literal serialNum
'FFFFFFFFFFFFFFF', so without this fallback they'd collide on
device_serial exactly like the #83 case above."""
resources = {
'/information/vs/0': {
'x.com.samsung.da.modelNum':
'DA_WM_A51_20_COMMON|20221341|30010102001211000103000000000000',
'x.com.samsung.da.description':
'DA_WM_A51_20_COMMON_DVE50A8800/DC92-02835A_0080',
'x.com.samsung.da.serialNum': 'FFFFFFFFFFFFFFF',
},
'/otninformation/vs/0': {},
}
coordinator = LocalThingsCoordinator(hass, mock_entry)
coordinator._run_discovery(resources)
assert coordinator.device_serial == mock_entry.data[CONF_HOST]
def test_is_placeholder_serial_catches_all_same_hex_digit():
assert _is_placeholder_serial('FFFFFFFFFFFFFFF') is True
assert _is_placeholder_serial('ffffffffffffffff') is True
assert _is_placeholder_serial('00000000') is True
def test_is_placeholder_serial_accepts_real_serials_and_short_runs():
assert _is_placeholder_serial('0A1B2C3D4E5F') is False
assert _is_placeholder_serial('') is False
# Too short to be the flash-unset sentinel -- a real serial could
# plausibly repeat one hex digit seven times by chance.
assert _is_placeholder_serial('FFFFFFF') is False
def test_run_discovery_detects_cooktop_via_resource_signature(
hass: HomeAssistant, mock_entry
) -> None: