livisi 0.0.1__py3-none-any.whl → 0.0.19__py3-none-any.whl
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.
- livisi/__init__.py +4 -93
- livisi/aiolivisi.py +270 -0
- livisi/const.py +40 -0
- livisi/errors.py +20 -0
- livisi/livisi_event.py +20 -0
- livisi/websocket.py +106 -0
- livisi-0.0.19.dist-info/LICENSE +201 -0
- livisi-0.0.19.dist-info/METADATA +225 -0
- livisi-0.0.19.dist-info/RECORD +11 -0
- livisi/livisi_connector.py +0 -508
- livisi/livisi_const.py +0 -30
- livisi/livisi_controller.py +0 -17
- livisi/livisi_device.py +0 -53
- livisi/livisi_errors.py +0 -124
- livisi/livisi_json_util.py +0 -23
- livisi/livisi_websocket.py +0 -86
- livisi/livisi_websocket_event.py +0 -13
- livisi-0.0.1.dist-info/LICENSE +0 -0
- livisi-0.0.1.dist-info/METADATA +0 -20
- livisi-0.0.1.dist-info/RECORD +0 -14
- {livisi-0.0.1.dist-info → livisi-0.0.19.dist-info}/WHEEL +0 -0
- {livisi-0.0.1.dist-info → livisi-0.0.19.dist-info}/top_level.txt +0 -0
livisi/__init__.py
CHANGED
@@ -1,93 +1,4 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
# livisi_connector.py
|
6
|
-
from .livisi_connector import LivisiConnection, connect
|
7
|
-
|
8
|
-
# livisi_controller.py
|
9
|
-
from .livisi_controller import LivisiController
|
10
|
-
|
11
|
-
# livisi_device.py
|
12
|
-
from .livisi_device import LivisiDevice
|
13
|
-
|
14
|
-
# livisi_websocket.py
|
15
|
-
from .livisi_websocket import LivisiWebsocket
|
16
|
-
|
17
|
-
# livisi_websocket_event.py
|
18
|
-
from .livisi_websocket_event import LivisiWebsocketEvent
|
19
|
-
|
20
|
-
# livisi_const.py
|
21
|
-
from .livisi_const import (
|
22
|
-
LOGGER,
|
23
|
-
V2_NAME,
|
24
|
-
V1_NAME,
|
25
|
-
V2_WEBSOCKET_PORT,
|
26
|
-
CLASSIC_WEBSOCKET_PORT,
|
27
|
-
WEBSERVICE_PORT,
|
28
|
-
REQUEST_TIMEOUT,
|
29
|
-
CONTROLLER_DEVICE_TYPES,
|
30
|
-
BATTERY_LOW,
|
31
|
-
UPDATE_AVAILABLE,
|
32
|
-
LIVISI_EVENT_STATE_CHANGED,
|
33
|
-
LIVISI_EVENT_BUTTON_PRESSED,
|
34
|
-
LIVISI_EVENT_MOTION_DETECTED,
|
35
|
-
IS_REACHABLE,
|
36
|
-
EVENT_BUTTON_PRESSED,
|
37
|
-
EVENT_BUTTON_LONG_PRESSED,
|
38
|
-
EVENT_MOTION_DETECTED,
|
39
|
-
COMMAND_RESTART,
|
40
|
-
)
|
41
|
-
|
42
|
-
# livisi_errors.py
|
43
|
-
from .livisi_errors import (
|
44
|
-
LivisiException,
|
45
|
-
ShcUnreachableException,
|
46
|
-
WrongCredentialException,
|
47
|
-
IncorrectIpAddressException,
|
48
|
-
TokenExpiredException,
|
49
|
-
ErrorCodeException,
|
50
|
-
ERROR_CODES,
|
51
|
-
)
|
52
|
-
|
53
|
-
# Define __all__ to specify what is exported when using 'from livisi import *'
|
54
|
-
__all__ = [
|
55
|
-
# From livisi_connector.py
|
56
|
-
"LivisiConnection",
|
57
|
-
"connect",
|
58
|
-
# From livisi_controller.py
|
59
|
-
"LivisiController",
|
60
|
-
# From livisi_device.py
|
61
|
-
"LivisiDevice",
|
62
|
-
# From livisi_websocket.py
|
63
|
-
"LivisiWebsocket",
|
64
|
-
# From livisi_websocket_event.py
|
65
|
-
"LivisiWebsocketEvent",
|
66
|
-
# From livisi_const.py
|
67
|
-
"LOGGER",
|
68
|
-
"V2_NAME",
|
69
|
-
"V1_NAME",
|
70
|
-
"V2_WEBSOCKET_PORT",
|
71
|
-
"CLASSIC_WEBSOCKET_PORT",
|
72
|
-
"WEBSERVICE_PORT",
|
73
|
-
"REQUEST_TIMEOUT",
|
74
|
-
"CONTROLLER_DEVICE_TYPES",
|
75
|
-
"BATTERY_LOW",
|
76
|
-
"UPDATE_AVAILABLE",
|
77
|
-
"LIVISI_EVENT_STATE_CHANGED",
|
78
|
-
"LIVISI_EVENT_BUTTON_PRESSED",
|
79
|
-
"LIVISI_EVENT_MOTION_DETECTED",
|
80
|
-
"IS_REACHABLE",
|
81
|
-
"EVENT_BUTTON_PRESSED",
|
82
|
-
"EVENT_BUTTON_LONG_PRESSED",
|
83
|
-
"EVENT_MOTION_DETECTED",
|
84
|
-
"COMMAND_RESTART",
|
85
|
-
# From livisi_errors.py
|
86
|
-
"LivisiException",
|
87
|
-
"ShcUnreachableException",
|
88
|
-
"WrongCredentialException",
|
89
|
-
"IncorrectIpAddressException",
|
90
|
-
"TokenExpiredException",
|
91
|
-
"ErrorCodeException",
|
92
|
-
"ERROR_CODES",
|
93
|
-
]
|
1
|
+
from .aiolivisi import AioLivisi
|
2
|
+
from .websocket import Websocket
|
3
|
+
from .errors import IncorrectIpAddressException, WrongCredentialException, ShcUnreachableException, LivisiException
|
4
|
+
from .livisi_event import LivisiEvent
|
livisi/aiolivisi.py
ADDED
@@ -0,0 +1,270 @@
|
|
1
|
+
"""Code to handle the communication with Livisi Smart home controllers."""
|
2
|
+
from __future__ import annotations
|
3
|
+
from typing import Any
|
4
|
+
import uuid
|
5
|
+
|
6
|
+
from aiohttp.client import ClientSession
|
7
|
+
|
8
|
+
from .errors import (
|
9
|
+
IncorrectIpAddressException,
|
10
|
+
ShcUnreachableException,
|
11
|
+
WrongCredentialException,
|
12
|
+
TokenExpiredException,
|
13
|
+
)
|
14
|
+
|
15
|
+
from .const import (
|
16
|
+
AUTH_GRANT_TYPE,
|
17
|
+
AUTH_PASSWORD,
|
18
|
+
AUTH_USERNAME,
|
19
|
+
AUTHENTICATION_HEADERS,
|
20
|
+
CLASSIC_PORT,
|
21
|
+
LOCATION,
|
22
|
+
CAPABILITY_MAP,
|
23
|
+
CAPABILITY_CONFIG,
|
24
|
+
REQUEST_TIMEOUT,
|
25
|
+
USERNAME,
|
26
|
+
)
|
27
|
+
|
28
|
+
ERRORS = {1: Exception}
|
29
|
+
|
30
|
+
|
31
|
+
class AioLivisi:
|
32
|
+
"""Handles the communication with the Livisi Smart Home controller."""
|
33
|
+
|
34
|
+
instance = None
|
35
|
+
|
36
|
+
def __init__(
|
37
|
+
self, web_session: ClientSession = None, auth_headers: dict[str, Any] = None
|
38
|
+
) -> None:
|
39
|
+
self._web_session: ClientSession = web_session
|
40
|
+
self._auth_headers: dict[str, Any] = auth_headers
|
41
|
+
self._token: str = ""
|
42
|
+
self._livisi_connection_data: dict[str, str] = None
|
43
|
+
|
44
|
+
async def async_set_token(
|
45
|
+
self, livisi_connection_data: dict[str, str] = None
|
46
|
+
) -> None:
|
47
|
+
"""Set the JWT from the LIVISI Smart Home Controller."""
|
48
|
+
access_data: dict = {}
|
49
|
+
try:
|
50
|
+
if self._livisi_connection_data is not None:
|
51
|
+
self._livisi_connection_data = livisi_connection_data
|
52
|
+
access_data = await self.async_get_jwt_token(livisi_connection_data)
|
53
|
+
self.token = access_data["access_token"]
|
54
|
+
self._auth_headers = {
|
55
|
+
"authorization": f"Bearer {self.token}",
|
56
|
+
"Content-type": "application/json",
|
57
|
+
"Accept": "*/*",
|
58
|
+
}
|
59
|
+
except Exception as error:
|
60
|
+
if len(access_data) == 0:
|
61
|
+
raise IncorrectIpAddressException from error
|
62
|
+
elif access_data["errorcode"] == 2009:
|
63
|
+
raise WrongCredentialException from error
|
64
|
+
else:
|
65
|
+
raise ShcUnreachableException from error
|
66
|
+
|
67
|
+
async def async_send_authorized_request(
|
68
|
+
self,
|
69
|
+
method,
|
70
|
+
url: str,
|
71
|
+
payload=None,
|
72
|
+
) -> dict:
|
73
|
+
"""Make a request to the Livisi Smart Home controller."""
|
74
|
+
ip_address = self._livisi_connection_data["ip_address"]
|
75
|
+
path = f"http://{ip_address}:{CLASSIC_PORT}/{url}"
|
76
|
+
return await self.async_send_request(method, path, payload, self._auth_headers)
|
77
|
+
|
78
|
+
async def async_send_unauthorized_request(
|
79
|
+
self,
|
80
|
+
method,
|
81
|
+
url: str,
|
82
|
+
headers,
|
83
|
+
payload=None,
|
84
|
+
) -> dict:
|
85
|
+
"""Send a request without JWT token."""
|
86
|
+
return await self.async_send_request(method, url, payload, headers)
|
87
|
+
|
88
|
+
async def async_get_jwt_token(self, livisi_connection_data: dict[str, str]):
|
89
|
+
"""Send a request for getting the JWT token."""
|
90
|
+
login_credentials = {
|
91
|
+
AUTH_USERNAME: USERNAME,
|
92
|
+
AUTH_PASSWORD: livisi_connection_data["password"],
|
93
|
+
AUTH_GRANT_TYPE: "password",
|
94
|
+
}
|
95
|
+
headers = AUTHENTICATION_HEADERS
|
96
|
+
self._livisi_connection_data = livisi_connection_data
|
97
|
+
ip_address = self._livisi_connection_data["ip_address"]
|
98
|
+
return await self.async_send_request(
|
99
|
+
"post",
|
100
|
+
url=f"http://{ip_address}:{CLASSIC_PORT}/auth/token",
|
101
|
+
payload=login_credentials,
|
102
|
+
headers=headers,
|
103
|
+
)
|
104
|
+
|
105
|
+
async def async_send_request(
|
106
|
+
self, method, url: str, payload=None, headers=None
|
107
|
+
) -> dict:
|
108
|
+
"""Send a request to the Livisi Smart Home controller."""
|
109
|
+
try:
|
110
|
+
response = await self.__async_send_request(method, url, payload, headers)
|
111
|
+
except Exception:
|
112
|
+
response = await self.__async_send_request(method, url, payload, headers)
|
113
|
+
if "errorcode" in response:
|
114
|
+
if response["errorcode"] == 2007:
|
115
|
+
raise TokenExpiredException
|
116
|
+
return response
|
117
|
+
|
118
|
+
async def __async_send_request(
|
119
|
+
self, method, url: str, payload=None, headers=None
|
120
|
+
) -> dict:
|
121
|
+
async with self._web_session.request(
|
122
|
+
method,
|
123
|
+
url,
|
124
|
+
json=payload,
|
125
|
+
headers=headers,
|
126
|
+
ssl=False,
|
127
|
+
timeout=REQUEST_TIMEOUT,
|
128
|
+
) as res:
|
129
|
+
data = await res.json()
|
130
|
+
return data
|
131
|
+
|
132
|
+
async def async_get_controller(self) -> dict[str, Any]:
|
133
|
+
"""Get Livisi Smart Home controller data."""
|
134
|
+
return await self.async_get_controller_status()
|
135
|
+
|
136
|
+
async def async_get_controller_status(self) -> dict[str, Any]:
|
137
|
+
"""Get Livisi Smart Home controller status."""
|
138
|
+
shc_info = await self.async_send_authorized_request("get", url="status")
|
139
|
+
return shc_info
|
140
|
+
|
141
|
+
async def async_get_devices(
|
142
|
+
self,
|
143
|
+
) -> list[dict[str, Any]]:
|
144
|
+
"""Send a request for getting the devices."""
|
145
|
+
devices = await self.async_send_authorized_request("get", url="device")
|
146
|
+
capabilities = await self.async_send_authorized_request("get", url="capability")
|
147
|
+
|
148
|
+
capability_map = {}
|
149
|
+
capability_config = {}
|
150
|
+
|
151
|
+
for capability in capabilities:
|
152
|
+
if "device" in capability:
|
153
|
+
device_id = capability["device"].split("/")[-1]
|
154
|
+
if device_id not in capability_map:
|
155
|
+
capability_map[device_id] = {}
|
156
|
+
capability_config[device_id] = {}
|
157
|
+
capability_map[device_id][capability["type"]] = (
|
158
|
+
"/capability/" + capability["id"]
|
159
|
+
)
|
160
|
+
if "config" in capability:
|
161
|
+
capability_config[device_id][capability["type"]] = capability["config"]
|
162
|
+
|
163
|
+
for device in devices:
|
164
|
+
device_id = device["id"]
|
165
|
+
device[CAPABILITY_MAP] = capability_map.get(device_id, {})
|
166
|
+
device[CAPABILITY_CONFIG] = capability_config.get(device_id, {})
|
167
|
+
|
168
|
+
for device in devices.copy():
|
169
|
+
if LOCATION in device and device.get(LOCATION) is not None:
|
170
|
+
device[LOCATION] = device[LOCATION].removeprefix("/location/")
|
171
|
+
return devices
|
172
|
+
|
173
|
+
async def async_get_device_state(self, capability) -> dict[str, Any] | None:
|
174
|
+
"""Get the state of the device."""
|
175
|
+
url = f"{capability}/state"
|
176
|
+
try:
|
177
|
+
return await self.async_send_authorized_request("get", url)
|
178
|
+
except Exception:
|
179
|
+
return None
|
180
|
+
|
181
|
+
async def async_pss_set_state(self, capability_id, is_on: bool) -> dict[str, Any]:
|
182
|
+
"""Set the PSS state."""
|
183
|
+
set_state_payload: dict[str, Any] = {
|
184
|
+
"id": uuid.uuid4().hex,
|
185
|
+
"type": "SetState",
|
186
|
+
"namespace": "core.RWE",
|
187
|
+
"target": capability_id,
|
188
|
+
"params": {"onState": {"type": "Constant", "value": is_on}},
|
189
|
+
}
|
190
|
+
return await self.async_send_authorized_request(
|
191
|
+
"post", "action", payload=set_state_payload
|
192
|
+
)
|
193
|
+
|
194
|
+
async def async_set_onstate(self, capability_id, is_on: bool) -> dict[str, Any]:
|
195
|
+
"""Set the onState for devices that support it."""
|
196
|
+
set_state_payload: dict[str, Any] = {
|
197
|
+
"id": uuid.uuid4().hex,
|
198
|
+
"type": "SetState",
|
199
|
+
"namespace": "core.RWE",
|
200
|
+
"target": capability_id,
|
201
|
+
"params": {"onState": {"type": "Constant", "value": is_on}},
|
202
|
+
}
|
203
|
+
return await self.async_send_authorized_request(
|
204
|
+
"post", "action", payload=set_state_payload
|
205
|
+
)
|
206
|
+
|
207
|
+
async def async_variable_set_value(
|
208
|
+
self, capability_id, value: bool
|
209
|
+
) -> dict[str, Any]:
|
210
|
+
"""Set the boolean variable state."""
|
211
|
+
set_value_payload: dict[str, Any] = {
|
212
|
+
"id": uuid.uuid4().hex,
|
213
|
+
"type": "SetState",
|
214
|
+
"namespace": "core.RWE",
|
215
|
+
"target": capability_id,
|
216
|
+
"params": {"value": {"type": "Constant", "value": value}},
|
217
|
+
}
|
218
|
+
return await self.async_send_authorized_request(
|
219
|
+
"post", "action", payload=set_value_payload
|
220
|
+
)
|
221
|
+
|
222
|
+
async def async_vrcc_set_temperature(
|
223
|
+
self, capability_id, target_temperature: float, is_avatar: bool
|
224
|
+
) -> dict[str, Any]:
|
225
|
+
"""Set the Virtual Climate Control state."""
|
226
|
+
if is_avatar:
|
227
|
+
params = "setpointTemperature"
|
228
|
+
else:
|
229
|
+
params = "pointTemperature"
|
230
|
+
set_state_payload: dict[str, Any] = {
|
231
|
+
"id": uuid.uuid4().hex,
|
232
|
+
"type": "SetState",
|
233
|
+
"namespace": "core.RWE",
|
234
|
+
"target": capability_id,
|
235
|
+
"params": {params: {"type": "Constant", "value": target_temperature}},
|
236
|
+
}
|
237
|
+
return await self.async_send_authorized_request(
|
238
|
+
"post", "action", payload=set_state_payload
|
239
|
+
)
|
240
|
+
|
241
|
+
async def async_get_all_rooms(self) -> dict[str, Any]:
|
242
|
+
"""Get all the rooms from LIVISI configuration."""
|
243
|
+
return await self.async_send_authorized_request("get", "location")
|
244
|
+
|
245
|
+
@property
|
246
|
+
def livisi_connection_data(self):
|
247
|
+
"""Return the connection data."""
|
248
|
+
return self._livisi_connection_data
|
249
|
+
|
250
|
+
@livisi_connection_data.setter
|
251
|
+
def livisi_connection_data(self, new_value):
|
252
|
+
self._livisi_connection_data = new_value
|
253
|
+
|
254
|
+
@property
|
255
|
+
def token(self):
|
256
|
+
"""Return the token."""
|
257
|
+
return self._token
|
258
|
+
|
259
|
+
@token.setter
|
260
|
+
def token(self, new_value):
|
261
|
+
self._token = new_value
|
262
|
+
|
263
|
+
@property
|
264
|
+
def web_session(self):
|
265
|
+
"""Return the web session."""
|
266
|
+
return self._web_session
|
267
|
+
|
268
|
+
@web_session.setter
|
269
|
+
def web_session(self, new_value):
|
270
|
+
self._web_session = new_value
|
livisi/const.py
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
from typing import Final
|
2
|
+
|
3
|
+
|
4
|
+
CLASSIC_PORT: Final = 8080
|
5
|
+
AVATAR_PORT: Final = 9090
|
6
|
+
USERNAME: Final = "admin"
|
7
|
+
AUTH_USERNAME: Final = "username"
|
8
|
+
AUTH_PASSWORD: Final = "password"
|
9
|
+
AUTH_GRANT_TYPE: Final = "grant_type"
|
10
|
+
REQUEST_TIMEOUT: Final = 2000
|
11
|
+
|
12
|
+
ON_STATE: Final = "onState"
|
13
|
+
VALUE: Final = "value"
|
14
|
+
POINT_TEMPERATURE: Final = "pointTemperature"
|
15
|
+
SET_POINT_TEMPERATURE: Final = "setpointTemperature"
|
16
|
+
TEMPERATURE: Final = "temperature"
|
17
|
+
HUMIDITY: Final = "humidity"
|
18
|
+
LUMINANCE: Final = "luminance"
|
19
|
+
IS_REACHABLE: Final = "isReachable"
|
20
|
+
IS_OPEN: Final = "isOpen"
|
21
|
+
LOCATION: Final = "location"
|
22
|
+
|
23
|
+
KEY_INDEX: Final = "index"
|
24
|
+
KEY_PRESS_TYPE: Final = "type"
|
25
|
+
KEY_PRESS_SHORT: Final = "ShortPress"
|
26
|
+
KEY_PRESS_LONG: Final = "LongPress"
|
27
|
+
|
28
|
+
|
29
|
+
CAPABILITY_MAP: Final = "capabilityMap"
|
30
|
+
CAPABILITY_CONFIG: Final = "capabilityConfig"
|
31
|
+
|
32
|
+
EVENT_STATE_CHANGED = "StateChanged"
|
33
|
+
EVENT_BUTTON_PRESSED = "ButtonPressed"
|
34
|
+
EVENT_MOTION_DETECTED = "MotionDetected"
|
35
|
+
|
36
|
+
AUTHENTICATION_HEADERS: Final = {
|
37
|
+
"Authorization": "Basic Y2xpZW50SWQ6Y2xpZW50UGFzcw==",
|
38
|
+
"Content-type": "application/json",
|
39
|
+
"Accept": "application/json",
|
40
|
+
}
|
livisi/errors.py
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
"""Errors for the Livisi Smart Home component."""
|
2
|
+
|
3
|
+
class LivisiException(Exception):
|
4
|
+
"""Base class for Livisi exceptions."""
|
5
|
+
|
6
|
+
|
7
|
+
class ShcUnreachableException(LivisiException):
|
8
|
+
"""Unable to connect to the Smart Home Controller."""
|
9
|
+
|
10
|
+
|
11
|
+
class WrongCredentialException(LivisiException):
|
12
|
+
"""The user credentials were wrong."""
|
13
|
+
|
14
|
+
|
15
|
+
class IncorrectIpAddressException(LivisiException):
|
16
|
+
"""The IP address provided by the user is incorrect."""
|
17
|
+
|
18
|
+
|
19
|
+
class TokenExpiredException(LivisiException):
|
20
|
+
"""The authentication token is expired."""
|
livisi/livisi_event.py
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
from pydantic import BaseModel
|
3
|
+
from typing import Optional
|
4
|
+
|
5
|
+
|
6
|
+
@dataclass(init=False)
|
7
|
+
class LivisiEvent(BaseModel):
|
8
|
+
namespace: str
|
9
|
+
properties: Optional[dict]
|
10
|
+
source: str
|
11
|
+
onState: Optional[bool]
|
12
|
+
vrccData: Optional[float]
|
13
|
+
luminance: Optional[int]
|
14
|
+
isReachable: Optional[bool]
|
15
|
+
sequenceNumber: Optional[str]
|
16
|
+
type: Optional[str]
|
17
|
+
timestamp: Optional[str]
|
18
|
+
isOpen: Optional[bool]
|
19
|
+
keyIndex: Optional[int]
|
20
|
+
isLongKeyPress: Optional[bool]
|
livisi/websocket.py
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
"""Code for communication with the Livisi application websocket."""
|
2
|
+
from typing import Callable
|
3
|
+
import urllib.parse
|
4
|
+
|
5
|
+
import websockets
|
6
|
+
from pydantic import ValidationError
|
7
|
+
|
8
|
+
from aiolivisi.livisi_event import LivisiEvent
|
9
|
+
|
10
|
+
from .aiolivisi import AioLivisi
|
11
|
+
from .const import (
|
12
|
+
AVATAR_PORT,
|
13
|
+
IS_REACHABLE,
|
14
|
+
ON_STATE,
|
15
|
+
VALUE,
|
16
|
+
IS_OPEN,
|
17
|
+
SET_POINT_TEMPERATURE,
|
18
|
+
POINT_TEMPERATURE,
|
19
|
+
HUMIDITY,
|
20
|
+
TEMPERATURE,
|
21
|
+
LUMINANCE,
|
22
|
+
KEY_INDEX,
|
23
|
+
KEY_PRESS_LONG,
|
24
|
+
KEY_PRESS_TYPE,
|
25
|
+
EVENT_BUTTON_PRESSED,
|
26
|
+
EVENT_STATE_CHANGED,
|
27
|
+
)
|
28
|
+
|
29
|
+
|
30
|
+
class Websocket:
|
31
|
+
"""Represents the websocket class."""
|
32
|
+
|
33
|
+
def __init__(self, aiolivisi: AioLivisi) -> None:
|
34
|
+
"""Initialize the websocket."""
|
35
|
+
self.aiolivisi = aiolivisi
|
36
|
+
self.connection_url: str = None
|
37
|
+
|
38
|
+
async def connect(self, on_data, on_close, port: int) -> None:
|
39
|
+
"""Connect to the socket."""
|
40
|
+
if port == AVATAR_PORT:
|
41
|
+
token = urllib.parse.quote(self.aiolivisi.token)
|
42
|
+
else:
|
43
|
+
token = self.aiolivisi.token
|
44
|
+
ip_address = self.aiolivisi.livisi_connection_data["ip_address"]
|
45
|
+
self.connection_url = f"ws://{ip_address}:{port}/events?token={token}"
|
46
|
+
try:
|
47
|
+
async with websockets.connect(
|
48
|
+
self.connection_url, ping_interval=10, ping_timeout=10
|
49
|
+
) as websocket:
|
50
|
+
try:
|
51
|
+
self._websocket = websocket
|
52
|
+
await self.consumer_handler(websocket, on_data)
|
53
|
+
except Exception:
|
54
|
+
await on_close()
|
55
|
+
return
|
56
|
+
except Exception:
|
57
|
+
await on_close()
|
58
|
+
return
|
59
|
+
|
60
|
+
async def disconnect(self) -> None:
|
61
|
+
"""Close the websocket."""
|
62
|
+
await self._websocket.close(code=1000, reason="Handle disconnect request")
|
63
|
+
|
64
|
+
async def consumer_handler(self, websocket, on_data: Callable):
|
65
|
+
"""Used when data is transmited using the websocket."""
|
66
|
+
async for message in websocket:
|
67
|
+
try:
|
68
|
+
event_data = LivisiEvent.parse_raw(message)
|
69
|
+
except ValidationError:
|
70
|
+
continue
|
71
|
+
|
72
|
+
if "device" in event_data.source:
|
73
|
+
event_data.source = event_data.source.replace("/device/", "")
|
74
|
+
if event_data.properties is None:
|
75
|
+
continue
|
76
|
+
|
77
|
+
if event_data.type == EVENT_STATE_CHANGED:
|
78
|
+
if ON_STATE in event_data.properties.keys():
|
79
|
+
event_data.onState = event_data.properties.get(ON_STATE)
|
80
|
+
elif VALUE in event_data.properties.keys() and isinstance(
|
81
|
+
event_data.properties.get(VALUE), bool
|
82
|
+
):
|
83
|
+
event_data.onState = event_data.properties.get(VALUE)
|
84
|
+
if SET_POINT_TEMPERATURE in event_data.properties.keys():
|
85
|
+
event_data.vrccData = event_data.properties.get(
|
86
|
+
SET_POINT_TEMPERATURE
|
87
|
+
)
|
88
|
+
elif POINT_TEMPERATURE in event_data.properties.keys():
|
89
|
+
event_data.vrccData = event_data.properties.get(POINT_TEMPERATURE)
|
90
|
+
elif TEMPERATURE in event_data.properties.keys():
|
91
|
+
event_data.vrccData = event_data.properties.get(TEMPERATURE)
|
92
|
+
elif HUMIDITY in event_data.properties.keys():
|
93
|
+
event_data.vrccData = event_data.properties.get(HUMIDITY)
|
94
|
+
if LUMINANCE in event_data.properties.keys():
|
95
|
+
event_data.luminance = event_data.properties.get(LUMINANCE)
|
96
|
+
if IS_REACHABLE in event_data.properties.keys():
|
97
|
+
event_data.isReachable = event_data.properties.get(IS_REACHABLE)
|
98
|
+
if IS_OPEN in event_data.properties.keys():
|
99
|
+
event_data.isOpen = event_data.properties.get(IS_OPEN)
|
100
|
+
elif event_data.type == EVENT_BUTTON_PRESSED:
|
101
|
+
if KEY_INDEX in event_data.properties.keys():
|
102
|
+
event_data.keyIndex = event_data.properties.get(KEY_INDEX)
|
103
|
+
event_data.isLongKeyPress = (
|
104
|
+
KEY_PRESS_LONG == event_data.properties.get(KEY_PRESS_TYPE)
|
105
|
+
)
|
106
|
+
on_data(event_data)
|