python-swidget 1.2.2__tar.gz → 1.2.4__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.2.2 → python_swidget-1.2.4}/PKG-INFO +3 -2
- {python_swidget-1.2.2 → python_swidget-1.2.4}/README.md +1 -1
- {python_swidget-1.2.2 → python_swidget-1.2.4}/pyproject.toml +2 -2
- {python_swidget-1.2.2 → python_swidget-1.2.4}/swidget/__init__.py +10 -5
- {python_swidget-1.2.2 → python_swidget-1.2.4}/swidget/cli.py +55 -43
- {python_swidget-1.2.2 → python_swidget-1.2.4}/swidget/discovery.py +35 -8
- {python_swidget-1.2.2 → python_swidget-1.2.4}/swidget/provision.py +57 -37
- {python_swidget-1.2.2 → python_swidget-1.2.4}/swidget/swidgetdevice.py +302 -95
- python_swidget-1.2.4/swidget/swidgetdimmer.py +73 -0
- python_swidget-1.2.4/swidget/swidgetoutlet.py +23 -0
- python_swidget-1.2.4/swidget/swidgetswitch.py +23 -0
- python_swidget-1.2.4/swidget/swidgettimerswitch.py +39 -0
- {python_swidget-1.2.2 → python_swidget-1.2.4}/swidget/websocket.py +39 -18
- python_swidget-1.2.2/swidget/swidgetdimmer.py +0 -50
- python_swidget-1.2.2/swidget/swidgetoutlet.py +0 -11
- python_swidget-1.2.2/swidget/swidgetswitch.py +0 -11
- python_swidget-1.2.2/swidget/swidgettimerswitch.py +0 -23
- {python_swidget-1.2.2 → python_swidget-1.2.4}/swidget/exceptions.py +0 -0
- {python_swidget-1.2.2 → python_swidget-1.2.4}/swidget/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-swidget
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.4
|
|
4
4
|
Summary: Python API for Swidget smart devices
|
|
5
5
|
Home-page: https://github.com/swidget/python-swidget
|
|
6
6
|
License: GPL-3.0-or-later
|
|
@@ -21,7 +21,7 @@ Requires-Dist: importlib-metadata
|
|
|
21
21
|
Requires-Dist: m2r (>=0,<1) ; extra == "docs"
|
|
22
22
|
Requires-Dist: mistune (<2.0.0) ; extra == "docs"
|
|
23
23
|
Requires-Dist: pydantic (>=1,<2)
|
|
24
|
-
Requires-Dist: requests (==2.
|
|
24
|
+
Requires-Dist: requests (==2.32.3)
|
|
25
25
|
Requires-Dist: sphinx (>=4,<5) ; extra == "docs"
|
|
26
26
|
Requires-Dist: sphinx_rtd_theme (>=0,<1) ; extra == "docs"
|
|
27
27
|
Requires-Dist: sphinxcontrib-programoutput (>=0,<1) ; extra == "docs"
|
|
@@ -52,3 +52,4 @@ A library to manage Swidget smart devices
|
|
|
52
52
|
dev.turn_on()
|
|
53
53
|
dev.close()
|
|
54
54
|
```
|
|
55
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "python-swidget"
|
|
3
|
-
version = "1.2.
|
|
3
|
+
version = "1.2.4"
|
|
4
4
|
description = "Python API for Swidget smart devices"
|
|
5
5
|
license = "GPL-3.0-or-later"
|
|
6
6
|
authors = ["Swidget"]
|
|
@@ -22,7 +22,7 @@ importlib-metadata = "*"
|
|
|
22
22
|
asyncclick = ">=8"
|
|
23
23
|
pydantic = "^1"
|
|
24
24
|
ssdp = "1.1.1"
|
|
25
|
-
requests = "2.
|
|
25
|
+
requests = "2.32.3"
|
|
26
26
|
types-requests = "2.31.0.6"
|
|
27
27
|
urllib3 = '1.26.5'
|
|
28
28
|
|
|
@@ -11,25 +11,30 @@ For device type specific actions `SwidgetDimmer`, `SwidgetOutlet`, or `SwidgetSw
|
|
|
11
11
|
Module-specific errors are raised as `SwidgetException` and are expected
|
|
12
12
|
to be handled by the user of the library.
|
|
13
13
|
"""
|
|
14
|
+
|
|
14
15
|
from importlib_metadata import version # type: ignore
|
|
15
16
|
|
|
16
|
-
from swidget.discovery import discover_devices, discover_single
|
|
17
|
+
from swidget.discovery import SwidgetDiscoveredDevice, discover_devices, discover_single
|
|
17
18
|
from swidget.exceptions import SwidgetException
|
|
18
19
|
from swidget.provision import provision_wifi
|
|
19
|
-
from swidget.swidgetdevice import
|
|
20
|
+
from swidget.swidgetdevice import (
|
|
21
|
+
DeviceType,
|
|
22
|
+
SwidgetAssembly,
|
|
23
|
+
SwidgetComponent,
|
|
24
|
+
SwidgetDevice,
|
|
25
|
+
)
|
|
20
26
|
from swidget.swidgetdimmer import SwidgetDimmer
|
|
21
27
|
from swidget.swidgetoutlet import SwidgetOutlet
|
|
22
28
|
from swidget.swidgetswitch import SwidgetSwitch
|
|
23
29
|
from swidget.swidgettimerswitch import SwidgetTimerSwitch
|
|
24
30
|
|
|
25
|
-
|
|
26
31
|
__version__ = version("python-swidget")
|
|
27
32
|
|
|
28
33
|
|
|
29
34
|
__all__ = [
|
|
30
35
|
"discover_devices",
|
|
31
36
|
"discover_single",
|
|
32
|
-
"provision_wifi"
|
|
37
|
+
"provision_wifi",
|
|
33
38
|
"SwidgetDiscoveredDevice",
|
|
34
39
|
"SwidgetException",
|
|
35
40
|
"DeviceType",
|
|
@@ -40,4 +45,4 @@ __all__ = [
|
|
|
40
45
|
"SwidgetOutlet",
|
|
41
46
|
"SwidgetSwitch",
|
|
42
47
|
"SwidgetTimerSwitch",
|
|
43
|
-
]
|
|
48
|
+
]
|
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
"""python-swidget cli tool."""
|
|
2
|
+
|
|
2
3
|
import logging
|
|
3
4
|
import sys
|
|
4
|
-
from
|
|
5
|
+
from contextlib import asynccontextmanager
|
|
5
6
|
from pprint import pformat as pf
|
|
7
|
+
from typing import Any, cast
|
|
6
8
|
|
|
7
9
|
import asyncclick as click
|
|
8
|
-
from contextlib import asynccontextmanager
|
|
9
10
|
|
|
10
11
|
from swidget import (
|
|
11
|
-
discover_devices,
|
|
12
|
-
discover_single,
|
|
13
|
-
provision_wifi,
|
|
14
12
|
SwidgetDevice,
|
|
15
13
|
SwidgetDimmer,
|
|
16
|
-
SwidgetSwitch,
|
|
17
14
|
SwidgetOutlet,
|
|
18
|
-
|
|
15
|
+
SwidgetSwitch,
|
|
16
|
+
SwidgetTimerSwitch,
|
|
17
|
+
discover_devices,
|
|
18
|
+
discover_single,
|
|
19
|
+
provision_wifi,
|
|
19
20
|
)
|
|
20
21
|
|
|
21
22
|
TYPE_TO_CLASS = {
|
|
22
23
|
"dimmer": SwidgetDimmer,
|
|
23
24
|
"switch": SwidgetSwitch,
|
|
24
25
|
"outlet": SwidgetOutlet,
|
|
25
|
-
"pana_switch": SwidgetTimerSwitch
|
|
26
|
+
"pana_switch": SwidgetTimerSwitch,
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
|
|
@@ -30,24 +31,23 @@ pass_dev = click.make_pass_decorator(SwidgetDevice)
|
|
|
30
31
|
|
|
31
32
|
|
|
32
33
|
@click.group(invoke_without_command=True)
|
|
33
|
-
@click.option(
|
|
34
|
+
@click.option(
|
|
35
|
+
"--host",
|
|
34
36
|
envvar="SWIDGET_HOST",
|
|
35
37
|
required=False,
|
|
36
38
|
help="The host name or IP address of the device to connect to.",
|
|
37
39
|
)
|
|
38
|
-
@click.option(
|
|
40
|
+
@click.option(
|
|
41
|
+
"-p",
|
|
42
|
+
"--password",
|
|
39
43
|
envvar="SWIDGET_PASSWORD",
|
|
40
44
|
required=False,
|
|
41
45
|
help="The password of the device to connect to.",
|
|
42
46
|
)
|
|
43
|
-
@click.option("-d", "--debug",
|
|
44
|
-
|
|
45
|
-
default=
|
|
46
|
-
|
|
47
|
-
@click.option("--http_only",
|
|
48
|
-
envvar="SWIDGET_HTTP_ONLY",
|
|
49
|
-
default=True,
|
|
50
|
-
is_flag=True)
|
|
47
|
+
@click.option("-d", "--debug", envvar="SWIDGET_DEBUG", default=False, is_flag=True)
|
|
48
|
+
@click.option(
|
|
49
|
+
"-ho", "--http_only", envvar="SWIDGET_HTTP_ONLY", default=True, is_flag=True
|
|
50
|
+
)
|
|
51
51
|
@click.option(
|
|
52
52
|
"--type",
|
|
53
53
|
envvar="SWIDGET_TYPE",
|
|
@@ -74,18 +74,22 @@ async def cli(ctx, host, password, debug, http_only, type):
|
|
|
74
74
|
await ctx.invoke(discover)
|
|
75
75
|
return
|
|
76
76
|
if type is not None:
|
|
77
|
-
dev = TYPE_TO_CLASS[type](
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
dev = TYPE_TO_CLASS[type](
|
|
78
|
+
host=host,
|
|
79
|
+
token_name="x-secret-key",
|
|
80
|
+
secret_key=password,
|
|
81
|
+
use_https=http_only,
|
|
82
|
+
use_websockets=False,
|
|
83
|
+
)
|
|
82
84
|
else:
|
|
83
85
|
click.echo("No --type defined, discovering...")
|
|
84
|
-
dev = await discover_single(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
dev = await discover_single(
|
|
87
|
+
host=host,
|
|
88
|
+
token_name="x-secret-key",
|
|
89
|
+
password=password,
|
|
90
|
+
use_https=http_only,
|
|
91
|
+
use_websockets=False,
|
|
92
|
+
)
|
|
89
93
|
await dev.update()
|
|
90
94
|
|
|
91
95
|
@asynccontextmanager
|
|
@@ -113,11 +117,15 @@ def wifi():
|
|
|
113
117
|
@click.option("--friendly_name", prompt=True, hide_input=False)
|
|
114
118
|
def join(ssid, network_password, secret_key, friendly_name):
|
|
115
119
|
"""Join the given wifi network."""
|
|
116
|
-
confirmation = click.prompt(
|
|
120
|
+
confirmation = click.prompt(
|
|
121
|
+
"Are you connected to a wifi network that stars with the name 'Swidget-' (y/n)"
|
|
122
|
+
)
|
|
117
123
|
if confirmation == "y":
|
|
118
124
|
click.echo(f"Asking the device to connect to network {ssid}..")
|
|
119
125
|
provision_wifi(friendly_name, ssid, network_password, secret_key)
|
|
120
|
-
click.echo(
|
|
126
|
+
click.echo(
|
|
127
|
+
"Disconnect from the 'swidget' network and connect back your main WiFi network"
|
|
128
|
+
)
|
|
121
129
|
return True
|
|
122
130
|
else:
|
|
123
131
|
click.echo("Not provisioning wifi")
|
|
@@ -135,6 +143,7 @@ async def discover(ctx, timeout):
|
|
|
135
143
|
for device in devices.values():
|
|
136
144
|
click.echo(f"{device.host}[{device.mac}] - {device.friendly_name}")
|
|
137
145
|
|
|
146
|
+
|
|
138
147
|
@cli.command()
|
|
139
148
|
@pass_dev
|
|
140
149
|
async def hwinfo(dev):
|
|
@@ -149,7 +158,7 @@ async def hwinfo(dev):
|
|
|
149
158
|
async def state(dev: SwidgetDevice):
|
|
150
159
|
"""Print out device state and versions."""
|
|
151
160
|
click.echo(click.style(f"== {dev.friendly_name} - {dev.model} ==", bold=True))
|
|
152
|
-
click.echo(f"\tFriendly Name:
|
|
161
|
+
click.echo(f"\tFriendly Name: {dev.friendly_name}")
|
|
153
162
|
click.echo(f"\tHost: {dev.ip_address}")
|
|
154
163
|
click.echo(
|
|
155
164
|
click.style(
|
|
@@ -159,9 +168,9 @@ async def state(dev: SwidgetDevice):
|
|
|
159
168
|
)
|
|
160
169
|
|
|
161
170
|
click.echo(click.style("\t== Generic information ==", bold=True))
|
|
162
|
-
click.echo(f"\tHardware:
|
|
163
|
-
click.echo(f"\tSoftware:
|
|
164
|
-
click.echo(f"\tMAC (rssi):
|
|
171
|
+
click.echo(f"\tHardware: {dev.hw_info['model']}")
|
|
172
|
+
click.echo(f"\tSoftware: {dev.hw_info['version']}")
|
|
173
|
+
click.echo(f"\tMAC (rssi): {dev.mac_address} ({dev.rssi})")
|
|
165
174
|
|
|
166
175
|
click.echo(click.style("\n\t== Current State ==", bold=True))
|
|
167
176
|
realtime_values = dev.realtime_values
|
|
@@ -177,7 +186,6 @@ async def state(dev: SwidgetDevice):
|
|
|
177
186
|
for function in dev.host_features:
|
|
178
187
|
click.echo(click.style(f"\t+ {function}", fg="green"))
|
|
179
188
|
|
|
180
|
-
|
|
181
189
|
click.echo(click.style("\n\t== Insert Features ==", bold=True))
|
|
182
190
|
for function in dev.insert_features:
|
|
183
191
|
click.echo(click.style(f"\t+ {function}", fg="green"))
|
|
@@ -198,7 +206,8 @@ async def raw_command(dev: SwidgetDevice, assembly, component, function, command
|
|
|
198
206
|
@cli.command()
|
|
199
207
|
@click.argument("brightness", type=click.IntRange(0, 100), default=None, required=False)
|
|
200
208
|
@pass_dev
|
|
201
|
-
async def brightness(dev: SwidgetDevice, brightness: Any=None):
|
|
209
|
+
async def brightness(dev: SwidgetDevice, brightness: Any = None):
|
|
210
|
+
"""Get or set the brightness of a dimmer device."""
|
|
202
211
|
dimmer_dev = cast(SwidgetDimmer, dev)
|
|
203
212
|
"""Get or set brightness."""
|
|
204
213
|
if not dimmer_dev.is_dimmer:
|
|
@@ -215,25 +224,26 @@ async def brightness(dev: SwidgetDevice, brightness: Any=None):
|
|
|
215
224
|
@cli.command()
|
|
216
225
|
@pass_dev
|
|
217
226
|
async def blink(dev):
|
|
218
|
-
"""Set the device insert to blink"""
|
|
219
|
-
click.echo(
|
|
227
|
+
"""Set the device insert to blink."""
|
|
228
|
+
click.echo("Requesting the device to blink")
|
|
220
229
|
return await dev.blink()
|
|
221
230
|
|
|
222
231
|
|
|
223
232
|
@cli.command()
|
|
224
233
|
@pass_dev
|
|
225
234
|
async def ping(dev):
|
|
226
|
-
"""Ping the device"""
|
|
227
|
-
click.echo(
|
|
235
|
+
"""Ping the device."""
|
|
236
|
+
click.echo("Pinging the device")
|
|
228
237
|
try:
|
|
229
238
|
result = await dev.ping()
|
|
230
239
|
if result == 200:
|
|
231
240
|
click.echo("Successfully pinged device")
|
|
232
241
|
else:
|
|
233
242
|
click.echo(result.status_code)
|
|
234
|
-
except:
|
|
243
|
+
except Exception:
|
|
235
244
|
click.echo("Unable to ping device")
|
|
236
245
|
|
|
246
|
+
|
|
237
247
|
@cli.command()
|
|
238
248
|
@pass_dev
|
|
239
249
|
async def on(dev: SwidgetDevice):
|
|
@@ -253,14 +263,15 @@ async def off(dev: SwidgetDevice):
|
|
|
253
263
|
@cli.command()
|
|
254
264
|
@pass_dev
|
|
255
265
|
async def enable_debug_server(dev: SwidgetDevice):
|
|
256
|
-
"""Enable Debug Server"""
|
|
257
|
-
click.echo(
|
|
266
|
+
"""Enable Debug Server."""
|
|
267
|
+
click.echo("Enabling debug server")
|
|
258
268
|
return await dev.enable_debug_server()
|
|
259
269
|
|
|
260
270
|
|
|
261
271
|
@cli.command()
|
|
262
272
|
@pass_dev
|
|
263
273
|
async def check_for_updates(dev: SwidgetDevice):
|
|
274
|
+
"""Connect to Swidget Cloud to see if there are any updates available for the insert."""
|
|
264
275
|
click.echo("Contacting Swidget servers to fetch for updates...")
|
|
265
276
|
available_updates = await dev.check_for_updates()
|
|
266
277
|
if len(available_updates) == 0:
|
|
@@ -275,6 +286,7 @@ async def check_for_updates(dev: SwidgetDevice):
|
|
|
275
286
|
@click.option("--version", required=False)
|
|
276
287
|
@pass_dev
|
|
277
288
|
async def upgrade(dev: SwidgetDevice, version: str):
|
|
289
|
+
"""Update the device to a newer version."""
|
|
278
290
|
if version is None:
|
|
279
291
|
click.echo("Contacting Swidget servers to fetch for latest version")
|
|
280
292
|
available_updates = await dev.check_for_updates()
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"""Module to discover Swidget devices."""
|
|
1
2
|
import asyncio
|
|
2
3
|
import logging
|
|
3
4
|
import socket
|
|
@@ -7,11 +8,12 @@ from urllib.parse import urlparse
|
|
|
7
8
|
import ssdp # type: ignore
|
|
8
9
|
|
|
9
10
|
from swidget.swidgetdevice import DeviceType, SwidgetDevice
|
|
11
|
+
|
|
12
|
+
from .exceptions import SwidgetException
|
|
10
13
|
from .swidgetdimmer import SwidgetDimmer
|
|
11
14
|
from .swidgetoutlet import SwidgetOutlet
|
|
12
15
|
from .swidgetswitch import SwidgetSwitch
|
|
13
16
|
from .swidgettimerswitch import SwidgetTimerSwitch
|
|
14
|
-
from .exceptions import SwidgetException
|
|
15
17
|
|
|
16
18
|
RESPONSE_SEC = 5
|
|
17
19
|
SWIDGET_ST = "urn:swidget:pico:1"
|
|
@@ -20,16 +22,28 @@ devices = dict()
|
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
class SwidgetDiscoveredDevice:
|
|
23
|
-
|
|
25
|
+
"""Stub class to capture details about discovered devices."""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
mac: str,
|
|
30
|
+
host: str,
|
|
31
|
+
host_type: str,
|
|
32
|
+
insert_type: str,
|
|
33
|
+
friendly_name: str = "Swidget Discovered Device",
|
|
34
|
+
):
|
|
24
35
|
self.mac = mac
|
|
25
36
|
self.host = host
|
|
26
37
|
self.friendly_name = friendly_name
|
|
38
|
+
self.host_type = host_type
|
|
39
|
+
self.insert_type = insert_type
|
|
27
40
|
|
|
28
41
|
|
|
29
42
|
class SwidgetProtocol(ssdp.SimpleServiceDiscoveryProtocol):
|
|
30
43
|
"""Protocol to handle responses and requests."""
|
|
44
|
+
|
|
31
45
|
def response_received(self, response: ssdp.SSDPResponse, addr: tuple):
|
|
32
|
-
"Handle an incoming response."
|
|
46
|
+
"""Handle an incoming response."""
|
|
33
47
|
headers = {h[0]: h[1] for h in response.headers}
|
|
34
48
|
mac_address = headers["USN"].split("-")[-1]
|
|
35
49
|
ip_address = urlparse(headers["LOCATION"]).hostname
|
|
@@ -37,11 +51,20 @@ class SwidgetProtocol(ssdp.SimpleServiceDiscoveryProtocol):
|
|
|
37
51
|
device_type = headers["SERVER"].split(" ")[1].split("+")[0]
|
|
38
52
|
insert_type = headers["SERVER"].split(" ")[1].split("+")[1].split("/")[0]
|
|
39
53
|
friendly_name = headers["SERVER"].split("/")[2].strip('"')
|
|
40
|
-
devices[mac_address] = SwidgetDiscoveredDevice(
|
|
41
|
-
|
|
54
|
+
devices[mac_address] = SwidgetDiscoveredDevice(
|
|
55
|
+
mac=mac_address,
|
|
56
|
+
host=ip_address,
|
|
57
|
+
friendly_name=friendly_name,
|
|
58
|
+
host_type=device_type,
|
|
59
|
+
insert_type=insert_type,
|
|
60
|
+
)
|
|
61
|
+
_LOGGER.debug(
|
|
62
|
+
f"Swidget device '{friendly_name}' at {ip_address}. {device_type}/{insert_type}"
|
|
63
|
+
)
|
|
42
64
|
|
|
43
65
|
|
|
44
66
|
async def discover_devices(timeout=RESPONSE_SEC):
|
|
67
|
+
"""Discover devices via SSDP."""
|
|
45
68
|
global devices
|
|
46
69
|
loop = asyncio.get_event_loop()
|
|
47
70
|
devices = dict()
|
|
@@ -64,7 +87,9 @@ async def discover_devices(timeout=RESPONSE_SEC):
|
|
|
64
87
|
return devices
|
|
65
88
|
|
|
66
89
|
|
|
67
|
-
async def discover_single(
|
|
90
|
+
async def discover_single(
|
|
91
|
+
host: str, token_name: str, password: str, use_https: bool, use_websockets: bool
|
|
92
|
+
) -> Any:
|
|
68
93
|
"""Discover a single device by the given IP address.
|
|
69
94
|
|
|
70
95
|
:param host: Hostname of device to query
|
|
@@ -72,7 +97,9 @@ async def discover_single(host: str, token_name: str, password: str, use_https:
|
|
|
72
97
|
:return: Object for querying/controlling found device.
|
|
73
98
|
"""
|
|
74
99
|
_LOGGER.debug(f"Checking for device at {host}")
|
|
75
|
-
swidget_device = SwidgetDevice(
|
|
100
|
+
swidget_device = SwidgetDevice(
|
|
101
|
+
host, token_name, password, use_https, use_websockets=False
|
|
102
|
+
)
|
|
76
103
|
_LOGGER.debug(f"Asking {host} for summary data")
|
|
77
104
|
await swidget_device.get_summary()
|
|
78
105
|
device_type = swidget_device.device_type
|
|
@@ -95,7 +122,7 @@ def _get_device_class(device_type: DeviceType) -> Type[SwidgetDevice]:
|
|
|
95
122
|
return SwidgetSwitch
|
|
96
123
|
elif device_type == DeviceType.Dimmer:
|
|
97
124
|
return SwidgetDimmer
|
|
98
|
-
elif device_type == DeviceType.TimerSwitch:
|
|
125
|
+
elif device_type == DeviceType.TimerSwitch: # This is the timer switch
|
|
99
126
|
return SwidgetTimerSwitch
|
|
100
127
|
elif device_type == DeviceType.RelaySwitch:
|
|
101
128
|
return SwidgetSwitch
|
|
@@ -1,42 +1,48 @@
|
|
|
1
|
-
|
|
1
|
+
"""Provision the Swidget device in local-only mode."""
|
|
2
2
|
import time
|
|
3
|
+
from enum import Enum
|
|
3
4
|
|
|
4
|
-
import requests
|
|
5
|
+
import requests # type: ignore
|
|
5
6
|
import urllib3
|
|
6
7
|
|
|
7
8
|
urllib3.disable_warnings()
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class DeviceConnectionResult(str, Enum):
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
"""Enum class for provisioning results."""
|
|
13
|
+
|
|
14
|
+
NoStarted = ("NotInitiated",)
|
|
15
|
+
InProgress = ("AttemptingConnect",)
|
|
16
|
+
Success = ("Success",)
|
|
17
|
+
AuthenticationFailure = ("AuthFail",)
|
|
18
|
+
SSIDNotFound = ("SSIDNotFound",)
|
|
19
|
+
NoIp = ("NoIpReceived",)
|
|
17
20
|
ConnectionFailure = "FailedToConnect"
|
|
18
21
|
|
|
19
22
|
|
|
20
23
|
def send_credentials(device_name, ssid, network_password, secret_key):
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
"""Send the credentials to the Swidget device."""
|
|
25
|
+
payload = {
|
|
26
|
+
"name": device_name,
|
|
27
|
+
"ssid": ssid,
|
|
28
|
+
"password": network_password,
|
|
29
|
+
"secretKey": secret_key,
|
|
30
|
+
}
|
|
25
31
|
url = "https://10.123.45.1/network"
|
|
26
32
|
sentProvisionRequestAttempts = 0
|
|
27
33
|
|
|
28
34
|
while True:
|
|
29
35
|
print(f"Provision Attempt: {sentProvisionRequestAttempts}")
|
|
30
36
|
if sentProvisionRequestAttempts >= 5:
|
|
31
|
-
print(
|
|
37
|
+
print("Provision failed, Ensure you're connected to the device's hotspot.")
|
|
32
38
|
return False, None
|
|
33
39
|
try:
|
|
34
40
|
initial_provision = requests.post(url, json=payload, verify=False)
|
|
35
41
|
if initial_provision.status_code == 200:
|
|
36
|
-
print(
|
|
42
|
+
print("Provision Success")
|
|
37
43
|
return True, initial_provision.json()["secretKey"]
|
|
38
44
|
else:
|
|
39
|
-
print(
|
|
45
|
+
print("Provision Failed: {initial_provision.json()}")
|
|
40
46
|
return False, None
|
|
41
47
|
except Exception as e:
|
|
42
48
|
print("Error", e)
|
|
@@ -45,61 +51,73 @@ def send_credentials(device_name, ssid, network_password, secret_key):
|
|
|
45
51
|
|
|
46
52
|
|
|
47
53
|
def verify_connect_result(key):
|
|
48
|
-
|
|
49
|
-
|
|
54
|
+
"""Function to query the device and verify if the device is successfully provisioned."""
|
|
55
|
+
headers = {"x-secret-key": key}
|
|
50
56
|
connect_verification_attempts = 0
|
|
51
|
-
while
|
|
57
|
+
while connect_verification_attempts < 5:
|
|
52
58
|
url = "https://10.123.45.1/network"
|
|
53
|
-
if connect_verification_attempts >= 5:
|
|
54
|
-
return False, None, None, "Failed to connect to device"
|
|
55
59
|
try:
|
|
56
60
|
verify_connection = requests.get(url, headers=headers, verify=False)
|
|
57
61
|
print(f"Verify Response: {verify_connection.json()}")
|
|
58
62
|
if verify_connection.status_code == 200:
|
|
59
63
|
print(f"Verification request success: {verify_connection.json()}")
|
|
60
64
|
connect_status = verify_connection.json()["status"]
|
|
61
|
-
if connect_status ==
|
|
62
|
-
return
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
if connect_status == "Success":
|
|
66
|
+
return (
|
|
67
|
+
True,
|
|
68
|
+
verify_connection.json()["ip"],
|
|
69
|
+
verify_connection.json()["mac"],
|
|
70
|
+
None,
|
|
71
|
+
)
|
|
72
|
+
elif connect_status == "InProgress":
|
|
73
|
+
time.sleep(5)
|
|
74
|
+
connect_verification_attempts += 1
|
|
75
|
+
continue
|
|
65
76
|
else:
|
|
66
77
|
return False, None, None, connect_status
|
|
67
78
|
else:
|
|
68
79
|
print(f"Verification request failure: {verify_connection.json()}")
|
|
80
|
+
time.sleep(5)
|
|
81
|
+
except Exception as e:
|
|
82
|
+
print(e)
|
|
69
83
|
time.sleep(5)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
connect_verification_attempts += 1
|
|
84
|
+
connect_verification_attempts += 1
|
|
85
|
+
return False, None, None, "Internal server error"
|
|
73
86
|
|
|
74
87
|
|
|
75
88
|
def provision_wifi(device_name, ssid, network_password, secret_key):
|
|
89
|
+
"""Main function to provision credentials to the Swidget device."""
|
|
76
90
|
print(f"Device_name: {device_name}")
|
|
77
91
|
print(f"SSID: {ssid}")
|
|
78
|
-
print(
|
|
79
|
-
print(
|
|
92
|
+
print("Network Password: <redacted>")
|
|
93
|
+
print("Swidget Secret Key: <redacted>")
|
|
80
94
|
|
|
81
|
-
send_success, key = send_credentials(
|
|
95
|
+
send_success, key = send_credentials(
|
|
96
|
+
device_name, ssid, network_password, secret_key
|
|
97
|
+
)
|
|
82
98
|
if not send_success:
|
|
83
99
|
return False
|
|
84
|
-
secret_key = key
|
|
100
|
+
secret_key = key # we set the key to whatever was returned from the device. this handles the case of an empty string (which causes insert to generate a key)
|
|
85
101
|
|
|
86
102
|
verify_success, ip, mac, errorMessage = verify_connect_result(secret_key)
|
|
87
103
|
if not verify_success:
|
|
88
104
|
print(f"Verify Error: {errorMessage}")
|
|
89
105
|
return False
|
|
90
106
|
print(f"Verified Connection: {ip} {mac}")
|
|
91
|
-
headers = {
|
|
107
|
+
headers = {"x-secret-key": secret_key}
|
|
92
108
|
# the device will remain in AP mode for 60 seconds before switching over to control mode. This is to allow time to send requests to update the device
|
|
93
109
|
# without swiching networks.
|
|
94
110
|
|
|
95
111
|
# issuing the setup_complete request will skip the 60s time
|
|
96
|
-
complete_setup = requests.get(
|
|
112
|
+
complete_setup = requests.get(
|
|
113
|
+
"https://10.123.45.1/setup_complete", headers=headers, verify=False
|
|
114
|
+
)
|
|
97
115
|
if complete_setup.status_code == 200:
|
|
98
116
|
print("Setup complete, enabled control server")
|
|
99
117
|
else:
|
|
100
118
|
return False
|
|
101
119
|
|
|
102
|
-
#alternatively, sleep for some time
|
|
120
|
+
# alternatively, sleep for some time
|
|
103
121
|
print("Device has been configured, switch your wifi network off `Swidget-` now")
|
|
104
122
|
time.sleep(30)
|
|
105
123
|
|
|
@@ -109,8 +127,10 @@ def provision_wifi(device_name, ssid, network_password, secret_key):
|
|
|
109
127
|
try:
|
|
110
128
|
url = f"https://{ip}/api/v1/name"
|
|
111
129
|
verify_name = requests.get(url, headers=headers, verify=False).json()
|
|
112
|
-
print(
|
|
130
|
+
print(
|
|
131
|
+
f"Verifying device name has been set: {verify_name}. Provisioning complete"
|
|
132
|
+
)
|
|
113
133
|
return True
|
|
114
|
-
except:
|
|
134
|
+
except Exception:
|
|
115
135
|
print("Connect to provided network")
|
|
116
|
-
time.sleep(5)
|
|
136
|
+
time.sleep(5)
|