Files
SmartThings-Local/smartthings_local/ocf/observe_refresh.py
T
Jack Nagy 3fdc735141 feat(packaging): nest protocol/ + ocf/ under smartthings_local, add PyPI packaging
Nest the two library packages under a single import namespace so they
can ship as one distribution:

  protocol/ -> smartthings_local/protocol/
  ocf/      -> smartthings_local/ocf/   (git mv, history preserved)

- Rewrite all imports protocol.* -> smartthings_local.protocol.*,
  ocf.* -> smartthings_local.ocf.* across the ocf modules, mqtt_demo/
  (bridge, descriptor, samples), and tests.
- Add pyproject.toml: dist name `smartthings-local`, hatch-vcs versioning
  from v* tags, wheel ships only smartthings_local/.
- Add .github/workflows/publish.yml: build + PyPI Trusted Publishing on
  v* tags (OIDC, no stored token).
- Force-include protocol/ocf_root_ca.pem via [tool.hatch.build] artifacts:
  it is tracked but matches .gitignore's *.pem, so hatchling's VCS file
  selection would drop it — and dtls_session.py loads it at runtime.
- Update mqtt_demo Dockerfile COPY and deploy.sh tar allowlist to the
  single smartthings_local/ package.
- .gitignore: build artifacts (_version.py, dist/, *.egg-info/).

Validated: pytest tests/ (11 passed), python -m build produces sdist +
wheel with the pem bundled, fresh pip install resolves all nested imports
with the pem readable from site-packages.
2026-07-06 20:29:19 +01:00

51 lines
1.7 KiB
Python

"""Periodic OBSERVE re-subscribe.
CoAP OBSERVE (RFC 7641) has no built-in TTL, but real-world peers age
out observer state on their own schedule — Samsung's RT-OCF is known
to silently drop notify delivery during cloud auth blips even though
the DTLS session stays healthy. Without a re-subscribe, recovery from
such a blip requires a full session reconnect.
This task derregisters the current observer tokens and re-subscribes
every `interval_s`. Cheap (one register CON per path), idempotent
(Samsung silently no-ops a register on an already-active token), and
resilient — individual subscribe failures are logged but don't abort
the task.
"""
from __future__ import annotations
import threading
from typing import Optional
from smartthings_local.protocol.dtls_session import DtlsCoapSession
class ObserveRefreshTask:
def __init__(self,
session: DtlsCoapSession,
paths,
interval_s: float = 6 * 3600.0,
logger=None):
self.session = session
self.paths = [list(p) for p in paths]
self.interval_s = interval_s
self.log = logger
self._refresh_count = 0
@property
def refresh_count(self) -> int:
return self._refresh_count
def run_forever(self, stop: threading.Event) -> None:
while not stop.wait(self.interval_s):
try:
self.session.refresh_observes(self.paths)
self._refresh_count += 1
if self.log:
self.log.info("OBSERVE refresh #%d (%d paths)",
self._refresh_count, len(self.paths))
except Exception as e:
if self.log:
self.log.warning("OBSERVE refresh: %s", e)