python-swidget 1.0.0__tar.gz → 1.0.2__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.0.0 → python_swidget-1.0.2}/PKG-INFO +3 -1
- {python_swidget-1.0.0 → python_swidget-1.0.2}/pyproject.toml +3 -1
- {python_swidget-1.0.0 → python_swidget-1.0.2}/swidget/cli.py +10 -18
- {python_swidget-1.0.0 → python_swidget-1.0.2}/swidget/discovery.py +10 -14
- {python_swidget-1.0.0 → python_swidget-1.0.2}/swidget/swidgetdevice.py +52 -49
- {python_swidget-1.0.0 → python_swidget-1.0.2}/swidget/swidgetdimmer.py +2 -2
- {python_swidget-1.0.0 → python_swidget-1.0.2}/swidget/swidgetswitch.py +1 -5
- {python_swidget-1.0.0 → python_swidget-1.0.2}/swidget/swidgettimerswitch.py +3 -1
- {python_swidget-1.0.0 → python_swidget-1.0.2}/swidget/websocket.py +37 -11
- {python_swidget-1.0.0 → python_swidget-1.0.2}/README.md +0 -0
- {python_swidget-1.0.0 → python_swidget-1.0.2}/swidget/__init__.py +0 -0
- {python_swidget-1.0.0 → python_swidget-1.0.2}/swidget/exceptions.py +0 -0
- {python_swidget-1.0.0 → python_swidget-1.0.2}/swidget/provision.py +0 -0
- {python_swidget-1.0.0 → python_swidget-1.0.2}/swidget/py.typed +0 -0
- {python_swidget-1.0.0 → python_swidget-1.0.2}/swidget/swidgetoutlet.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-swidget
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
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,10 +21,12 @@ 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.31.0)
|
|
24
25
|
Requires-Dist: sphinx (>=4,<5) ; extra == "docs"
|
|
25
26
|
Requires-Dist: sphinx_rtd_theme (>=0,<1) ; extra == "docs"
|
|
26
27
|
Requires-Dist: sphinxcontrib-programoutput (>=0,<1) ; extra == "docs"
|
|
27
28
|
Requires-Dist: ssdp (==1.1.1)
|
|
29
|
+
Requires-Dist: types-requests (==2.31.0.20240406)
|
|
28
30
|
Project-URL: Repository, https://github.com/swidget/python-swidget
|
|
29
31
|
Description-Content-Type: text/markdown
|
|
30
32
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "python-swidget"
|
|
3
|
-
version = "1.0.
|
|
3
|
+
version = "1.0.2"
|
|
4
4
|
description = "Python API for Swidget smart devices"
|
|
5
5
|
license = "GPL-3.0-or-later"
|
|
6
6
|
authors = ["Swidget"]
|
|
@@ -22,6 +22,8 @@ importlib-metadata = "*"
|
|
|
22
22
|
asyncclick = ">=8"
|
|
23
23
|
pydantic = "^1"
|
|
24
24
|
ssdp = "1.1.1"
|
|
25
|
+
requests = "2.31.0"
|
|
26
|
+
types-requests = "2.31.0.20240406"
|
|
25
27
|
|
|
26
28
|
# required only for docs
|
|
27
29
|
sphinx = { version = "^4", optional = true }
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""python-swidget cli tool."""
|
|
2
2
|
import logging
|
|
3
3
|
import sys
|
|
4
|
+
from typing import Any, cast
|
|
4
5
|
from pprint import pformat as pf
|
|
5
|
-
from typing import cast
|
|
6
6
|
|
|
7
7
|
import asyncclick as click
|
|
8
8
|
from contextlib import asynccontextmanager
|
|
@@ -25,8 +25,6 @@ TYPE_TO_CLASS = {
|
|
|
25
25
|
"pana_switch": SwidgetTimerSwitch
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
click.anyio_backend = "asyncio"
|
|
29
|
-
|
|
30
28
|
|
|
31
29
|
pass_dev = click.make_pass_decorator(SwidgetDevice)
|
|
32
30
|
|
|
@@ -72,7 +70,7 @@ async def cli(ctx, host, password, debug, http_only, type):
|
|
|
72
70
|
if ctx.invoked_subcommand == "discover" or ctx.invoked_subcommand == "wifi":
|
|
73
71
|
return
|
|
74
72
|
if host is None:
|
|
75
|
-
click.echo("No
|
|
73
|
+
click.echo("No hostname or IP given, trying discovery..")
|
|
76
74
|
await ctx.invoke(discover)
|
|
77
75
|
return
|
|
78
76
|
if type is not None:
|
|
@@ -127,7 +125,7 @@ def join(ssid, network_password, secret_key, friendly_name):
|
|
|
127
125
|
|
|
128
126
|
|
|
129
127
|
@cli.command()
|
|
130
|
-
@click.option("--timeout", default=
|
|
128
|
+
@click.option("--timeout", default=10, required=False)
|
|
131
129
|
@click.pass_context
|
|
132
130
|
async def discover(ctx, timeout):
|
|
133
131
|
"""Discover devices in the network."""
|
|
@@ -193,31 +191,25 @@ async def state(dev: SwidgetDevice):
|
|
|
193
191
|
@click.argument("command")
|
|
194
192
|
async def raw_command(dev: SwidgetDevice, assembly, component, function, command):
|
|
195
193
|
"""Run a raw command on the device."""
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if parameters is not None:
|
|
199
|
-
parameters = ast.literal_eval(parameters)
|
|
200
|
-
|
|
201
|
-
res = await dev.send_command(assembly, component, function, command)
|
|
202
|
-
|
|
203
|
-
click.echo(res)
|
|
204
|
-
return res
|
|
194
|
+
await dev.send_command(assembly, component, function, command)
|
|
195
|
+
click.echo("Command sent")
|
|
205
196
|
|
|
206
197
|
|
|
207
198
|
@cli.command()
|
|
208
199
|
@click.argument("brightness", type=click.IntRange(0, 100), default=None, required=False)
|
|
209
200
|
@pass_dev
|
|
210
|
-
async def brightness(dev:
|
|
201
|
+
async def brightness(dev: SwidgetDevice, brightness: Any=None):
|
|
202
|
+
dimmer_dev = cast(SwidgetDimmer, dev)
|
|
211
203
|
"""Get or set brightness."""
|
|
212
|
-
if not
|
|
204
|
+
if not dimmer_dev.is_dimmer:
|
|
213
205
|
click.echo("This device does not support brightness.")
|
|
214
206
|
return
|
|
215
207
|
|
|
216
208
|
if brightness is None:
|
|
217
|
-
click.echo(f"Brightness: {
|
|
209
|
+
click.echo(f"Brightness: {dimmer_dev.brightness}")
|
|
218
210
|
else:
|
|
219
211
|
click.echo(f"Setting brightness to {brightness}")
|
|
220
|
-
return await
|
|
212
|
+
return await dimmer_dev.set_brightness(brightness)
|
|
221
213
|
|
|
222
214
|
|
|
223
215
|
@cli.command()
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import json
|
|
3
2
|
import logging
|
|
4
3
|
import socket
|
|
5
|
-
from typing import
|
|
4
|
+
from typing import Any, Type
|
|
6
5
|
from urllib.parse import urlparse
|
|
7
6
|
|
|
8
|
-
import ssdp
|
|
7
|
+
import ssdp # type: ignore
|
|
9
8
|
|
|
10
9
|
from swidget.swidgetdevice import DeviceType, SwidgetDevice
|
|
11
10
|
from .swidgetdimmer import SwidgetDimmer
|
|
@@ -39,10 +38,7 @@ class SwidgetProtocol(ssdp.SimpleServiceDiscoveryProtocol):
|
|
|
39
38
|
insert_type = headers["SERVER"].split(" ")[1].split("+")[1].split("/")[0]
|
|
40
39
|
friendly_name = headers["SERVER"].split("/")[2].strip('"')
|
|
41
40
|
devices[mac_address] = SwidgetDiscoveredDevice(mac_address, ip_address, friendly_name)
|
|
42
|
-
|
|
43
|
-
=======
|
|
44
|
-
_LOGGER.debug(f"Swidget device '{friendly_name}' at {ip_address}")
|
|
45
|
-
>>>>>>> 690a0c560a8b2ff39245d1a8354ced968d79e5de
|
|
41
|
+
_LOGGER.debug(f"Swidget device '{friendly_name}' at {ip_address}. {device_type}/{insert_type}")
|
|
46
42
|
|
|
47
43
|
|
|
48
44
|
async def discover_devices(timeout=RESPONSE_SEC):
|
|
@@ -68,7 +64,7 @@ async def discover_devices(timeout=RESPONSE_SEC):
|
|
|
68
64
|
return devices
|
|
69
65
|
|
|
70
66
|
|
|
71
|
-
async def discover_single(host: str, token_name: str, password: str, use_https: bool, use_websockets: bool) ->
|
|
67
|
+
async def discover_single(host: str, token_name: str, password: str, use_https: bool, use_websockets: bool) -> Any:
|
|
72
68
|
"""Discover a single device by the given IP address.
|
|
73
69
|
|
|
74
70
|
:param host: Hostname of device to query
|
|
@@ -91,16 +87,16 @@ async def discover_single(host: str, token_name: str, password: str, use_https:
|
|
|
91
87
|
return dev
|
|
92
88
|
|
|
93
89
|
|
|
94
|
-
def _get_device_class(device_type:
|
|
90
|
+
def _get_device_class(device_type: DeviceType) -> Type[SwidgetDevice]:
|
|
95
91
|
"""Find SmartDevice subclass for device described by passed data."""
|
|
96
|
-
if device_type ==
|
|
92
|
+
if device_type == DeviceType.Outlet:
|
|
97
93
|
return SwidgetOutlet
|
|
98
|
-
elif device_type ==
|
|
94
|
+
elif device_type == DeviceType.Switch:
|
|
99
95
|
return SwidgetSwitch
|
|
100
|
-
elif device_type ==
|
|
96
|
+
elif device_type == DeviceType.Dimmer:
|
|
101
97
|
return SwidgetDimmer
|
|
102
|
-
elif device_type ==
|
|
98
|
+
elif device_type == DeviceType.TimerSwitch: # This is the timer switch
|
|
103
99
|
return SwidgetTimerSwitch
|
|
104
|
-
elif device_type ==
|
|
100
|
+
elif device_type == DeviceType.RelaySwitch:
|
|
105
101
|
return SwidgetSwitch
|
|
106
102
|
raise SwidgetException("Unknown device type: %s" % device_type)
|
|
@@ -5,7 +5,7 @@ import time
|
|
|
5
5
|
from aiohttp import ClientSession, TCPConnector
|
|
6
6
|
import asyncio
|
|
7
7
|
from enum import Enum
|
|
8
|
-
from typing import Dict, List
|
|
8
|
+
from typing import Any, Dict, List
|
|
9
9
|
|
|
10
10
|
from .exceptions import SwidgetException
|
|
11
11
|
from .websocket import SwidgetWebsocket
|
|
@@ -24,21 +24,21 @@ class DeviceType(Enum):
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class SwidgetDevice:
|
|
27
|
-
def __init__(self, host, token_name, secret_key, use_https=True, use_websockets=True):
|
|
27
|
+
def __init__(self, host, token_name, secret_key, use_https=True, use_websockets=True) -> None:
|
|
28
28
|
self.token_name = token_name
|
|
29
29
|
self.ip_address = host
|
|
30
|
-
|
|
31
30
|
self.use_https = use_https
|
|
32
31
|
self.uri_scheme = 'https' if self.use_https is True else 'http'
|
|
33
32
|
self.secret_key = secret_key
|
|
34
33
|
self.use_websockets = use_websockets
|
|
35
34
|
self.device_type = DeviceType.Unknown
|
|
36
35
|
self._friendly_name = "Unknown Swidget Device"
|
|
36
|
+
self.assemblies: Dict[Any, Any] = dict()
|
|
37
37
|
headers = {self.token_name: self.secret_key,
|
|
38
38
|
'Connection': 'keep-alive'}
|
|
39
39
|
connector = TCPConnector(verify_ssl=False, force_close=True)
|
|
40
40
|
self._session = ClientSession(headers=headers, connector=connector)
|
|
41
|
-
self._last_update =
|
|
41
|
+
self._last_update: int = 0
|
|
42
42
|
if self.use_websockets:
|
|
43
43
|
self._websocket = SwidgetWebsocket(
|
|
44
44
|
host=self.ip_address,
|
|
@@ -47,34 +47,37 @@ class SwidgetDevice:
|
|
|
47
47
|
callback=self.message_callback,
|
|
48
48
|
session=self._session)
|
|
49
49
|
|
|
50
|
-
def get_websocket(self):
|
|
50
|
+
def get_websocket(self) -> SwidgetWebsocket | None:
|
|
51
51
|
if self.use_websockets:
|
|
52
52
|
return self._websocket
|
|
53
53
|
return None
|
|
54
54
|
|
|
55
|
-
def set_countdown_timer(self, minutes):
|
|
55
|
+
def set_countdown_timer(self, minutes) -> Any:
|
|
56
56
|
raise NotImplementedError()
|
|
57
57
|
|
|
58
|
-
async def connect(self):
|
|
58
|
+
async def connect(self) -> None:
|
|
59
59
|
await self._websocket.connect()
|
|
60
60
|
|
|
61
|
-
async def start(self):
|
|
61
|
+
async def start(self) -> None:
|
|
62
62
|
"""Start the websocket."""
|
|
63
63
|
_LOGGER.debug("SwidgetDevice.start()")
|
|
64
64
|
if self.use_websockets:
|
|
65
65
|
_LOGGER.debug("Calling self._websocket.connect()")
|
|
66
66
|
await self._websocket.connect()
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
_LOGGER.debug("Calling self.update() ")
|
|
68
|
+
await self.update()
|
|
69
69
|
|
|
70
|
-
async def stop(self):
|
|
70
|
+
async def stop(self) -> None:
|
|
71
71
|
"""Stop the websocket."""
|
|
72
72
|
_LOGGER.debug("SwidgetDevice.stop()")
|
|
73
73
|
if hasattr(self, '_websocket'):
|
|
74
74
|
await self._websocket.close()
|
|
75
75
|
await self._session.close()
|
|
76
76
|
|
|
77
|
-
async def
|
|
77
|
+
async def close(self) -> None:
|
|
78
|
+
await self.stop()
|
|
79
|
+
|
|
80
|
+
async def message_callback(self, message) -> None:
|
|
78
81
|
"""Entrypoint for a websocket callback"""
|
|
79
82
|
_LOGGER.debug("SwidgetDevice.message_callback() called")
|
|
80
83
|
if message["request_id"] == "summary":
|
|
@@ -86,7 +89,7 @@ class SwidgetDevice:
|
|
|
86
89
|
else:
|
|
87
90
|
_LOGGER.error(f"Unknown message type from websocket. Type given was: {message["request_id"]}")
|
|
88
91
|
|
|
89
|
-
async def get_summary(self):
|
|
92
|
+
async def get_summary(self) -> None:
|
|
90
93
|
"""Get a summary of the device over HTTP"""
|
|
91
94
|
_LOGGER.debug("SwidgetDevice.get_summary() called")
|
|
92
95
|
if self.use_websockets:
|
|
@@ -100,7 +103,7 @@ class SwidgetDevice:
|
|
|
100
103
|
summary = await response.json()
|
|
101
104
|
await self.process_summary(summary)
|
|
102
105
|
|
|
103
|
-
async def process_summary(self, summary):
|
|
106
|
+
async def process_summary(self, summary) -> None:
|
|
104
107
|
""" Process the data around the summary of the device"""
|
|
105
108
|
_LOGGER.debug("SwidgetDevice.process_summary() called")
|
|
106
109
|
_LOGGER.debug(f"Summary to process: {summary}")
|
|
@@ -111,12 +114,12 @@ class SwidgetDevice:
|
|
|
111
114
|
"host": SwidgetAssembly(summary["host"]),
|
|
112
115
|
"insert": SwidgetAssembly(summary["insert"]),
|
|
113
116
|
}
|
|
114
|
-
self.device_type = self.assemblies['host'].type
|
|
117
|
+
self.device_type = DeviceType(self.assemblies['host'].type)
|
|
115
118
|
self.insert_type = self.assemblies['insert'].type
|
|
116
119
|
self.id = self.assemblies['host'].id
|
|
117
120
|
self._last_update = int(time.time())
|
|
118
121
|
|
|
119
|
-
async def get_friendly_name(self):
|
|
122
|
+
async def get_friendly_name(self) -> None:
|
|
120
123
|
_LOGGER.debug("SwidgetDevice.get_friendly_name() called")
|
|
121
124
|
try:
|
|
122
125
|
async with self._session.get(
|
|
@@ -127,12 +130,12 @@ class SwidgetDevice:
|
|
|
127
130
|
name = {"name": f"Swidget {self.device_type} w/{self.insert_type} insert"}
|
|
128
131
|
await self.process_friendly_name(name['name'])
|
|
129
132
|
|
|
130
|
-
async def process_friendly_name(self, name):
|
|
133
|
+
async def process_friendly_name(self, name) -> None:
|
|
131
134
|
_LOGGER.debug("SwidgetDevice.process_friendly_name() called")
|
|
132
135
|
self._friendly_name = name
|
|
133
136
|
self._last_update = int(time.time())
|
|
134
137
|
|
|
135
|
-
async def get_state(self):
|
|
138
|
+
async def get_state(self) -> None:
|
|
136
139
|
""" Get the state of the device over HTTP"""
|
|
137
140
|
_LOGGER.debug("SwidgetDevice.get_state() called")
|
|
138
141
|
if self.use_websockets:
|
|
@@ -146,7 +149,7 @@ class SwidgetDevice:
|
|
|
146
149
|
state = await response.json()
|
|
147
150
|
await self.process_state(state)
|
|
148
151
|
|
|
149
|
-
async def process_state(self, state):
|
|
152
|
+
async def process_state(self, state) -> None:
|
|
150
153
|
""" Process any information about the state of the device or insert"""
|
|
151
154
|
# State is not always in the state (during callback)
|
|
152
155
|
_LOGGER.debug("SwidgetDevice.process_state() called")
|
|
@@ -163,9 +166,9 @@ class SwidgetDevice:
|
|
|
163
166
|
pass
|
|
164
167
|
self._last_update = int(time.time())
|
|
165
168
|
|
|
166
|
-
async def update(self):
|
|
169
|
+
async def update(self) -> None:
|
|
167
170
|
_LOGGER.debug("SwidgetDevice.update() called")
|
|
168
|
-
if self._last_update
|
|
171
|
+
if self._last_update == 0:
|
|
169
172
|
_LOGGER.debug("Performing the initial update to obtain sysinfo")
|
|
170
173
|
await self.get_summary()
|
|
171
174
|
await self.get_state()
|
|
@@ -178,25 +181,25 @@ class SwidgetDevice:
|
|
|
178
181
|
await self.get_summary()
|
|
179
182
|
await self.get_state()
|
|
180
183
|
|
|
181
|
-
async def send_config(self, payload: dict):
|
|
184
|
+
async def send_config(self, payload: dict) -> None:
|
|
182
185
|
_LOGGER.debug("SwidgetDevice.send_config() called")
|
|
183
186
|
data = json.dumps({"type":"config","request_id":"send_config", "payload": payload})
|
|
184
187
|
await self._websocket.send_str(data)
|
|
185
188
|
|
|
186
189
|
async def send_command(
|
|
187
190
|
self, assembly: str, component: str, function: str, command: dict
|
|
188
|
-
):
|
|
191
|
+
) -> None:
|
|
189
192
|
_LOGGER.debug("SwidgetDevice.send_command() called")
|
|
190
193
|
"""Send a command to the Swidget device either using a HTTP call or the existing websocket"""
|
|
191
194
|
data = {assembly: {"components": {component: {function: command}}}}
|
|
192
195
|
_LOGGER.debug(f"Command to send: {data}")
|
|
193
196
|
if self.use_websockets:
|
|
194
197
|
_LOGGER.debug("In websocket mode. Sending command over websocket")
|
|
195
|
-
|
|
198
|
+
command_data = json.dumps({"type": "command",
|
|
196
199
|
"request_id": "command",
|
|
197
200
|
"payload": data
|
|
198
201
|
})
|
|
199
|
-
await self._websocket.send_str(
|
|
202
|
+
await self._websocket.send_str(command_data)
|
|
200
203
|
else:
|
|
201
204
|
_LOGGER.debug("NOT in websocket mode, sending command over HTTP")
|
|
202
205
|
async with self._session.post(
|
|
@@ -210,7 +213,7 @@ class SwidgetDevice:
|
|
|
210
213
|
function_value = state[assembly]["components"][component][function]
|
|
211
214
|
self.assemblies[assembly].components[component].functions[function] = function_value # fmt: skip
|
|
212
215
|
|
|
213
|
-
async def ping(self):
|
|
216
|
+
async def ping(self) -> int | SwidgetException:
|
|
214
217
|
"""Ping the device to ensure it's devices
|
|
215
218
|
|
|
216
219
|
:raises SwidgetException: Raise the exception if there we are unable to connect to the Swidget device
|
|
@@ -225,7 +228,7 @@ class SwidgetDevice:
|
|
|
225
228
|
except:
|
|
226
229
|
raise SwidgetException
|
|
227
230
|
|
|
228
|
-
async def blink(self):
|
|
231
|
+
async def blink(self) -> Any:
|
|
229
232
|
"""Make the device LED blink
|
|
230
233
|
|
|
231
234
|
:raises SwidgetException: Raise the exception if there we are unable to connect to the Swidget device
|
|
@@ -240,7 +243,7 @@ class SwidgetDevice:
|
|
|
240
243
|
except:
|
|
241
244
|
raise SwidgetException
|
|
242
245
|
|
|
243
|
-
async def enable_debug_server(self):
|
|
246
|
+
async def enable_debug_server(self) -> Any:
|
|
244
247
|
"""Enable the Swidget local debug server
|
|
245
248
|
|
|
246
249
|
:raises SwidgetException: Raise the exception if there we are unable to connect to the Swidget device
|
|
@@ -255,7 +258,7 @@ class SwidgetDevice:
|
|
|
255
258
|
except:
|
|
256
259
|
raise SwidgetException
|
|
257
260
|
|
|
258
|
-
async def factory_reset(self):
|
|
261
|
+
async def factory_reset(self) -> Any:
|
|
259
262
|
"""Factory reset the Swidget device
|
|
260
263
|
|
|
261
264
|
:raises SwidgetException: Raise the exception if there we are unable to connect to the Swidget device
|
|
@@ -270,7 +273,7 @@ class SwidgetDevice:
|
|
|
270
273
|
except:
|
|
271
274
|
raise SwidgetException
|
|
272
275
|
|
|
273
|
-
async def check_for_updates(self):
|
|
276
|
+
async def check_for_updates(self) -> Any:
|
|
274
277
|
"""Tell the device to contact the Swidget servers to see if there is an available update
|
|
275
278
|
|
|
276
279
|
:raises SwidgetException: Raise the exception if there we are unable to connect to the Swidget device
|
|
@@ -285,7 +288,7 @@ class SwidgetDevice:
|
|
|
285
288
|
except:
|
|
286
289
|
raise SwidgetException
|
|
287
290
|
|
|
288
|
-
async def update_version(self, version):
|
|
291
|
+
async def update_version(self, version) -> Any:
|
|
289
292
|
"""Tell the device to download and apply an update
|
|
290
293
|
|
|
291
294
|
:raises SwidgetException: Raise the exception if there we are unable to connect to the Swidget device
|
|
@@ -299,7 +302,7 @@ class SwidgetDevice:
|
|
|
299
302
|
ssl=False,
|
|
300
303
|
data=json.dumps(data)
|
|
301
304
|
) as response:
|
|
302
|
-
return await response
|
|
305
|
+
return await response.json()
|
|
303
306
|
except:
|
|
304
307
|
raise SwidgetException
|
|
305
308
|
|
|
@@ -322,7 +325,7 @@ class SwidgetDevice:
|
|
|
322
325
|
"rssi": self.rssi
|
|
323
326
|
}
|
|
324
327
|
|
|
325
|
-
async def get_child_consumption(self, plug_id=0):
|
|
328
|
+
async def get_child_consumption(self, plug_id=0) -> Any:
|
|
326
329
|
"""Get the power consumption of a plug in watts."""
|
|
327
330
|
if plug_id == "all":
|
|
328
331
|
return_dict = {}
|
|
@@ -334,7 +337,7 @@ class SwidgetDevice:
|
|
|
334
337
|
return return_dict
|
|
335
338
|
return self.assemblies['host'].components[str(plug_id)].functions['power']['current']
|
|
336
339
|
|
|
337
|
-
async def total_consumption(self):
|
|
340
|
+
async def total_consumption(self) -> float:
|
|
338
341
|
"""Get the total power consumption in watts."""
|
|
339
342
|
total_consumption = 0
|
|
340
343
|
for id, properties in self.assemblies['host'].components.items():
|
|
@@ -342,7 +345,7 @@ class SwidgetDevice:
|
|
|
342
345
|
return total_consumption
|
|
343
346
|
|
|
344
347
|
@property
|
|
345
|
-
async def realtime_values(self):
|
|
348
|
+
async def realtime_values(self) -> Dict:
|
|
346
349
|
"""Get a dict of realtime value attributes from the insert and host
|
|
347
350
|
|
|
348
351
|
:return: A dictionary of insert sensor values and power consumption values
|
|
@@ -363,7 +366,7 @@ class SwidgetDevice:
|
|
|
363
366
|
try:
|
|
364
367
|
return list(self.assemblies['host'].components['0'].functions.keys())
|
|
365
368
|
except KeyError:
|
|
366
|
-
return
|
|
369
|
+
return list()
|
|
367
370
|
|
|
368
371
|
@property
|
|
369
372
|
def insert_features(self) -> List[str]:
|
|
@@ -371,9 +374,9 @@ class SwidgetDevice:
|
|
|
371
374
|
try:
|
|
372
375
|
return list(self.assemblies['insert'].components.keys())
|
|
373
376
|
except KeyError:
|
|
374
|
-
return
|
|
377
|
+
return list()
|
|
375
378
|
|
|
376
|
-
def get_function_values(self, function: str):
|
|
379
|
+
def get_function_values(self, function: str) -> Dict:
|
|
377
380
|
"""Return the values of an insert function."""
|
|
378
381
|
return_values = dict()
|
|
379
382
|
for function, data in self.assemblies['insert'].components[function].functions.items():
|
|
@@ -385,7 +388,7 @@ class SwidgetDevice:
|
|
|
385
388
|
return_values[function] = data['now']
|
|
386
389
|
return return_values
|
|
387
390
|
|
|
388
|
-
def get_sensor_value(self, function, sensor):
|
|
391
|
+
def get_sensor_value(self, function, sensor) -> float | str:
|
|
389
392
|
"""Return the value of a sensor given a function and sensor"""
|
|
390
393
|
if sensor == "occupied":
|
|
391
394
|
return self.assemblies['insert'].components[function].functions['occupied']['state']
|
|
@@ -395,22 +398,22 @@ class SwidgetDevice:
|
|
|
395
398
|
@property
|
|
396
399
|
def is_outlet(self) -> bool:
|
|
397
400
|
"""Return True if the device is an outlet."""
|
|
398
|
-
return self.device_type ==
|
|
401
|
+
return self.device_type == DeviceType.Outlet
|
|
399
402
|
|
|
400
403
|
@property
|
|
401
404
|
def is_switch(self) -> bool:
|
|
402
405
|
"""Return True if the device is a switch"""
|
|
403
|
-
return self.device_type ==
|
|
406
|
+
return self.device_type == DeviceType.Switch or self.device_type == DeviceType.TimerSwitch or self.device_type == DeviceType.RelaySwitch
|
|
404
407
|
|
|
405
408
|
@property
|
|
406
409
|
def is_pana_switch(self) -> bool:
|
|
407
410
|
"""Return True if the device is a pana_switch"""
|
|
408
|
-
return self.device_type ==
|
|
411
|
+
return self.device_type == DeviceType.TimerSwitch
|
|
409
412
|
|
|
410
413
|
@property
|
|
411
414
|
def is_dimmer(self) -> bool:
|
|
412
415
|
"""Return True if the device is a dimmer"""
|
|
413
|
-
return self.device_type ==
|
|
416
|
+
return self.device_type == DeviceType.Dimmer
|
|
414
417
|
|
|
415
418
|
@property
|
|
416
419
|
def is_dimmable(self) -> bool:
|
|
@@ -430,27 +433,27 @@ class SwidgetDevice:
|
|
|
430
433
|
return True
|
|
431
434
|
return False
|
|
432
435
|
|
|
433
|
-
async def turn_on(self):
|
|
436
|
+
async def turn_on(self) -> None:
|
|
434
437
|
"""Turn the device on."""
|
|
435
438
|
_LOGGER.debug("SwidgetDevice.turn_on() called")
|
|
436
439
|
await self.send_command(
|
|
437
440
|
assembly="host", component="0", function="toggle", command={"state": "on"}
|
|
438
441
|
)
|
|
439
442
|
|
|
440
|
-
async def turn_off(self):
|
|
443
|
+
async def turn_off(self) -> None:
|
|
441
444
|
"""Turn the device off."""
|
|
442
445
|
_LOGGER.debug("SwidgetDevice.turn_off() called")
|
|
443
446
|
await self.send_command(
|
|
444
447
|
assembly="host", component="0", function="toggle", command={"state": "off"}
|
|
445
448
|
)
|
|
446
449
|
|
|
447
|
-
async def turn_on_usb_insert(self):
|
|
450
|
+
async def turn_on_usb_insert(self) -> None:
|
|
448
451
|
"""Turn the USB insert on."""
|
|
449
452
|
await self.send_command(
|
|
450
453
|
assembly="insert", component="usb", function="toggle", command={"state": "on"}
|
|
451
454
|
)
|
|
452
455
|
|
|
453
|
-
async def turn_off_usb_insert(self):
|
|
456
|
+
async def turn_off_usb_insert(self) -> None:
|
|
454
457
|
"""Turn the USB insert off."""
|
|
455
458
|
await self.send_command(
|
|
456
459
|
assembly="insert", component="usb", function="toggle", command={"state": "off"}
|
|
@@ -464,8 +467,8 @@ class SwidgetDevice:
|
|
|
464
467
|
return True
|
|
465
468
|
return False
|
|
466
469
|
|
|
467
|
-
def __repr__(self):
|
|
468
|
-
if self._last_update
|
|
470
|
+
def __repr__(self) -> str:
|
|
471
|
+
if self._last_update == 0:
|
|
469
472
|
return f"<{self.device_type} at {self.ip_address} - update() needed>"
|
|
470
473
|
return f"<{self.device_type} model {self.model} at {self.ip_address}>"
|
|
471
474
|
|
|
@@ -28,14 +28,14 @@ class SwidgetDimmer(SwidgetDevice):
|
|
|
28
28
|
except KeyError:
|
|
29
29
|
return self.assemblies['host'].components["0"].functions["level"]["default"]
|
|
30
30
|
|
|
31
|
-
async def set_brightness(self, brightness):
|
|
31
|
+
async def set_brightness(self, brightness) -> None:
|
|
32
32
|
"""Set the brightness of the device."""
|
|
33
33
|
_LOGGER.debug("SwidgetDimmer.set_brightness() called")
|
|
34
34
|
await self.send_command(
|
|
35
35
|
assembly="host", component="0", function="level", command={"now": brightness}
|
|
36
36
|
)
|
|
37
37
|
|
|
38
|
-
async def set_default_brightness(self, brightness):
|
|
38
|
+
async def set_default_brightness(self, brightness) -> None:
|
|
39
39
|
_LOGGER.debug("SwidgetDimmer.set_default_brightness() called")
|
|
40
40
|
await self.send_command(
|
|
41
41
|
assembly="host", component="0", function="level", command={"default": brightness}
|
|
@@ -8,8 +8,4 @@ class SwidgetSwitch(SwidgetDevice):
|
|
|
8
8
|
|
|
9
9
|
def __init__(self, host, token_name: str, secret_key: str, use_https: bool, use_websockets: bool) -> None:
|
|
10
10
|
super().__init__(host=host, token_name=token_name, secret_key=secret_key, use_https=use_https, use_websockets=use_websockets)
|
|
11
|
-
self._device_type = DeviceType.Switch
|
|
12
|
-
|
|
13
|
-
async def current_consumption(self) -> float:
|
|
14
|
-
"""Get the current power consumption in watts."""
|
|
15
|
-
return sum([await plug.current_consumption() for plug in self.children])
|
|
11
|
+
self._device_type = DeviceType.Switch
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
2
4
|
from swidget.swidgetdevice import (
|
|
3
5
|
DeviceType,
|
|
4
6
|
)
|
|
@@ -13,7 +15,7 @@ class SwidgetTimerSwitch(SwidgetSwitch):
|
|
|
13
15
|
super().__init__(host=host, token_name=token_name, secret_key=secret_key, use_https=use_https, use_websockets=use_websockets)
|
|
14
16
|
self._device_type = DeviceType.TimerSwitch
|
|
15
17
|
|
|
16
|
-
async def set_countdown_timer(self, minutes):
|
|
18
|
+
async def set_countdown_timer(self, minutes) -> Any:
|
|
17
19
|
"""Set the countdown timer."""
|
|
18
20
|
_LOGGER.debug("SwidgetTimerSwitch.set_brightness() called")
|
|
19
21
|
await self.send_command(
|
|
@@ -1,9 +1,23 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import aiohttp
|
|
3
|
+
from aiohttp import ClientWebSocketResponse, WSMsgType
|
|
2
4
|
import logging
|
|
3
5
|
import socket
|
|
4
6
|
|
|
5
7
|
_LOGGER = logging.getLogger(__name__)
|
|
6
8
|
|
|
9
|
+
|
|
10
|
+
async def cancel_task(*tasks: asyncio.Task | None) -> None:
|
|
11
|
+
"""Cancel task(s)."""
|
|
12
|
+
for task in tasks:
|
|
13
|
+
if task is not None and not task.done():
|
|
14
|
+
task.cancel()
|
|
15
|
+
try:
|
|
16
|
+
await task
|
|
17
|
+
except asyncio.CancelledError:
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
7
21
|
class SwidgetWebsocket:
|
|
8
22
|
"""A websocket connection to a Swidget Device"""
|
|
9
23
|
|
|
@@ -19,7 +33,7 @@ class SwidgetWebsocket:
|
|
|
19
33
|
session=None,
|
|
20
34
|
use_security=True,
|
|
21
35
|
):
|
|
22
|
-
|
|
36
|
+
self.host = host
|
|
23
37
|
self.session = session or aiohttp.ClientSession()
|
|
24
38
|
self.use_security = use_security
|
|
25
39
|
self.uri = self.get_uri(host, token_name, secret_key)
|
|
@@ -29,11 +43,17 @@ class SwidgetWebsocket:
|
|
|
29
43
|
self.failed_attempts = 0
|
|
30
44
|
self._error_reason = None
|
|
31
45
|
self.headers = {'Connection': 'Upgrade'}
|
|
46
|
+
self._receiver_task: asyncio.Task | None = None
|
|
32
47
|
|
|
33
48
|
@property
|
|
34
49
|
def connected(self) -> bool:
|
|
35
50
|
return self._client is not None and not self._client.closed
|
|
36
51
|
|
|
52
|
+
@property
|
|
53
|
+
def websocket(self) -> ClientWebSocketResponse | None:
|
|
54
|
+
"""Return the web socket."""
|
|
55
|
+
return self._client
|
|
56
|
+
|
|
37
57
|
def get_uri(self, host, token_name, secret_key):
|
|
38
58
|
"""Generate the websocket URI"""
|
|
39
59
|
if self.use_security:
|
|
@@ -43,7 +63,10 @@ class SwidgetWebsocket:
|
|
|
43
63
|
|
|
44
64
|
async def connect(self) -> None:
|
|
45
65
|
_LOGGER.debug("websocket.connect() called")
|
|
66
|
+
"""Create a new connection and, optionally, start the monitor."""
|
|
67
|
+
await cancel_task(self._receiver_task)
|
|
46
68
|
if self.connected:
|
|
69
|
+
_LOGGER.debug("Websocket already connected")
|
|
47
70
|
return
|
|
48
71
|
|
|
49
72
|
if not self.session:
|
|
@@ -51,16 +74,20 @@ class SwidgetWebsocket:
|
|
|
51
74
|
|
|
52
75
|
try:
|
|
53
76
|
self._client = await self.session.ws_connect(url=self.uri, headers=self.headers, verify_ssl=self._verify_ssl, heartbeat=30)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
)
|
|
77
|
+
_LOGGER.debug("Websocket now connected")
|
|
78
|
+
except aiohttp.WSServerHandshakeError as handshake_error:
|
|
79
|
+
_LOGGER.error(f"Error occurred during websocket handshake: {handshake_error}")
|
|
80
|
+
raise
|
|
81
|
+
except aiohttp.ClientConnectionError as connection_error:
|
|
82
|
+
_LOGGER.error(f"Error connecting to the websocket server: {connection_error}")
|
|
83
|
+
raise
|
|
84
|
+
except socket.gaierror as gai_error:
|
|
85
|
+
_LOGGER.error(f"Error resolving host: {gai_error}")
|
|
86
|
+
raise
|
|
87
|
+
except Exception as e:
|
|
88
|
+
_LOGGER.error(f"An unexpected error occurred: {e}")
|
|
63
89
|
raise
|
|
90
|
+
self._receiver_task = asyncio.ensure_future(self.listen())
|
|
64
91
|
|
|
65
92
|
async def close(self) -> None:
|
|
66
93
|
_LOGGER.debug("websocket.close() called")
|
|
@@ -88,7 +115,6 @@ class SwidgetWebsocket:
|
|
|
88
115
|
|
|
89
116
|
if message.type == aiohttp.WSMsgType.TEXT:
|
|
90
117
|
message_data = message.json()
|
|
91
|
-
_LOGGER.debug(f"Data received from websocket: {message_data}")
|
|
92
118
|
await self.callback(message_data)
|
|
93
119
|
|
|
94
120
|
if message.type in (
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|