python-swidget 1.4.1__tar.gz → 1.4.3__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.1 → python_swidget-1.4.3}/PKG-INFO +1 -1
- {python_swidget-1.4.1 → python_swidget-1.4.3}/pyproject.toml +1 -1
- {python_swidget-1.4.1 → python_swidget-1.4.3}/swidget/__init__.py +7 -1
- {python_swidget-1.4.1 → python_swidget-1.4.3}/swidget/discovery.py +42 -0
- {python_swidget-1.4.1 → python_swidget-1.4.3}/README.md +0 -0
- {python_swidget-1.4.1 → python_swidget-1.4.3}/swidget/cli.py +0 -0
- {python_swidget-1.4.1 → python_swidget-1.4.3}/swidget/exceptions.py +0 -0
- {python_swidget-1.4.1 → python_swidget-1.4.3}/swidget/provision.py +0 -0
- {python_swidget-1.4.1 → python_swidget-1.4.3}/swidget/py.typed +0 -0
- {python_swidget-1.4.1 → python_swidget-1.4.3}/swidget/swidgetdevice.py +0 -0
- {python_swidget-1.4.1 → python_swidget-1.4.3}/swidget/swidgetdimmer.py +0 -0
- {python_swidget-1.4.1 → python_swidget-1.4.3}/swidget/swidgetoutlet.py +0 -0
- {python_swidget-1.4.1 → python_swidget-1.4.3}/swidget/swidgetswitch.py +0 -0
- {python_swidget-1.4.1 → python_swidget-1.4.3}/swidget/swidgettimerswitch.py +0 -0
- {python_swidget-1.4.1 → python_swidget-1.4.3}/swidget/websocket.py +0 -0
|
@@ -14,7 +14,12 @@ to be handled by the user of the library.
|
|
|
14
14
|
|
|
15
15
|
from importlib_metadata import version
|
|
16
16
|
|
|
17
|
-
from swidget.discovery import
|
|
17
|
+
from swidget.discovery import (
|
|
18
|
+
SwidgetDiscoveredDevice,
|
|
19
|
+
detect_secure,
|
|
20
|
+
discover_devices,
|
|
21
|
+
discover_single,
|
|
22
|
+
)
|
|
18
23
|
from swidget.exceptions import SwidgetException
|
|
19
24
|
from swidget.provision import provision_wifi
|
|
20
25
|
from swidget.swidgetdevice import (
|
|
@@ -33,6 +38,7 @@ __version__ = version("python-swidget")
|
|
|
33
38
|
|
|
34
39
|
|
|
35
40
|
__all__ = [
|
|
41
|
+
"detect_secure",
|
|
36
42
|
"discover_devices",
|
|
37
43
|
"discover_single",
|
|
38
44
|
"provision_wifi",
|
|
@@ -6,6 +6,7 @@ from typing import Any, Type
|
|
|
6
6
|
from urllib.parse import urlparse
|
|
7
7
|
|
|
8
8
|
import ssdp
|
|
9
|
+
from aiohttp import ClientError, ClientSession, ClientTimeout, TCPConnector
|
|
9
10
|
|
|
10
11
|
from swidget.swidgetdevice import DeviceType, SwidgetDevice
|
|
11
12
|
|
|
@@ -17,10 +18,51 @@ from .swidgettimerswitch import SwidgetTimerSwitch
|
|
|
17
18
|
|
|
18
19
|
RESPONSE_SEC = 5
|
|
19
20
|
SWIDGET_ST = "urn:swidget:pico:1"
|
|
21
|
+
# Generous timeout: TLS handshakes on the device's MCU can take several
|
|
22
|
+
# seconds on first connection.
|
|
23
|
+
DETECT_TIMEOUT_SEC = 10
|
|
20
24
|
_LOGGER = logging.getLogger(__name__)
|
|
21
25
|
devices = dict()
|
|
22
26
|
|
|
23
27
|
|
|
28
|
+
async def detect_secure(host: str, timeout: float = DETECT_TIMEOUT_SEC) -> bool:
|
|
29
|
+
"""Determine whether a Swidget device requires HTTPS+auth.
|
|
30
|
+
|
|
31
|
+
Probes ``/api/v1/summary`` over HTTPS first (no credentials). A 403
|
|
32
|
+
response means the firmware enforces auth — the device is in secure
|
|
33
|
+
mode. If HTTPS is unreachable, falls back to HTTP; a 200 there means
|
|
34
|
+
the device is in plaintext mode.
|
|
35
|
+
|
|
36
|
+
:param host: Hostname or IP of the device.
|
|
37
|
+
:param timeout: Per-request timeout in seconds. Defaults are generous
|
|
38
|
+
because the device performs TLS termination on a low-power MCU
|
|
39
|
+
and the first handshake can be several seconds.
|
|
40
|
+
:return: True if the device requires HTTPS+auth, False if plaintext.
|
|
41
|
+
:raises SwidgetException: If neither protocol returns a usable response.
|
|
42
|
+
"""
|
|
43
|
+
timeout_obj = ClientTimeout(total=timeout)
|
|
44
|
+
connector = TCPConnector(ssl=False, force_close=True)
|
|
45
|
+
async with ClientSession(connector=connector, timeout=timeout_obj) as session:
|
|
46
|
+
# Try plaintext first: it's the cheap path (no TLS handshake on the
|
|
47
|
+
# MCU) and a 200 response unambiguously means plaintext mode.
|
|
48
|
+
try:
|
|
49
|
+
async with session.get(f"http://{host}/api/v1/summary") as resp:
|
|
50
|
+
if resp.status == 200:
|
|
51
|
+
return False
|
|
52
|
+
except (ClientError, asyncio.TimeoutError):
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
# Fall back to HTTPS. A 403 means the firmware is enforcing auth.
|
|
56
|
+
try:
|
|
57
|
+
async with session.get(f"https://{host}/api/v1/summary") as resp:
|
|
58
|
+
if resp.status == 403:
|
|
59
|
+
return True
|
|
60
|
+
except (ClientError, asyncio.TimeoutError):
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
raise SwidgetException(f"Could not reach Swidget device at {host}")
|
|
64
|
+
|
|
65
|
+
|
|
24
66
|
class SwidgetDiscoveredDevice:
|
|
25
67
|
"""Stub class to capture details about discovered devices."""
|
|
26
68
|
|
|
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
|