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 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
- 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()
35
47
 
36
48
  @property
37
49
  def passkey(self) -> str:
38
50
  """Return the passkey."""
39
- return self._passkey
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
- try:
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._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"]
49
64
 
50
65
  @property
51
66
  def name(self) -> str | None:
52
67
  """Return the display name."""
53
- if self._device_id is not None:
54
- return self._model + " - " + self._device_id
55
- return self._model
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._mac_address
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._model
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._fw_ver
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, info: dict[str, None | str]) -> None:
97
+ def update_info(self, new_info: dict[str, None | str]) -> None:
83
98
  """Add or update device info."""
84
- self._mac_address = info.get("mac_address")
85
- self._model = info.get("model")
86
- 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()
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._binary_sensors:
108
+ if key not in self.binary_sensors:
93
109
  self._binary_sensors[key] = CCLSensor(key)
94
- self._new_sensors.append(self._binary_sensors[key])
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._sensors:
113
+ if key not in self.sensors:
98
114
  self._sensors[key] = CCLSensor(key)
99
- self._new_sensors.append(self._sensors[key])
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
- 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:
107
135
  """Register callback, called when Sensor changes state."""
108
- self._update_callbacks.add(callback)
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.discard(callback)
140
+ self._update_callbacks.pop(sensor_key, None)
113
141
 
114
- def _publish_updates(self) -> None:
142
+ def _publish_updates(self) -> int:
115
143
  """Schedule call all registered callbacks."""
116
- try:
117
- for callback in self._update_callbacks:
144
+ count = 0
145
+ for sensor_key, callback in self._update_callbacks.items():
146
+ try:
118
147
  callback()
119
- except Exception as err: # pylint: disable=broad-exception-caught
120
- _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
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("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/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.7
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,,