oneUiVersion looks like the signal you'd want -- the device naming its own
type, '7.0 Dishwasher' -- and it was the first thing detection consulted. It
never earned the position:
- Only 7 of 49 fixtures report it at all.
- All 7 resolve to the same registry from their modelNum board token alone.
- No device-support issue has ever been fixed by adding a mapping for it.
Every one went through modelNum. The alias keys it needed in
_REGISTRY_BY_KEY ('airpurifier', 'air_conditioner', 'hood') were
speculative when the registries were first written and never used since.
So it bought a key-normalizing helper (_type_key), a lookup with a suffix
fallback (for_device), three alias keys, and a second config-flow step whose
only reason to exist was phrasing a sentence about oneUiVersion -- for a
signal that has never once been decisive.
Remove it from detection. It stays in diagnostics, where it's genuinely
useful: it names the firmware generation ('7.0 Air conditioner' is Tizen
Lite), which matters when triaging an issue.
Detection order was also duplicated in four places -- the coordinator, the
config flow's probe, the golden-regression harness, and the skill -- which
is how the harness and the shipped order drift apart. Collapse it into
by_type.resolve(resources), and call that everywhere.
The two "appliance type not recognized" config steps become one. They
differed only in whether they blamed a missing oneUiVersion, which is not a
distinction a user can act on, and never was.
Verified by the full suite (795 passing), including every golden regression
-- so entity output is byte-identical for all 49 device fixtures.
TestOneUiVersionIsNotConsulted locks in the premise rather than just the
outcome: for every dump that reports a oneUiVersion, the model strings alone
must still reach a registry. If a future device breaks that, the test says
so instead of the device silently losing half its entities.
Also note in requirements-dev.txt that Python 3.13 resolves the pinned
harness floor -- 3.12 and older resolve nothing and fail the whole install.
The suite was never run in CI (only hassfest/HACS), and requirements-dev
listed only pytest + smartthings-local while conftest.py needs the full
Home Assistant test harness.
- requirements-dev.txt: add pytest-homeassistant-custom-component (pulls
in home-assistant + pytest + pytest-socket) and the integration's
runtime deps (cbor2, pyOpenSSL, cryptography) needed to import it.
- .github/workflows/validate.yml: add a Pytest job on Python 3.14
(current Home Assistant's floor) that installs requirements-dev and
runs the suite.
- test_config_flow.py: the UDP liveness-sweep test needs real loopback
sockets, which the HA harness blocks by default; take the pytest-socket
socket_enabled fixture so it runs instead of erroring.
Full suite: 249 passed.
Now that mbillow/localthings#8's protocol fixes are merged upstream and
smartthings-local 0.1.0 is on PyPI, drop the vendored ocf/coap_dtls.py +
ocf/observe_refresh.py transport (dead code, never instantiated) and the
duplicated ocf_root_ca.pem in favor of the real package. manifest.json and
requirements-dev.txt now pin smartthings-local>=0.1.0 instead of the
unmerged git branch.
README rewritten from scratch — it still described the old standalone
MQTT-bridge (samsung_appliance/, main.py, docker-compose bridge) that this
repo moved off of; it now documents the real architecture: a native HA
custom component with config-flow-driven cert minting and a per-device-type
capability registry, with the protocol layer split out to smartthings-local.