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 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
- self._passkey = passkey
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._passkey
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
- try:
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._device_id
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._device_id is not None:
55
- return self._model + " - " + self._device_id
56
- return self._model
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._mac_address
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._model
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._fw_ver
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, info: dict[str, None | str]) -> None:
97
+ def update_info(self, new_info: dict[str, None | str]) -> None:
84
98
  """Add or update device info."""
85
- self._mac_address = info.get("mac_address")
86
- self._model = info.get("model")
87
- self._fw_ver = info.get("fw_ver")
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._binary_sensors:
108
+ if key not in self.binary_sensors:
94
109
  self._binary_sensors[key] = CCLSensor(key)
95
- self._new_sensors.append(self._binary_sensors[key])
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._sensors:
113
+ if key not in self.sensors:
99
114
  self._sensors[key] = CCLSensor(key)
100
- self._new_sensors.append(self._sensors[key])
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
- def register_update_cb(self, callback: Callable[[], None]) -> None:
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.add(callback)
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.discard(callback)
140
+ self._update_callbacks.pop(sensor_key, None)
114
141
 
115
- def _publish_updates(self) -> None:
142
+ def _publish_updates(self) -> int:
116
143
  """Schedule call all registered callbacks."""
117
- try:
118
- for callback in self._update_callbacks:
144
+ count = 0
145
+ for sensor_key, callback in self._update_callbacks.items():
146
+ try:
119
147
  callback()
120
- except Exception as err: # pylint: disable=broad-exception-caught
121
- _LOGGER.warning("Error while publishing sensor updates: %s", err)
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("Error while publishing new sensors: %s", err)
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 isinstance(CCL_SENSORS[self._key].compartment, CCLDeviceCompartment):
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
- _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
-
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
- _passkey = request.path[-8:]
41
- for passkey in CCLServer.devices:
42
- if passkey == _passkey:
43
- _device = CCLServer.devices[_passkey]
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(_device, CCLDevice), 404
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
- _body = await request.json()
51
+ body = await request.json()
51
52
 
52
53
  except Exception as err: # pylint: disable=broad-exception-caught
53
- _status = err.args[0]
54
- if _status == 400:
55
- _text = "400 Bad Request"
56
- elif _status == 404:
57
- _text = "404 Not Found"
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
- _status = 500
60
- _text = "500 Internal Server Error"
60
+ status = 500
61
+ text = "500 Internal Server Error"
61
62
  _LOGGER.debug("Request exception occured: %s", err)
62
- return web.Response(status=_status, text=_text)
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
- _info.setdefault(key, value)
67
+ info.setdefault(key, value)
68
68
  elif key in CCL_SENSORS:
69
- _sensors.setdefault(key, value)
69
+ sensors.setdefault(key, value)
70
70
 
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)
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('/{passkey}', handler)])
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.6
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,,