python-swidget 1.4.12__tar.gz → 1.4.14__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.12 → python_swidget-1.4.14}/PKG-INFO +1 -1
- {python_swidget-1.4.12 → python_swidget-1.4.14}/pyproject.toml +1 -1
- {python_swidget-1.4.12 → python_swidget-1.4.14}/swidget/discovery.py +50 -15
- {python_swidget-1.4.12 → python_swidget-1.4.14}/swidget/swidgetdevice.py +20 -0
- {python_swidget-1.4.12 → python_swidget-1.4.14}/README.md +0 -0
- {python_swidget-1.4.12 → python_swidget-1.4.14}/swidget/__init__.py +0 -0
- {python_swidget-1.4.12 → python_swidget-1.4.14}/swidget/cli.py +0 -0
- {python_swidget-1.4.12 → python_swidget-1.4.14}/swidget/exceptions.py +0 -0
- {python_swidget-1.4.12 → python_swidget-1.4.14}/swidget/provision.py +0 -0
- {python_swidget-1.4.12 → python_swidget-1.4.14}/swidget/py.typed +0 -0
- {python_swidget-1.4.12 → python_swidget-1.4.14}/swidget/swidgetdimmer.py +0 -0
- {python_swidget-1.4.12 → python_swidget-1.4.14}/swidget/swidgetoutlet.py +0 -0
- {python_swidget-1.4.12 → python_swidget-1.4.14}/swidget/swidgetswitch.py +0 -0
- {python_swidget-1.4.12 → python_swidget-1.4.14}/swidget/swidgettimerswitch.py +0 -0
- {python_swidget-1.4.12 → python_swidget-1.4.14}/swidget/websocket.py +0 -0
|
@@ -18,6 +18,36 @@ from .swidgettimerswitch import SwidgetTimerSwitch
|
|
|
18
18
|
|
|
19
19
|
RESPONSE_SEC = 5
|
|
20
20
|
SWIDGET_STS = ("urn:swidget:pico:1", "urn:swidget:video:1")
|
|
21
|
+
|
|
22
|
+
# Per-ST raw device-id length (in hex chars) before any UUID padding. The
|
|
23
|
+
# firmware embeds the device id in the SSDP USN differently per family:
|
|
24
|
+
# pico uses a fixed UUID prefix + the 12-char id as the last segment;
|
|
25
|
+
# video pads the 24-char id with zeros to fit a 32-char UUID. Both can
|
|
26
|
+
# be recovered as long as we know the original length per ST.
|
|
27
|
+
_USN_DEVICE_ID_LENGTH = {
|
|
28
|
+
"urn:swidget:pico:1": 12,
|
|
29
|
+
"urn:swidget:video:1": 24,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def device_id_from_ssdp(usn: str, st: str) -> Optional[str]:
|
|
34
|
+
"""Extract the canonical device id from an SSDP USN/ST pair.
|
|
35
|
+
|
|
36
|
+
Returns None when the inputs aren't recognized — callers should treat
|
|
37
|
+
that as "skip this discovery" rather than synthesizing an id.
|
|
38
|
+
"""
|
|
39
|
+
if not usn or not usn.startswith("uuid:") or st not in _USN_DEVICE_ID_LENGTH:
|
|
40
|
+
return None
|
|
41
|
+
uuid_part = usn[len("uuid:") :]
|
|
42
|
+
expected_len = _USN_DEVICE_ID_LENGTH[st]
|
|
43
|
+
if st == "urn:swidget:pico:1":
|
|
44
|
+
# Last hyphenated segment carries the real 12-char MAC.
|
|
45
|
+
last = uuid_part.rsplit("-", 1)[-1]
|
|
46
|
+
return last if len(last) == expected_len else None
|
|
47
|
+
# Video (and any future variant that pads): dehyphenate, take the
|
|
48
|
+
# leading expected_len chars, ignore the trailing zero padding.
|
|
49
|
+
flat = uuid_part.replace("-", "")
|
|
50
|
+
return flat[:expected_len] if len(flat) >= expected_len else None
|
|
21
51
|
# Generous timeout: TLS handshakes on the device's MCU can take several
|
|
22
52
|
# seconds on first connection.
|
|
23
53
|
DETECT_TIMEOUT_SEC = 10
|
|
@@ -89,22 +119,27 @@ class SwidgetProtocol(ssdp.SimpleServiceDiscoveryProtocol):
|
|
|
89
119
|
def response_received(self, response: ssdp.SSDPResponse, addr: tuple):
|
|
90
120
|
"""Handle an incoming response."""
|
|
91
121
|
headers = {h[0]: h[1] for h in response.headers}
|
|
92
|
-
|
|
122
|
+
st = headers.get("ST", "")
|
|
123
|
+
if st not in SWIDGET_STS:
|
|
124
|
+
return
|
|
125
|
+
device_id = device_id_from_ssdp(headers.get("USN", ""), st)
|
|
126
|
+
if not device_id:
|
|
127
|
+
_LOGGER.debug("Skipping SSDP response with unparseable USN: %s", headers)
|
|
128
|
+
return
|
|
93
129
|
ip_address = urlparse(headers["LOCATION"]).hostname
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
)
|
|
130
|
+
device_type = headers["SERVER"].split(" ")[1].split("+")[0]
|
|
131
|
+
insert_type = headers["SERVER"].split(" ")[1].split("+")[1].split("/")[0]
|
|
132
|
+
friendly_name = headers["SERVER"].split("/")[2].strip('"')
|
|
133
|
+
devices[device_id] = SwidgetDiscoveredDevice(
|
|
134
|
+
mac=device_id,
|
|
135
|
+
host=ip_address,
|
|
136
|
+
friendly_name=friendly_name,
|
|
137
|
+
host_type=device_type,
|
|
138
|
+
insert_type=insert_type,
|
|
139
|
+
)
|
|
140
|
+
_LOGGER.debug(
|
|
141
|
+
f"Discovered Swidget device via SSDP: '{friendly_name}' at {ip_address} Type:{device_type}/{insert_type}"
|
|
142
|
+
)
|
|
108
143
|
|
|
109
144
|
|
|
110
145
|
async def discover_devices(timeout=RESPONSE_SEC):
|
|
@@ -924,6 +924,26 @@ class SwidgetDevice:
|
|
|
924
924
|
{"insert": {"components": {"video": {"rtsp": {"enable": bool(enabled)}}}}}
|
|
925
925
|
)
|
|
926
926
|
|
|
927
|
+
@property
|
|
928
|
+
def rtsp_stream_source(self) -> Optional[str]:
|
|
929
|
+
"""Return the device's RTSP URL, or None when unavailable.
|
|
930
|
+
|
|
931
|
+
Path ``/ph264`` is fixed by firmware; the port comes from device
|
|
932
|
+
config. None if RTSP is disabled, the insert isn't video, or the
|
|
933
|
+
config hasn't been loaded yet.
|
|
934
|
+
"""
|
|
935
|
+
if self.rtsp_enabled is not True:
|
|
936
|
+
return None
|
|
937
|
+
try:
|
|
938
|
+
port = int(
|
|
939
|
+
self.device_config.config["insert"]["components"]["video"]["rtsp"][
|
|
940
|
+
"port"
|
|
941
|
+
]
|
|
942
|
+
)
|
|
943
|
+
except (KeyError, TypeError, ValueError):
|
|
944
|
+
return None
|
|
945
|
+
return f"rtsp://{self.ip_address}:{port}/ph264"
|
|
946
|
+
|
|
927
947
|
async def __aenter__(self) -> "SwidgetDevice":
|
|
928
948
|
"""Initialize and connect the Swidget Websocket client."""
|
|
929
949
|
await self.connect()
|
|
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
|