python-swidget 1.4.16__tar.gz → 1.4.18__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_swidget-1.4.16 → python_swidget-1.4.18}/PKG-INFO +1 -1
- {python_swidget-1.4.16 → python_swidget-1.4.18}/pyproject.toml +1 -1
- {python_swidget-1.4.16 → python_swidget-1.4.18}/swidget/discovery.py +17 -6
- {python_swidget-1.4.16 → python_swidget-1.4.18}/swidget/swidgetdevice.py +18 -5
- {python_swidget-1.4.16 → python_swidget-1.4.18}/README.md +0 -0
- {python_swidget-1.4.16 → python_swidget-1.4.18}/swidget/__init__.py +0 -0
- {python_swidget-1.4.16 → python_swidget-1.4.18}/swidget/cli.py +0 -0
- {python_swidget-1.4.16 → python_swidget-1.4.18}/swidget/exceptions.py +0 -0
- {python_swidget-1.4.16 → python_swidget-1.4.18}/swidget/provision.py +0 -0
- {python_swidget-1.4.16 → python_swidget-1.4.18}/swidget/py.typed +0 -0
- {python_swidget-1.4.16 → python_swidget-1.4.18}/swidget/swidgetdimmer.py +0 -0
- {python_swidget-1.4.16 → python_swidget-1.4.18}/swidget/swidgetfan.py +0 -0
- {python_swidget-1.4.16 → python_swidget-1.4.18}/swidget/swidgetoutlet.py +0 -0
- {python_swidget-1.4.16 → python_swidget-1.4.18}/swidget/swidgetswitch.py +0 -0
- {python_swidget-1.4.16 → python_swidget-1.4.18}/swidget/swidgettimerswitch.py +0 -0
- {python_swidget-1.4.16 → python_swidget-1.4.18}/swidget/websocket.py +0 -0
|
@@ -59,10 +59,19 @@ devices = dict()
|
|
|
59
59
|
async def detect_secure(host: str, timeout: float = DETECT_TIMEOUT_SEC) -> bool:
|
|
60
60
|
"""Determine whether a Swidget device requires HTTPS+auth.
|
|
61
61
|
|
|
62
|
-
Probes ``/api/v1/summary`` over
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
Probes ``/api/v1/summary`` over HTTP first; a 200 means the device
|
|
63
|
+
is in plaintext mode. Secure-mode firmware running on the same port
|
|
64
|
+
answers 403 ("TLS required") instead, so anything non-200 falls
|
|
65
|
+
through to the HTTPS probe.
|
|
66
|
+
|
|
67
|
+
On HTTPS, both 401 and 403 indicate secure mode:
|
|
68
|
+
- **401 "Authorization Missing"** — no token in the request (our
|
|
69
|
+
probe doesn't carry one).
|
|
70
|
+
- **403 "Forbidden"** — token rejected ("Incorrect Token") or the
|
|
71
|
+
device's secret key has never been provisioned ("Key not set").
|
|
72
|
+
|
|
73
|
+
Both states mean "secure mode is enforced, prompt the caller for
|
|
74
|
+
credentials", so we collapse them into a single True return.
|
|
66
75
|
|
|
67
76
|
:param host: Hostname or IP of the device.
|
|
68
77
|
:param timeout: Per-request timeout in seconds. Defaults are generous
|
|
@@ -83,10 +92,12 @@ async def detect_secure(host: str, timeout: float = DETECT_TIMEOUT_SEC) -> bool:
|
|
|
83
92
|
except (ClientError, asyncio.TimeoutError):
|
|
84
93
|
pass
|
|
85
94
|
|
|
86
|
-
# Fall back to HTTPS.
|
|
95
|
+
# Fall back to HTTPS. 401 ("Authorization Missing") and 403
|
|
96
|
+
# ("Forbidden" — wrong token, or key not set) both mean the
|
|
97
|
+
# firmware is in secure mode; the caller now needs credentials.
|
|
87
98
|
try:
|
|
88
99
|
async with session.get(f"https://{host}/api/v1/summary") as resp:
|
|
89
|
-
if resp.status
|
|
100
|
+
if resp.status in (401, 403):
|
|
90
101
|
return True
|
|
91
102
|
except (ClientError, asyncio.TimeoutError):
|
|
92
103
|
pass
|
|
@@ -1004,14 +1004,27 @@ class SwidgetAssembly:
|
|
|
1004
1004
|
class SwidgetComponent:
|
|
1005
1005
|
"""Component-level representation of a Swidget Assembly.
|
|
1006
1006
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1007
|
+
``summary_functions`` is the immutable list of function tags the
|
|
1008
|
+
device declared in its summary — this is the schema. ``functions``
|
|
1009
|
+
starts as a same-keyed dict of placeholder ``None`` values and is
|
|
1010
|
+
later mutated by ``process_state`` to carry live datapoint values.
|
|
1011
|
+
|
|
1012
|
+
Process_state also leaks in keys that aren't in the summary
|
|
1013
|
+
functions list (e.g. fans emit a ``modules`` map in state that
|
|
1014
|
+
isn't a declared function tag), so ``functions.keys()`` is *not*
|
|
1015
|
+
schema-stable across summary refreshes. Anything that needs a
|
|
1016
|
+
stable schema fingerprint (entity wiring, structure-change
|
|
1017
|
+
detection) must read ``summary_functions``, not ``functions``.
|
|
1018
|
+
|
|
1019
|
+
``max_cfm``, ``model_code`` and ``modules`` come from the optional
|
|
1020
|
+
summary-level fields fan hosts emit alongside ``functions`` —
|
|
1021
|
+
non-fan components don't populate them.
|
|
1011
1022
|
"""
|
|
1012
1023
|
|
|
1013
1024
|
def __init__(self, component):
|
|
1014
|
-
|
|
1025
|
+
funcs = list(component.get("functions", []))
|
|
1026
|
+
self.summary_functions: tuple[str, ...] = tuple(funcs)
|
|
1027
|
+
self.functions = {f: None for f in funcs}
|
|
1015
1028
|
self.max_cfm = component.get("maxCFM")
|
|
1016
1029
|
self.model_code = component.get("code")
|
|
1017
1030
|
self.modules = list(component.get("modules", []))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|