98 Commits
Author SHA1 Message Date
Quite Yellow 4999e8beac Merge pull request #47 from Moballo-LLC/codex/ocf-multicast-responder
feat(protocol): discover known-host OCF responder ports
2026-08-21 12:17:13 +01:00
Quite Yellow b3045f5ddb Merge pull request #51 from Moballo-LLC/codex/coap-initial-pacing
fix(protocol): pace CoAP request sends
v0.1.9
2026-08-21 08:38:01 +01:00
Jason Morcos 0d9d13b8fb fix(protocol): pace CoAP request sends 2026-08-18 14:25:10 -07:00
Quite Yellow 0722c552c9 Merge pull request #52 from QuiteYellow/fix/oven-idle-setpoint
fix(mqtt): withhold the oven setpoint when no cycle is set
2026-08-18 20:27:44 +01:00
Jack Nagy a45ee4c004 fix(mqtt): withhold the oven setpoint when no cycle is set
With no cycle set the oven reports x.com.samsung.da.desired = 0, and
flatten() published that straight through as target_temp_c. Home
Assistant rejects it against the Number entity's declared 30-270 range
on every publish, which produced 66,899 log errors over three weeks:

  Invalid value for number.samsung_oven_setpoint: 0 (range 30.0 - 270.0)

0 is not a 0 degree target, it is the absence of a setpoint, so treat
anything outside the settable band as absent. null lands as unknown on
both the Number and the Setpoint sensor, the way completion_minutes
already reads when the oven is idle. _setpoint applied these bounds on
the write side already; only the read path was missing them.

Adds the first tests for the sample descriptors. One of them pins a
non-obvious asymmetry: the write path snaps to the 5 degree step grid
before bounds-checking, so 29 commits as 30 and 271 as 270, and only 0
is refused outright. The invariant that has to hold is the weaker one,
that every value the write path commits is one flatten() will publish
back, or a write appears to succeed and then reads as unknown.
2026-08-18 20:25:19 +01:00
Jason Morcos 512df7ff36 feat(protocol): discover known-host OCF responder ports 2026-08-18 12:22:11 -07:00
Quite Yellow 6b9a508fd9 Merge pull request #44 from Moballo-LLC/codex/issue-9-session-workers
fix(mqtt): retire session workers on reconnect
2026-08-18 20:19:04 +01:00
Quite Yellow ab0ec4961c Merge pull request #46 from Moballo-LLC/codex/owner-psk-clarifications
docs(protocol): clarify OwnerPSK vector scope
2026-08-18 20:18:58 +01:00
Jason Morcos 6db846563a docs(protocol): clarify OwnerPSK vector scope 2026-08-18 11:10:44 -07:00
Jason Morcos 8c374e17b4 fix(mqtt): retire session workers on reconnect 2026-08-18 11:10:38 -07:00
Quite Yellow cd86424ca0 Merge pull request #45 from Moballo-LLC/codex/owner-psk-derivation
feat(protocol): add pure OwnerPSK derivation
2026-08-17 19:49:06 +01:00
Quite Yellow 6da7e95731 Merge pull request #43 from Moballo-LLC/codex/py-08b1-completion-cancel
fix(protocol): keep completed sessions on late cancel
2026-08-17 19:48:53 +01:00
Quite Yellow a3bec9470d Merge pull request #42 from Moballo-LLC/codex/py-08a1-completed-handshake
fix(protocol): retain completed DTLS handshakes
2026-08-17 19:48:44 +01:00
Jason Morcos 79493fbd48 feat(protocol): add pure OwnerPSK derivation 2026-08-15 12:24:11 -07:00
Jason Morcos 627fcb19da fix(protocol): keep completed sessions on late cancel 2026-08-15 11:44:24 -07:00
Jason Morcos 31be87061a fix(protocol): retain completed DTLS handshakes 2026-08-15 11:42:28 -07:00
Quite Yellow bc4465b274 Merge pull request #40 from QuiteYellow/fix/observe-block2-truncation
fix(protocol): reassemble blockwise OBSERVE notifications
v0.1.8
2026-08-15 14:08:04 +01:00
Jack Nagy e63acb759f feat(protocol): surface OBSERVE refetch outcomes under DEBUG_BRIDGE
The refetch path logged only at debug, and the bridge configures logging
at INFO, so a successful re-read and a total failure produced identical
output: nothing. That makes the hardware validation for #39 impossible
to read.

One line per refetch, promoted to INFO when DEBUG_BRIDGE=1 and left at
debug otherwise, naming the href, the one-shot token, the block count,
and the reassembled size. The token is the part that matters: it is what
shows the re-read used a fresh 4-byte token rather than the observation's
1-byte one, which is the assumption the whole design rests on.

Gating on DEBUG_BRIDGE rather than raising the logger keeps the per-block
retransmit lines out of the way, and matches how the module already gates
its frame dump.
2026-08-15 13:28:00 +01:00
Jack Nagy 231e88a8c6 fix(protocol): reassemble blockwise OBSERVE notifications
A notification carries only the first block of a large representation
(RFC 7959 §2.6). _dispatch_coap handed that block straight to
on_notification, so consumers decoded a truncated CBOR buffer. Reported
twice on /mode/vs/0: #37 and mbillow/localthings#361.

The recovery is a re-read from block 0 on a fresh 4-byte one-shot token,
not a §2.6 continuation. §3.4 rules out reusing the observation's token,
and this server drops a transfer that opens at NUM>0 under a token it
has not seen, so a continuation is the one shape that cannot work here.
A truncated notification is now withheld and queued to a worker thread
that re-reads the resource and delivers the reassembled representation.
When the re-read fails the notification is dropped at debug level and
the poll tiers carry freshness, which is what they already did.

The re-read has to run off the reader thread: _dispatch_coap runs there
and the transfer waits on an event only that same thread can set. The
worker is serialized and paces between transfers, so a notification
storm stays under the firmware request ceiling.

Also in the Block2 loop, now extracted and shared by both paths:

- compare the response's Block2 NUM against the one requested, so a
  retransmitted block is no longer concatenated as if it were the next
- compare ETags across blocks (§2.4) and restart once when the
  representation changes mid-transfer
- recompute the next block number from the accumulated byte offset when
  the server negotiates the block size down
- re-check reader liveness while waiting on a block, so a mid-transfer
  reader death fails fast instead of burning the whole timeout
- guard the token counters and _pending with a lock, now that the
  session issues concurrent reads of its own

Closes #39
2026-08-15 12:39:48 +01:00
Jack Nagy dec84c9ad8 docs(readme): fill gaps left by the auth/handshake stack merges
- add dtls_handshake.py (from #34) to the repo-layout tree
- point the issue #16 / #20 notes at SamsungServerProfile / ServerCertificateAuth instead of calling that path unsupported
- list the new certificate-profile, connect-deadline, and session-interruption test modules
v0.1.7
2026-08-15 11:17:42 +01:00
Quite Yellow e9aee1c235 Merge pull request #33 from Moballo-LLC/codex/py-07-certificate-profiles
Add bound Samsung server certificate profile
2026-08-15 10:20:14 +01:00
Quite Yellow 9ef5598813 Merge pull request #35 from Moballo-LLC/codex/py-08b-session-interruption
Add cancellable DTLS connection attempts
2026-08-15 10:20:05 +01:00
Quite Yellow 917b0e47c5 Merge pull request #34 from Moballo-LLC/codex/py-08a-bounded-connect
Bound DTLS handshakes with a monotonic deadline
2026-08-15 10:19:52 +01:00
Jason Morcos 83a5973434 feat(protocol): add bound server certificate profile 2026-08-14 14:55:10 -07:00
Jason Morcos 3f0e437880 feat(protocol): add cancellable session interruption 2026-08-14 14:37:30 -07:00
Jason Morcos a44930f9df feat(protocol): bound DTLS handshake deadline 2026-08-14 14:26:33 -07:00
Quite Yellow b0d51abcc8 Merge pull request #38 from QuiteYellow/fix/reader-death-visible
fix(dtls): make reader-thread death visible and fail fast
v0.1.6
2026-08-14 17:38:41 +01:00
Jack Nagy 7a74a955f3 fix(dtls): make reader-thread death visible and fail fast
The reader loop exited silently on any socket error, leaving conn/sock
set so the session still looked open. Every later get()/post()/ping()
then waited out its full request timeout on a session nobody was
reading, raising SessionTimeoutError on repeat, forever.

This started biting in v0.1.3 (d677c72), which moved to connected UDP
sockets: a connected socket surfaces ICMP errors on recv, so one
ECONNREFUSED from a rebooting appliance now killed the reader.

- Advisory ICMP errnos (ECONNREFUSED/EHOSTUNREACH/...) no longer kill
  the reader; the next datagram usually works.
- Real reader exits log at WARNING; close()-driven exits stay quiet.
- A _reader_running Event lets get/post/ping/subscribe/refresh_observes
  fail fast via _check_live() with SessionClosedError instead of waiting
  out a timeout. Callers that never start a reader are unaffected.

Refs QuiteYellow/SmartThings-Local#37
2026-08-14 17:33:13 +01:00
Quite Yellow d4aebebca4 Merge pull request #32 from Moballo-LLC/codex/py-06-psk-auth
feat(protocol): add PSK authentication provider
v0.1.5
2026-08-09 14:21:54 +01:00
Quite Yellow 7f0f1e5531 Merge pull request #31 from Moballo-LLC/codex/py-05-certificate-auth
refactor(protocol): add certificate authentication provider
v0.1.4
2026-08-09 10:41:16 +01:00
Jason Morcos 2a6fc627f2 feat(protocol): add PSK authentication provider 2026-08-08 16:06:53 -07:00
Jason Morcos 8fb37ca2ed refactor(protocol): add certificate authentication provider 2026-08-08 14:29:30 -07:00
Jack Nagy 74d78543e1 chore: allow NOTICE in distribution check allowlist
The distribution checker enforces an exact-contents allowlist; add the
NOTICE file to the wheel dist-info/licenses expectation and the sdist
required set so the packaged trademark notice passes verification.
2026-08-07 16:50:26 +01:00
Quite Yellow da8f917541 Merge pull request #29 from QuiteYellow/docs/trademark-notice
docs: add trademark non-affiliation notice (README + NOTICE)
2026-08-07 16:47:27 +01:00
Jack Nagy a63bc0bb01 docs: add trademark non-affiliation notice (README + NOTICE)
Add a Trademarks & disclaimer section to the README and a root NOTICE
file stating this is an independent, unofficial project not affiliated
with Samsung, and that Samsung/SmartThings marks are used nominatively.

Ship NOTICE inside the distributed artifacts by adding it to
license-files (wheel .dist-info/licenses/) and the sdist include list.
2026-08-07 16:46:47 +01:00
Quite Yellow 11c87b275b Merge pull request #25 from Moballo-LLC/codex/py-04-bounded-probe
feat(protocol): add bounded DTLS endpoint probing
v0.1.3
2026-08-07 15:53:19 +01:00
Quite Yellow 7ec999b3ff Merge pull request #24 from Moballo-LLC/codex/py-03-connected-endpoints
feat(protocol): resolve and connect UDP endpoints
2026-08-07 15:37:36 +01:00
Quite Yellow 31a52d6ff6 Merge pull request #23 from Moballo-LLC/codex/py-02-typed-errors
feat(errors): add redacted typed transport failures
2026-08-07 15:29:25 +01:00
Jack Nagy 2a82a764bb chore: add Buy Me a Coffee funding link 2026-08-05 16:52:10 +01:00
Jason Morcos dd453ebdfb feat(protocol): bound DTLS endpoint probing 2026-08-03 12:45:14 -07:00
Jason Morcos d677c72f89 feat(protocol): add resolved connected UDP endpoints 2026-08-03 12:45:04 -07:00
Jason Morcos c7e15a7dd3 feat(errors): add redacted typed failures 2026-08-03 12:44:51 -07:00
Quite Yellow 280939d646 Merge pull request #27 from QuiteYellow/fix/sdist-check-hgignore-optional
fix(ci): treat sdist VCS-ignore files as optional in distribution check
2026-08-03 19:51:01 +01:00
Jack Nagy 98e0020e2f fix(ci): treat sdist VCS-ignore files as optional in distribution check
hatchling 1.31.0 ships only .gitignore in the sdist; older releases also
bundled .hgignore. check_sdist required an exact member set including
.hgignore, so the Validate workflow's package job failed on main once CI
resolved the newer hatchling.

Require the tracked source set plus the fixed metadata files, and accept
.gitignore/.hgignore as optional members either way.
2026-08-03 19:45:09 +01:00
Quite Yellow 597a88ff25 Merge pull request #21 from Moballo-LLC/codex/py-01-ci-foundation
ci: add validation and package smoke tests
2026-08-03 19:33:28 +01:00
Quite Yellow 93e39de079 Merge pull request #22 from Moballo-LLC/codex/libressl-sha1-retry
fix(setup-cert): keep SHA-1 retry compatible with LibreSSL
2026-08-03 19:33:25 +01:00
Jason Morcos fc6240b72e test(safety): allow public GitHub attachments 2026-08-02 16:31:15 -07:00
Jason Morcos 6dc9dca339 fix(setup-cert): keep SHA-1 retry compatible with LibreSSL 2026-08-02 10:43:40 -07:00
Jason Morcos 2c93cb3097 test: make worker cleanup checks deterministic 2026-08-02 10:13:56 -07:00
Jason Morcos 23338995bf build: require pyOpenSSL DTLS timeout support 2026-08-02 10:07:49 -07:00