Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 57 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,14 @@ auth = CertificateAuth.from_files(
sess = DtlsCoapSession(
"192.0.2.100", 49154,
auth=auth,
on_notification=lambda href, payload: ...,
)
sess.connect()
sess.start_reader()

code, body = sess.get(["device", "0"]) # Block2-aware read
code, _ = sess.post(["mode", "vs", "0"], cbor2.dumps({})) # write
sess.subscribe(["operational", "state", "vs", "0"], # OBSERVE
on_notification=lambda href, payload: ...)
sess.subscribe(["operational", "state", "vs", "0"]) # OBSERVE
sess.close()
```

Expand All @@ -69,6 +69,41 @@ repeatable.
`delete()` uses the same path, query, extension-option, timeout, and response
contract without sending a request payload.

Observe relations may also include repeated URI-query strings. The same query
is retained for a blockwise notification refetch and for best-effort
deregistration:

```python
sess.subscribe(["mode", "vs", "0"], query=("if=oic.if.b",))
```

An RFC 7641 relation is confirmed only by a valid Observe response option;
duplicate and stale 24-bit sequence values are not delivered. Some older
Samsung firmware omits that option. For those devices, a plain initial `2.05`
is probationary until a later packet arrives on the same token with a different
Message ID. Optional `on_observe_pending`, `on_legacy_notification`, and
`on_observe_error` constructor callbacks let consumers keep that compatibility
path distinct from confirmed RFC notifications and ordinary polling.

Periodic renewal can target only the relations that need it; unrelated
observations remain active. Existing query variants are preserved unless the
caller supplies an explicit replacement:

```python
successful, failures = sess.refresh_observes(
(("mode", "vs", "0"),),
queries_by_href={"/mode/vs/0": ("if=oic.if.b",)},
)
removed = sess.unsubscribe(("mode", "vs", "0"))
```

`successful` reports hrefs whose replacement registration datagram was sent;
confirmation still comes from the Observe callbacks. `unsubscribe()` retires
every query-qualified relation for that exact path without disturbing sibling
paths. Refresh, unsubscribe, and orderly close pace every deregistration just
as `subscribe()` paces each registration, avoiding request bursts during
relation maintenance.

POST bodies through 1024 bytes retain the single-request behavior. Larger
bodies use token-stable Block1 requests under one monotonic timeout, include
Size1 on the first request, honor a server-requested smaller block size, and
Expand Down Expand Up @@ -102,8 +137,26 @@ sess.connect(timeout=8.0, cancel=cancel_connect)
```

Setting the signal stops subscribed connection attempts and closes their
temporary UDP sockets. It does not alter an already established session or add
new session lifecycle methods. Interrupted attempts raise `SessionClosedError`.
temporary UDP sockets. It does not alter an already established session.
Interrupted attempts raise `SessionClosedError`.

Hosts that stop network work before their blocking executor drains can use the
session's two-phase shutdown. `quiesce_for_close()` is terminal: it interrupts
an in-progress handshake, wakes pending requests and notification refetches,
and rejects new work while retaining an established DTLS socket. A subsequent
`close()` flushes the authenticated close-notify record before closing that
socket:

```python
sess.quiesce_for_close() # safe from the host's early shutdown phase
# Later, after session workers have joined:
sess.close()
```

Use `abort()` when orderly shutdown is impossible. It performs the same
terminal wakeup but closes the established socket immediately, without waiting
for close-notify. All three methods are idempotent; a quiesced or aborted
session cannot be connected again.

Reads retransmit each Block2 request; writes send once. Where a lost write
has been shown to be the cause rather than a device that is simply refusing
Expand Down
Loading