aioccl 2024.12.6__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 -49
- aioccl/sensor.py +1 -1
- aioccl/server.py +35 -35
- {aioccl-2024.12.6.dist-info → aioccl-2024.12.12.dist-info}/METADATA +1 -2
- aioccl-2024.12.12.dist-info/RECORD +9 -0
- aioccl-2024.12.6.dist-info/RECORD +0 -9
- {aioccl-2024.12.6.dist-info → aioccl-2024.12.12.dist-info}/LICENSE +0 -0
- {aioccl-2024.12.6.dist-info → aioccl-2024.12.12.dist-info}/WHEEL +0 -0
- {aioccl-2024.12.6.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,60 +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
|
-
|
32
18
|
|
33
19
|
def __init__(self, passkey: str):
|
34
20
|
"""Initialize a CCL device."""
|
35
|
-
|
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()
|
36
47
|
|
37
48
|
@property
|
38
49
|
def passkey(self) -> str:
|
39
50
|
"""Return the passkey."""
|
40
|
-
return self.
|
51
|
+
return self._info["passkey"]
|
41
52
|
|
42
53
|
@property
|
43
54
|
def device_id(self) -> str | None:
|
44
55
|
"""Return the device ID."""
|
45
|
-
|
46
|
-
self._device_id = self._mac_address.replace(":", "").lower()[-6:]
|
47
|
-
except Exception: # pylint: disable=broad-exception-caught
|
56
|
+
if self.mac_address is None:
|
48
57
|
return None
|
49
|
-
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"]
|
50
64
|
|
51
65
|
@property
|
52
66
|
def name(self) -> str | None:
|
53
67
|
"""Return the display name."""
|
54
|
-
if self.
|
55
|
-
return self.
|
56
|
-
return self.
|
68
|
+
if self.device_id is not None:
|
69
|
+
return self.model + " - " + self.device_id
|
70
|
+
return self._info["model"]
|
57
71
|
|
58
72
|
@property
|
59
73
|
def mac_address(self) -> str | None:
|
60
74
|
"""Return the MAC address."""
|
61
|
-
return self.
|
75
|
+
return self._info["mac_address"]
|
62
76
|
|
63
77
|
@property
|
64
78
|
def model(self) -> str | None:
|
65
79
|
"""Return the model."""
|
66
|
-
return self.
|
80
|
+
return self._info["model"]
|
67
81
|
|
68
82
|
@property
|
69
83
|
def fw_ver(self) -> str | None:
|
70
84
|
"""Return the firmware version."""
|
71
|
-
return self.
|
85
|
+
return self._info["fw_ver"]
|
72
86
|
|
73
87
|
@property
|
74
88
|
def binary_sensors(self) -> dict[str, CCLSensor] | None:
|
@@ -80,45 +94,66 @@ class CCLDevice:
|
|
80
94
|
"""Store sensor data under this device."""
|
81
95
|
return self._sensors
|
82
96
|
|
83
|
-
def update_info(self,
|
97
|
+
def update_info(self, new_info: dict[str, None | str]) -> None:
|
84
98
|
"""Add or update device info."""
|
85
|
-
|
86
|
-
|
87
|
-
|
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()
|
88
103
|
|
89
104
|
def update_sensors(self, sensors: dict[str, None | str | int | float]) -> None:
|
90
105
|
"""Add or update all sensor values."""
|
91
106
|
for key, value in sensors.items():
|
92
107
|
if CCL_SENSORS.get(key).binary:
|
93
|
-
if key not in self.
|
108
|
+
if key not in self.binary_sensors:
|
94
109
|
self._binary_sensors[key] = CCLSensor(key)
|
95
|
-
self._new_sensors.append(self.
|
110
|
+
self._new_sensors.append(self.binary_sensors[key])
|
96
111
|
self._binary_sensors[key].value = value
|
97
112
|
else:
|
98
|
-
if key not in self.
|
113
|
+
if key not in self.sensors:
|
99
114
|
self._sensors[key] = CCLSensor(key)
|
100
|
-
self._new_sensors.append(self.
|
115
|
+
self._new_sensors.append(self.sensors[key])
|
101
116
|
self._sensors[key].value = value
|
102
|
-
self._publish_new_sensors()
|
103
|
-
self._publish_updates()
|
104
|
-
self._last_updated_time = time.monotonic()
|
105
|
-
_LOGGER.debug("Sensors Updated: %s", self._last_updated_time)
|
106
117
|
|
107
|
-
|
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:
|
108
135
|
"""Register callback, called when Sensor changes state."""
|
109
|
-
self._update_callbacks
|
136
|
+
self._update_callbacks[sensor_key] = callback
|
110
137
|
|
111
|
-
def remove_update_cb(self, callback: Callable[[], None]) -> None:
|
138
|
+
def remove_update_cb(self, sensor_key, callback: Callable[[], None]) -> None:
|
112
139
|
"""Remove previously registered callback."""
|
113
|
-
self._update_callbacks.
|
140
|
+
self._update_callbacks.pop(sensor_key, None)
|
114
141
|
|
115
|
-
def _publish_updates(self) ->
|
142
|
+
def _publish_updates(self) -> int:
|
116
143
|
"""Schedule call all registered callbacks."""
|
117
|
-
|
118
|
-
|
144
|
+
count = 0
|
145
|
+
for sensor_key, callback in self._update_callbacks.items():
|
146
|
+
try:
|
119
147
|
callback()
|
120
|
-
|
121
|
-
|
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
|
122
157
|
|
123
158
|
def register_new_binary_sensor_cb(self, callback: Callable[[], None]) -> None:
|
124
159
|
"""Register callback, called when Sensor changes state."""
|
@@ -138,6 +173,7 @@ class CCLDevice:
|
|
138
173
|
|
139
174
|
def _publish_new_sensors(self) -> None:
|
140
175
|
"""Schedule call all registered callbacks."""
|
176
|
+
count = 0
|
141
177
|
for sensor in self._new_sensors[:]:
|
142
178
|
try:
|
143
179
|
if sensor.binary:
|
@@ -147,5 +183,12 @@ class CCLDevice:
|
|
147
183
|
for callback in self._new_sensor_callbacks:
|
148
184
|
callback(sensor)
|
149
185
|
self._new_sensors.remove(sensor)
|
186
|
+
count += 1
|
150
187
|
except Exception as err: # pylint: disable=broad-exception-caught
|
151
|
-
_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/sensor.py
CHANGED
@@ -34,7 +34,7 @@ class CCLSensor:
|
|
34
34
|
@property
|
35
35
|
def compartment(self) -> None | str:
|
36
36
|
"""Decide which compartment it belongs to."""
|
37
|
-
if
|
37
|
+
if CCL_SENSORS[self._key].compartment in CCLDeviceCompartment:
|
38
38
|
return CCL_SENSORS[self._key].compartment.value
|
39
39
|
return None
|
40
40
|
|
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=uNavxCAu1ULuk6xEOGC4URuqckw14vDXM6i-mGwN6nY,5462
|
3
|
-
aioccl/sensor.py,sha256=tX07jxkE2I8VdfCH3vzBINMtg5kI5ke8oOLE3pXRUeE,16920
|
4
|
-
aioccl/server.py,sha256=a4Mjo2KNSOuCcna1YEv6SYzJx-h0a_Qq_EbyCogTHr4,3209
|
5
|
-
aioccl-2024.12.6.dist-info/LICENSE,sha256=PBRsTHchx7o0TQ2R2ktEAfFmn1iNHTxcOP_xaeuSAuo,11349
|
6
|
-
aioccl-2024.12.6.dist-info/METADATA,sha256=JgQI_OeeX_wPM4CArB02kqQdDqT6QgebURbAbmy2n9E,768
|
7
|
-
aioccl-2024.12.6.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
8
|
-
aioccl-2024.12.6.dist-info/top_level.txt,sha256=-xIJfyfXTaW_EH7XCIHyxjSSSwjLmawa2qhsrHZH1vA,7
|
9
|
-
aioccl-2024.12.6.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|