aioccl 2024.12.7__py3-none-any.whl → 2024.12.12__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.
- aioccl/device.py +92 -48
- aioccl/server.py +35 -35
- {aioccl-2024.12.7.dist-info → aioccl-2024.12.12.dist-info}/METADATA +1 -2
- aioccl-2024.12.12.dist-info/RECORD +9 -0
- aioccl-2024.12.7.dist-info/RECORD +0 -9
- {aioccl-2024.12.7.dist-info → aioccl-2024.12.12.dist-info}/LICENSE +0 -0
- {aioccl-2024.12.7.dist-info → aioccl-2024.12.12.dist-info}/WHEEL +0 -0
- {aioccl-2024.12.7.dist-info → aioccl-2024.12.12.dist-info}/top_level.txt +0 -0
aioccl/device.py
CHANGED
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
4
4
|
|
5
5
|
import logging
|
6
6
|
import time
|
7
|
-
from typing import Callable
|
7
|
+
from typing import Callable, TypedDict
|
8
8
|
|
9
9
|
from .sensor import CCLSensor, CCL_SENSORS
|
10
10
|
|
@@ -15,59 +15,74 @@ CCL_DEVICE_INFO_TYPES = ("serial_no", "mac_address", "model", "fw_ver")
|
|
15
15
|
|
16
16
|
class CCLDevice:
|
17
17
|
"""Mapping for a CCL device."""
|
18
|
-
_binary_sensors: dict[str, CCLSensor] | None = {}
|
19
|
-
_device_id: str | None = None
|
20
|
-
_fw_ver: str | None = None
|
21
|
-
_last_updated_time: float | None = None
|
22
|
-
_mac_address: str | None = None
|
23
|
-
_model: str | None = None
|
24
|
-
_new_binary_sensor_callbacks = set()
|
25
|
-
_new_sensors: list[CCLSensor] | None = []
|
26
|
-
_new_sensor_callbacks = set()
|
27
|
-
_passkey = ''
|
28
|
-
_sensors: dict[str, CCLSensor] | None = {}
|
29
|
-
_serial_no: str | None = None
|
30
|
-
_update_callbacks = set()
|
31
18
|
|
32
19
|
def __init__(self, passkey: str):
|
33
20
|
"""Initialize a CCL device."""
|
34
|
-
|
21
|
+
|
22
|
+
class Info(TypedDict):
|
23
|
+
"""Store device information."""
|
24
|
+
fw_ver: str | None
|
25
|
+
last_update_time: float | None
|
26
|
+
mac_address: str | None
|
27
|
+
model: str | None
|
28
|
+
passkey: str
|
29
|
+
serial_no: str | None
|
30
|
+
|
31
|
+
self._info: Info = {
|
32
|
+
"fw_ver": None,
|
33
|
+
"last_update_time": None,
|
34
|
+
"mac_address": None,
|
35
|
+
"model": None,
|
36
|
+
"passkey": passkey,
|
37
|
+
"serial_no": None,
|
38
|
+
}
|
39
|
+
|
40
|
+
self._binary_sensors: dict[str, CCLSensor] | None = {}
|
41
|
+
self._sensors: dict[str, CCLSensor] | None = {}
|
42
|
+
self._update_callbacks = {}
|
43
|
+
|
44
|
+
self._new_binary_sensor_callbacks = set()
|
45
|
+
self._new_sensors: list[CCLSensor] | None = []
|
46
|
+
self._new_sensor_callbacks = set()
|
35
47
|
|
36
48
|
@property
|
37
49
|
def passkey(self) -> str:
|
38
50
|
"""Return the passkey."""
|
39
|
-
return self.
|
51
|
+
return self._info["passkey"]
|
40
52
|
|
41
53
|
@property
|
42
54
|
def device_id(self) -> str | None:
|
43
55
|
"""Return the device ID."""
|
44
|
-
|
45
|
-
self._device_id = self._mac_address.replace(":", "").lower()[-6:]
|
46
|
-
except Exception: # pylint: disable=broad-exception-caught
|
56
|
+
if self.mac_address is None:
|
47
57
|
return None
|
48
|
-
return self.
|
58
|
+
return self.mac_address.replace(":", "").lower()[-6:]
|
59
|
+
|
60
|
+
@property
|
61
|
+
def last_update_time(self) -> str | None:
|
62
|
+
"""Return the last update time."""
|
63
|
+
return self._info["last_update_time"]
|
49
64
|
|
50
65
|
@property
|
51
66
|
def name(self) -> str | None:
|
52
67
|
"""Return the display name."""
|
53
|
-
if self.
|
54
|
-
return self.
|
55
|
-
return self.
|
68
|
+
if self.device_id is not None:
|
69
|
+
return self.model + " - " + self.device_id
|
70
|
+
return self._info["model"]
|
56
71
|
|
57
72
|
@property
|
58
73
|
def mac_address(self) -> str | None:
|
59
74
|
"""Return the MAC address."""
|
60
|
-
return self.
|
75
|
+
return self._info["mac_address"]
|
61
76
|
|
62
77
|
@property
|
63
78
|
def model(self) -> str | None:
|
64
79
|
"""Return the model."""
|
65
|
-
return self.
|
80
|
+
return self._info["model"]
|
66
81
|
|
67
82
|
@property
|
68
83
|
def fw_ver(self) -> str | None:
|
69
84
|
"""Return the firmware version."""
|
70
|
-
return self.
|
85
|
+
return self._info["fw_ver"]
|
71
86
|
|
72
87
|
@property
|
73
88
|
def binary_sensors(self) -> dict[str, CCLSensor] | None:
|
@@ -79,45 +94,66 @@ class CCLDevice:
|
|
79
94
|
"""Store sensor data under this device."""
|
80
95
|
return self._sensors
|
81
96
|
|
82
|
-
def update_info(self,
|
97
|
+
def update_info(self, new_info: dict[str, None | str]) -> None:
|
83
98
|
"""Add or update device info."""
|
84
|
-
|
85
|
-
|
86
|
-
|
99
|
+
for key, value in new_info.items():
|
100
|
+
if key in self._info:
|
101
|
+
self._info[key] = str(value)
|
102
|
+
self._info["last_update_time"] = time.monotonic()
|
87
103
|
|
88
104
|
def update_sensors(self, sensors: dict[str, None | str | int | float]) -> None:
|
89
105
|
"""Add or update all sensor values."""
|
90
106
|
for key, value in sensors.items():
|
91
107
|
if CCL_SENSORS.get(key).binary:
|
92
|
-
if key not in self.
|
108
|
+
if key not in self.binary_sensors:
|
93
109
|
self._binary_sensors[key] = CCLSensor(key)
|
94
|
-
self._new_sensors.append(self.
|
110
|
+
self._new_sensors.append(self.binary_sensors[key])
|
95
111
|
self._binary_sensors[key].value = value
|
96
112
|
else:
|
97
|
-
if key not in self.
|
113
|
+
if key not in self.sensors:
|
98
114
|
self._sensors[key] = CCLSensor(key)
|
99
|
-
self._new_sensors.append(self.
|
115
|
+
self._new_sensors.append(self.sensors[key])
|
100
116
|
self._sensors[key].value = value
|
101
|
-
self._publish_new_sensors()
|
102
|
-
self._publish_updates()
|
103
|
-
self._last_updated_time = time.monotonic()
|
104
|
-
_LOGGER.debug("Sensors Updated: %s", self._last_updated_time)
|
105
117
|
|
106
|
-
|
118
|
+
add_count = self._publish_new_sensors()
|
119
|
+
_LOGGER.debug(
|
120
|
+
"Added %s new sensors for device %s at %s.",
|
121
|
+
add_count,
|
122
|
+
self.device_id,
|
123
|
+
self.last_update_time,
|
124
|
+
)
|
125
|
+
|
126
|
+
update_count = self._publish_updates()
|
127
|
+
_LOGGER.debug(
|
128
|
+
"Updated %s sensors in total for device %s at %s.",
|
129
|
+
update_count,
|
130
|
+
self.device_id,
|
131
|
+
self.last_update_time,
|
132
|
+
)
|
133
|
+
|
134
|
+
def register_update_cb(self, sensor_key, callback: Callable[[], None]) -> None:
|
107
135
|
"""Register callback, called when Sensor changes state."""
|
108
|
-
self._update_callbacks
|
136
|
+
self._update_callbacks[sensor_key] = callback
|
109
137
|
|
110
|
-
def remove_update_cb(self, callback: Callable[[], None]) -> None:
|
138
|
+
def remove_update_cb(self, sensor_key, callback: Callable[[], None]) -> None:
|
111
139
|
"""Remove previously registered callback."""
|
112
|
-
self._update_callbacks.
|
140
|
+
self._update_callbacks.pop(sensor_key, None)
|
113
141
|
|
114
|
-
def _publish_updates(self) ->
|
142
|
+
def _publish_updates(self) -> int:
|
115
143
|
"""Schedule call all registered callbacks."""
|
116
|
-
|
117
|
-
|
144
|
+
count = 0
|
145
|
+
for sensor_key, callback in self._update_callbacks.items():
|
146
|
+
try:
|
118
147
|
callback()
|
119
|
-
|
120
|
-
|
148
|
+
count += 1
|
149
|
+
except Exception as err: # pylint: disable=broad-exception-caught
|
150
|
+
_LOGGER.warning(
|
151
|
+
"Error while updating sensor %s for device %s: %s",
|
152
|
+
sensor_key,
|
153
|
+
self.device_id,
|
154
|
+
err,
|
155
|
+
)
|
156
|
+
return count
|
121
157
|
|
122
158
|
def register_new_binary_sensor_cb(self, callback: Callable[[], None]) -> None:
|
123
159
|
"""Register callback, called when Sensor changes state."""
|
@@ -137,6 +173,7 @@ class CCLDevice:
|
|
137
173
|
|
138
174
|
def _publish_new_sensors(self) -> None:
|
139
175
|
"""Schedule call all registered callbacks."""
|
176
|
+
count = 0
|
140
177
|
for sensor in self._new_sensors[:]:
|
141
178
|
try:
|
142
179
|
if sensor.binary:
|
@@ -146,5 +183,12 @@ class CCLDevice:
|
|
146
183
|
for callback in self._new_sensor_callbacks:
|
147
184
|
callback(sensor)
|
148
185
|
self._new_sensors.remove(sensor)
|
186
|
+
count += 1
|
149
187
|
except Exception as err: # pylint: disable=broad-exception-caught
|
150
|
-
_LOGGER.warning(
|
188
|
+
_LOGGER.warning(
|
189
|
+
"Error while adding sensor %s for device %s: %s",
|
190
|
+
sensor.key,
|
191
|
+
self.device_id,
|
192
|
+
err,
|
193
|
+
)
|
194
|
+
return count
|
aioccl/server.py
CHANGED
@@ -23,60 +23,60 @@ class CCLServer:
|
|
23
23
|
def register(device: CCLDevice) -> None:
|
24
24
|
"""Register a device with a passkey."""
|
25
25
|
CCLServer.devices.setdefault(device.passkey, device)
|
26
|
-
_LOGGER.debug("Device registered: %s", device)
|
27
|
-
|
26
|
+
_LOGGER.debug("Device registered: %s", device.passkey)
|
27
|
+
|
28
28
|
@staticmethod
|
29
29
|
async def handler(request: web.BaseRequest | web.Request) -> web.Response:
|
30
30
|
"""Handle POST requests for data updating."""
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
31
|
+
body: dict[str, None | str | int | float] = {}
|
32
|
+
device: CCLDevice = None
|
33
|
+
info: dict[str, None | str] = {}
|
34
|
+
passkey: str = ""
|
35
|
+
sensors: dict[str, None | str | int | float] = {}
|
36
|
+
status: None | int = None
|
37
|
+
text: None | str = None
|
38
|
+
|
39
|
+
_LOGGER.debug("Request received: %s", passkey)
|
39
40
|
try:
|
40
|
-
|
41
|
-
for
|
42
|
-
if passkey ==
|
43
|
-
|
41
|
+
passkey = request.path[-8:]
|
42
|
+
for ref_passkey, ref_device in CCLServer.devices.items():
|
43
|
+
if passkey == ref_passkey:
|
44
|
+
device = ref_device
|
44
45
|
break
|
45
|
-
assert isinstance(
|
46
|
+
assert isinstance(device, CCLDevice), 404
|
46
47
|
|
47
48
|
assert request.content_type == "application/json", 400
|
48
49
|
assert 0 < request.content_length <= 5000, 400
|
49
50
|
|
50
|
-
|
51
|
+
body = await request.json()
|
51
52
|
|
52
53
|
except Exception as err: # pylint: disable=broad-exception-caught
|
53
|
-
|
54
|
-
if
|
55
|
-
|
56
|
-
elif
|
57
|
-
|
54
|
+
status = err.args[0]
|
55
|
+
if status == 400:
|
56
|
+
text = "400 Bad Request"
|
57
|
+
elif status == 404:
|
58
|
+
text = "404 Not Found"
|
58
59
|
else:
|
59
|
-
|
60
|
-
|
60
|
+
status = 500
|
61
|
+
text = "500 Internal Server Error"
|
61
62
|
_LOGGER.debug("Request exception occured: %s", err)
|
62
|
-
return web.Response(status=
|
63
|
+
return web.Response(status=status, text=text)
|
63
64
|
|
64
|
-
|
65
|
-
for key, value in _body.items():
|
65
|
+
for key, value in body.items():
|
66
66
|
if key in CCL_DEVICE_INFO_TYPES:
|
67
|
-
|
67
|
+
info.setdefault(key, value)
|
68
68
|
elif key in CCL_SENSORS:
|
69
|
-
|
69
|
+
sensors.setdefault(key, value)
|
70
70
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
_LOGGER.debug("Request processed: %s",
|
76
|
-
return web.Response(status=
|
71
|
+
device.update_info(info)
|
72
|
+
device.update_sensors(sensors)
|
73
|
+
status = 200
|
74
|
+
text = "200 OK"
|
75
|
+
_LOGGER.debug("Request processed: %s", passkey)
|
76
|
+
return web.Response(status=status, text=text)
|
77
77
|
|
78
78
|
app = web.Application()
|
79
|
-
app.add_routes([web.get(
|
79
|
+
app.add_routes([web.get("/{passkey}", handler)])
|
80
80
|
runner = web.AppRunner(app)
|
81
81
|
|
82
82
|
@staticmethod
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: aioccl
|
3
|
-
Version: 2024.12.
|
3
|
+
Version: 2024.12.12
|
4
4
|
Summary: A Python library for CCL API server
|
5
5
|
Home-page: https://github.com/fkiscd/aioccl
|
6
6
|
Download-URL: https://github.com/fkiscd/aioccl
|
@@ -17,7 +17,6 @@ Classifier: Programming Language :: Python :: 3.12
|
|
17
17
|
Description-Content-Type: text/markdown
|
18
18
|
License-File: LICENSE
|
19
19
|
Requires-Dist: aiohttp>3
|
20
|
-
Requires-Dist: aiohttp_cors>=0.7.0
|
21
20
|
|
22
21
|
# aioCCL
|
23
22
|
A Python library for CCL API server
|
@@ -0,0 +1,9 @@
|
|
1
|
+
aioccl/__init__.py,sha256=XnegKtbvHvUTwVuuWNLb4LB9ZuGGar0xrZYOqk7scyg,133
|
2
|
+
aioccl/device.py,sha256=TCp4QAP89ZHMNF3RqDMtCfTNRNqFGji_EiID3CtyJjs,6644
|
3
|
+
aioccl/sensor.py,sha256=k89-wHjhYN2yv1bJYn8J26KqB8YHE7zlSTPpHW8CjDA,16910
|
4
|
+
aioccl/server.py,sha256=IVBpFnpMkBndO5GK8Chl5WIArqw99LJ_Nln8cWT-4RQ,3237
|
5
|
+
aioccl-2024.12.12.dist-info/LICENSE,sha256=PBRsTHchx7o0TQ2R2ktEAfFmn1iNHTxcOP_xaeuSAuo,11349
|
6
|
+
aioccl-2024.12.12.dist-info/METADATA,sha256=MghnFaNkzQ9JUhwbcMM0pyLUZzEaONzQQA9eSfvri2A,734
|
7
|
+
aioccl-2024.12.12.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
8
|
+
aioccl-2024.12.12.dist-info/top_level.txt,sha256=-xIJfyfXTaW_EH7XCIHyxjSSSwjLmawa2qhsrHZH1vA,7
|
9
|
+
aioccl-2024.12.12.dist-info/RECORD,,
|
@@ -1,9 +0,0 @@
|
|
1
|
-
aioccl/__init__.py,sha256=XnegKtbvHvUTwVuuWNLb4LB9ZuGGar0xrZYOqk7scyg,133
|
2
|
-
aioccl/device.py,sha256=1BpnQSdCjHour9nvWF6sFkKillBlE-Iv96EK_niVS5w,5461
|
3
|
-
aioccl/sensor.py,sha256=k89-wHjhYN2yv1bJYn8J26KqB8YHE7zlSTPpHW8CjDA,16910
|
4
|
-
aioccl/server.py,sha256=a4Mjo2KNSOuCcna1YEv6SYzJx-h0a_Qq_EbyCogTHr4,3209
|
5
|
-
aioccl-2024.12.7.dist-info/LICENSE,sha256=PBRsTHchx7o0TQ2R2ktEAfFmn1iNHTxcOP_xaeuSAuo,11349
|
6
|
-
aioccl-2024.12.7.dist-info/METADATA,sha256=t_k46fKsOfcx0qz-Sga3cN4Ryv7pCSIfQu0sgRhxD9A,768
|
7
|
-
aioccl-2024.12.7.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
8
|
-
aioccl-2024.12.7.dist-info/top_level.txt,sha256=-xIJfyfXTaW_EH7XCIHyxjSSSwjLmawa2qhsrHZH1vA,7
|
9
|
-
aioccl-2024.12.7.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|