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:
@@ -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:
|
||||
|
||||
@@ -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]]):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user