b5e25d72d3585dcc68f93d668aef404201fe9dbe
5
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ba089dbb5c |
Bump smartthings-local floor to 0.1.8
Two releases landed since the 0.1.6 pin, both confirmed by upstream (QuiteYellow, in issue #361) as additive/opt-in with no interface changes on our side: - 0.1.7: server-certificate profiles (SamsungServerProfile), a bounded DTLS handshake deadline (connect() now defaults to a 12s bound instead of none), and a cancellable connect() via ConnectCancellation. Our connect() call sites in coordinator.py and config_flow.py pass no args, so they pick up the bounded handshake for free; the cert-profile and cancellation pieces are opt-in and unused here. - 0.1.8: fixes blockwise OBSERVE notification reassembly (QuiteYellow/SmartThings-Local#39) -- a notification carrying only the first Block2 block was previously handed straight to on_notification instead of being reassembled, and separately, the Block2 loop could append a retransmitted/late block as if it were the next one, or miscompute the next block offset after a mid- transfer size downshift. Both corrupt a multi-block observed resource without necessarily truncating it -- the "premature end of stream" / "error decoding unicode string" CBOR failures reported in issue #361 on /mode/vs/0. All error types stay within the existing compatible-built-in table (ConnectionError/TimeoutError subclasses), so no exception handling changes. `>=0.1.6` already permitted pip to resolve 0.1.8 on a fresh install, but an environment that already has 0.1.6 or 0.1.7 satisfying that floor won't be upgraded by Home Assistant's requirement check -- which is what #361's reporter is very likely still hitting on 0.22.0. Raising the floor to >=0.1.8 forces that upgrade on the next release. Verified against smartthings-local 0.1.8 from PyPI: full suite (1573 tests), ruff, and ty all pass. No source changes needed beyond the three version pins (manifest.json, requirements-dev.txt, Dockerfile). |
||
|
|
3f9789512d |
Update to smartthings-local 0.1.6, handle redacted typed errors
Bumps the smartthings-local floor from >=0.1.2 to >=0.1.6 (manifest,
requirements-dev.txt, Dockerfile) and adopts the interface/behavior
changes introduced along the way:
- 0.1.3 ("redacted typed failures", PR #23) replaced connect()'s
ConnectionError(f"DTLS handshake error: {e}") with fixed, redacted
exceptions (SessionError, SessionTimeoutError, etc.) that never carry
backend text -- including the TLS alert name. The config flow's
_classify_handshake_failure relied on parsing that text out of the
exception (_alert_name) to tell a rejected certificate from any other
handshake failure; against a current library that regex never matches
again, silently downgrading every setup failure to the generic
"cannot_connect" message.
Fixed by adding _resolve_alert: it still tries _alert_name first (a
harmless fallback if it ever matches), then falls back to one bounded
smartthings_local.protocol.dtls_probe.diagnose_dtls_handshake() call
against the specific port that failed, which classifies the fatal
Alert straight from the raw record instead of an exception string.
_handshake_and_read now threads the resolved per-port alerts into
_classify_handshake_failure, so CertRejected vs. HandshakeFailed keeps
working the way it did before the redaction.
- 0.1.3 also moved the DTLS session onto a connected UDP socket (see
endpoint.py's open_connected_udp_socket), which changes why
coordinator._local_source_port needs a unique port per device -- the
kernel now demuxes by the full local-port/remote-peer tuple instead of
relying on an unconnected recvfrom(). Docstring updated to match.
- 0.1.6's reader-thread fail-fast fix (_check_live/_reader_running) makes
a dead reader raise SessionClosedError immediately instead of hanging
a request out to its timeout. No code change needed: SessionClosedError
is a ConnectionError subclass (not TimeoutError), so the coordinator's
existing _defer_reconnect_for/isinstance(e, TimeoutError) split already
routes it to the immediate-reconnect path.
Every other new/changed piece (endpoint.py, dtls_probe.py bounded
probing, auth.py's CertificateAuth/PskAuth providers) stays behind
compatible built-in exception types and unchanged get()/post()/
subscribe()/ping() signatures, per the library's own compatibility
table, so the coordinator's and observe.py's broad exception handling
needed no changes.
Tests: added coverage for _resolve_alert's exception-text vs.
diagnostic-handshake fallback, _classify_handshake_failure with a
resolved alerts mapping, and an end-to-end config-flow re-mint test
against a FakeSession that raises the new redacted SessionError instead
of the old text-bearing ConnectionError.
Full suite (1517 tests), ruff, and ty all pass against smartthings-local
0.1.6 installed from PyPI.
|
||
|
|
15be379243 |
Rebuild device discovery on the ClientHello probe and resolve identity up front
Two problems, one setup path. Port detection (issue #211): the config flow found the DTLS port by elimination -- a 1-byte UDP probe can't tell a silent port from a real DTLS server, so every port it couldn't rule out got a full certificate handshake, and every false positive cost the whole 12s HANDSHAKE_TIMEOUT_S before the next was tried. Adding an appliance took 30-40s. smartthings-local 0.1.2 ships a stateless ClientHello probe that settles this positively: a real DTLS server answers with a HelloVerifyRequest in ~1 RTT, and per RFC 6347 4.2.1 it does so without allocating association state, so the probe leaves nothing behind on the appliance. The whole 49152-49160 range is probed at once and exactly one confirmed port is given a certificate handshake. Fanning out is safe here in a way racing real handshakes is not -- each probe is bounded by a 3s budget, so the pool costs one probe's wall clock rather than the sum of the range, with no losing threads left running behind us. The UDP sweep stays as the fallback for when the probe confirms nothing: it errs in the opposite direction (it reports everything it can't rule out), so it still surfaces a device on a path that eats our ClientHello, and it keeps its issue #192 preferred-port rescue. Port detection now runs first and needs no credentials, so an unreachable host fails before any round trip to Samsung's cloud. And a second appliance reuses the existing entry's leaf cert rather than re-minting -- every device accepts the same one -- which makes adding one independent of Samsung-cloud reachability. A confirmed-live device rejecting the reused leaf (the UUID does rotate) re-mints and retries once, so reuse stays self-correcting; a timeout doesn't, since a fresh cert can't fix nothing answering. Identity (issue #236): the coordinator seeded device_serial with the configured host and only replaced it after the first successful poll. But device_serial mints *permanent* registry keys -- entity unique_ids and device identifiers -- so anything registering before that poll returned was written into the registry keyed on the IP address forever. The connection-mode sensor is added unconditionally rather than from `bound`, so it was the reliable victim: when the serial-keyed identity appeared moments later HA created a second device and entity, and the IP-keyed pair was orphaned. Deleting them didn't help; the next restart that lost the race recreated them. The probe already learns the identity, so store it on the config entry -- serial, model, manufacturer, device type. The coordinator seeds device_serial and its DeviceInfo from those at construction, so keys are correct from the first entity that registers even if the first poll is slow or fails outright. There is no placeholder left to correct. Discovery now treats the registered identity as authoritative rather than re-keying a device that already has registry entries; it adopts and persists the polled identity only for an entry that has none, and warns if a different appliance answers on the same IP. Entry version 1 -> 2 recovers the serial from the entry's unique_id (the flow has always keyed it on the probe's serial) and repairs what the old registration orphaned: IP-keyed devices and entities are rewritten in place where the serial-keyed key is free -- keeping entity_id, name, area and every automation referencing them -- and removed where both exist, since the IP-keyed one has been dead since the restart that made it. Placeholder-serial boards (issues #83/#189) were keyed two ways at once, `host:port` on the entry and `host` in the registry; migration collapses the entry onto the registry's form. One resolve_serial() now serves both sides, so they can't drift apart again. The remaining step in the desired pipeline -- probe for subdevices, then register devices, then populate entities -- already holds: _enumerate_subdevices_blocking runs before _run_discovery, which runs before platforms are forwarded. Duplicating it in the config flow would mean re-running Pattern B's per-href fallback probe, which is the opposite of what issue #211 is about. |
||
|
|
4c1497f5ba |
Bake smartthings-local into the dev Docker image
The dev container was relying on HA's runtime pip-install of manifest.json requirements, which only fires when the integration is set up and needs outbound network access at that exact moment; a container that had been running since before the smartthings-local migration kept the old code loaded in memory and never went through that install path, so restarting it failed once it picked up the new manifest.json. Repurpose the stale MQTT-bridge-era Dockerfile (its own code was already deleted from this repo) to build on the official HA image with smartthings-local pre-installed, and point docker-compose.yml at it via `build: .`. Verified end-to-end: rebuilt the image, recreated the container, and confirmed both live appliances (fridge, dishwasher) reconnect and discover entities with no runtime install needed. |
||
|
|
c99ef324fc | Initial commit |