feat(washer): wire model-number fallback into discovery and config flow
This commit is contained in:
1 parent
2852a21c4a
commit
22c5295d18
4 files changed
+72
-6
No files matched your search
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user