feat(washer): wire model-number fallback into discovery and config flow

This commit is contained in:
Marc Billow committed 2026-07-11 12:53:57 -05:00
1 parent 2852a21c4a
commit 22c5295d18
4 files changed
+72 -6

No files matched your search

+9 -4
View File
@@ -125,7 +125,7 @@ def _probe_and_validate(host: str, ca_cert_pem: str, ca_key_pem: str) -> dict:
import cbor2
from smartthings_local.protocol.dtls_session import DtlsCoapSession
from .registry.batch import parse_device0_batch
from .registry.by_type import for_device
from .registry.by_type import for_device, for_device_by_model
_LOGGER.debug("Fetching Samsung cloud UUID from %s", _SAMSUNG_CLOUD_HOST)
try:
@@ -177,15 +177,20 @@ def _probe_and_validate(host: str, ca_cert_pem: str, ca_key_pem: str) -> dict:
.get('swVersionInfo', {})
.get('oneUiVersion', '')
)
info_resource = resources.get('/information/vs/0', {})
recognized_registry = (
for_device(one_ui_version) if one_ui_version else None
) or for_device_by_model(
info_resource.get('x.com.samsung.da.modelNum', ''),
info_resource.get('x.com.samsung.da.description', ''),
)
return {
"port": port,
"serial": serial,
"leaf_cert_pem": fullchain_pem,
"leaf_key_pem": leaf_key_pem,
"one_ui_version": one_ui_version,
"device_type_recognized": bool(
one_ui_version and for_device(one_ui_version) is not None
),
"device_type_recognized": recognized_registry is not None,
}
except CannotConnect:
raise
+7 -2
View File
@@ -20,7 +20,7 @@ from smartthings_local.protocol.dtls_session import DtlsCoapSession
from smartthings_local.ocf.state_cache import StateCache
from .registry.batch import parse_device0_batch
from .registry.by_type import for_device
from .registry.by_type import for_device, for_device_by_model
from .registry.discovery import discover, BoundEntity
from .registry import CAPABILITIES
from .registry.adapter import flatten
@@ -273,7 +273,13 @@ class LocalThingsCoordinator(DataUpdateCoordinator[dict[str, Any]]):
.get('swVersionInfo', {})
.get('oneUiVersion', ''))
self.one_ui_version = one_ui
info = resources.get('/information/vs/0', {})
reg = for_device(one_ui) if one_ui else None
if reg is None:
reg = for_device_by_model(
info.get('x.com.samsung.da.modelNum', ''),
info.get('x.com.samsung.da.description', ''),
)
unbound: list[str] = []
if reg is not None:
self._log.debug("device type: %s (oneUiVersion=%r)", reg.name, one_ui)
@@ -287,7 +293,6 @@ class LocalThingsCoordinator(DataUpdateCoordinator[dict[str, Any]]):
self.bound = bound
self._unbound_hrefs = unbound
info = resources.get('/information/vs/0', {})
serial = info.get('x.com.samsung.da.serialNum', '')
if not serial:
serial = self._entry.data[CONF_HOST]
+35
View File
@@ -134,3 +134,38 @@ async def test_duplicate_device_aborted(hass: HomeAssistant, mock_probe) -> None
)
assert result['type'] == FlowResultType.ABORT
assert result['reason'] == 'already_configured'
def test_probe_marks_washer_as_recognized(monkeypatch):
"""A washer's probe response (no oneUiVersion) must still resolve via
the modelNum/description fallback so setup doesn't warn about an
unrecognized device type."""
from custom_components.localthings import config_flow
device0 = [
{'rt': ['x.com.samsung.devcol']},
{'href': '/information/vs/0', 'rep': {
'x.com.samsung.da.modelNum':
'DA_WM_TP1_21_COMMON|20375141|20010002001811424AA30217008A0000',
'x.com.samsung.da.description':
'DA_WM_TP1_21_COMMON_WW5000C/DC92-03495A_B048',
'x.com.samsung.da.serialNum': 'TEST-SERIAL',
}},
{'href': '/otninformation/vs/0', 'rep': {'otnStatus': 'None'}},
]
from custom_components.localthings.registry.batch import parse_device0_batch
resources = parse_device0_batch(device0)
info_resource = resources.get('/information/vs/0', {})
one_ui_version = (
resources.get('/otninformation/vs/0', {}).get('swVersionInfo', {}).get('oneUiVersion', '')
)
from custom_components.localthings.registry.by_type import for_device, for_device_by_model
recognized = bool(
(one_ui_version and for_device(one_ui_version) is not None)
or for_device_by_model(
info_resource.get('x.com.samsung.da.modelNum', ''),
info_resource.get('x.com.samsung.da.description', ''),
) is not None
)
assert recognized is True
+21
View File
@@ -113,6 +113,27 @@ async def test_coverage_gap_issue_absent_for_fully_covered_real_fixture(
assert ir.async_get(hass).async_get_issue(DOMAIN, issue_id) is None
def test_run_discovery_detects_washer_via_model_fallback(
hass: HomeAssistant, mock_entry
) -> None:
"""Washer hardware reports no oneUiVersion; device type must resolve
via for_device_by_model instead of falling back to generic CAPABILITIES."""
resources = {
'/information/vs/0': {
'x.com.samsung.da.modelNum':
'DA_WM_TP1_21_COMMON|20375141|20010002001811424AA30217008A0000',
'x.com.samsung.da.description':
'DA_WM_TP1_21_COMMON_WW5000C/DC92-03495A_B048',
'x.com.samsung.da.serialNum': 'TEST-SERIAL',
},
'/otninformation/vs/0': {'otnStatus': 'None'},
'/power/vs/0': {'x.com.samsung.da.power': 'On'},
}
coordinator = LocalThingsCoordinator(hass, mock_entry)
coordinator._run_discovery(resources)
assert coordinator.device_type_name == 'washer'
def test_update_coverage_gap_issue_creates_issue_for_unknown_type(
hass: HomeAssistant, mock_entry
) -> None: