casambi-bt-revamped 0.3.7.dev14__py3-none-any.whl → 0.3.11__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.
CasambiBt/_cache.py CHANGED
@@ -6,28 +6,28 @@ import shutil
6
6
  from types import TracebackType
7
7
  from typing import Final
8
8
 
9
- from aiopath import AsyncPath # type: ignore
9
+ from anyio import Path
10
10
 
11
11
  _LOGGER = logging.getLogger(__name__)
12
12
 
13
- CACHE_PATH_DEFAULT: Final = AsyncPath(os.getcwd()) / "casambi-bt-store"
13
+ CACHE_PATH_DEFAULT: Final = Path(os.getcwd()) / "casambi-bt-store"
14
14
  CACHE_VERSION: Final = 2
15
15
 
16
- # We need a global lock since there could be multiple Caambi instances
16
+ # We need a global lock since there could be multiple Casambi instances
17
17
  # with their own cache instances pointing to the same folder.
18
18
  _cacheLock = asyncio.Lock()
19
19
 
20
20
 
21
- def _blocking_delete(path: AsyncPath) -> None:
21
+ def _blocking_delete(path: Path) -> None:
22
22
  shutil.rmtree(pathlib.Path(path))
23
23
 
24
24
 
25
25
  class Cache:
26
- def __init__(self, cachePath: AsyncPath | pathlib.Path | None) -> None:
26
+ def __init__(self, cachePath: Path | pathlib.Path | None) -> None:
27
27
  if cachePath is None:
28
28
  self._cachePath = CACHE_PATH_DEFAULT
29
- elif not isinstance(cachePath, AsyncPath):
30
- self._cachePath = AsyncPath(cachePath)
29
+ elif not isinstance(cachePath, Path):
30
+ self._cachePath = Path(cachePath)
31
31
  else:
32
32
  self._cachePath = cachePath
33
33
 
@@ -69,7 +69,7 @@ class Cache:
69
69
  await self._cachePath.mkdir(mode=0o700)
70
70
  await self._cacheVersionFile.write_text(str(CACHE_VERSION))
71
71
 
72
- async def __aenter__(self) -> AsyncPath:
72
+ async def __aenter__(self) -> Path:
73
73
  await _cacheLock.acquire()
74
74
 
75
75
  if self._uuid is None:
@@ -78,7 +78,7 @@ class Cache:
78
78
  try:
79
79
  await self._ensureCacheValid()
80
80
 
81
- cacheDir = AsyncPath(self._cachePath / self._uuid)
81
+ cacheDir = Path(self._cachePath / self._uuid)
82
82
  if not await cacheDir.exists():
83
83
  _LOGGER.debug("Creating cache entry for id %s", self._uuid)
84
84
  await cacheDir.mkdir()
CasambiBt/_client.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import inspect
2
3
  import logging
3
4
  import struct
4
5
  from binascii import b2a_hex as b2a
@@ -183,8 +184,15 @@ class CasambiClient:
183
184
 
184
185
  # Device will initiate key exchange, so listen for that
185
186
  self._logger.debug("Starting notify")
187
+ notify_kwargs: dict[str, Any] = {}
188
+ notify_params = inspect.signature(self._gattClient.start_notify).parameters
189
+ if "bluez" in notify_params:
190
+ notify_kwargs["bluez"] = {"use_start_notify": True}
191
+
186
192
  await self._gattClient.start_notify(
187
- CASA_AUTH_CHAR_UUID, self._queueCallback
193
+ CASA_AUTH_CHAR_UUID,
194
+ self._queueCallback,
195
+ **notify_kwargs,
188
196
  )
189
197
  finally:
190
198
  self._activityLock.release()
@@ -233,18 +241,23 @@ class CasambiClient:
233
241
  self._callbackQueue.put_nowait((handle, data))
234
242
 
235
243
  async def _processCallbacks(self) -> None:
236
- while True:
237
- handle, data = await self._callbackQueue.get()
238
-
239
- # Try to loose any races here.
240
- # Otherwise a state change caused by the last packet might not have been handled yet
241
- await asyncio.sleep(0.001)
242
- await self._activityLock.acquire()
243
- try:
244
- self._callbackMulitplexer(handle, data)
245
- finally:
246
- self._callbackQueue.task_done()
247
- self._activityLock.release()
244
+ try:
245
+ while True:
246
+ handle, data = await self._callbackQueue.get()
247
+
248
+ # Try to loose any races here.
249
+ # Otherwise a state change caused by the last packet might not have been handled yet
250
+ await asyncio.sleep(0.001)
251
+ await self._activityLock.acquire()
252
+ try:
253
+ self._callbackMulitplexer(handle, data)
254
+ finally:
255
+ self._callbackQueue.task_done()
256
+ self._activityLock.release()
257
+ except asyncio.CancelledError:
258
+ # Task cancelled during shutdown; log at debug and exit cleanly.
259
+ self._logger.debug("Callback processing task cancelled during shutdown.")
260
+ raise
248
261
 
249
262
  def _callbackMulitplexer(
250
263
  self, handle: BleakGATTCharacteristic, data: bytes
@@ -759,8 +772,17 @@ class CasambiClient:
759
772
  self._logger.info("Disconnecting...")
760
773
 
761
774
  if self._callbackTask is not None:
775
+ # Cancel and await the background callback task to avoid
776
+ # 'Task was destroyed but it is pending' warnings.
762
777
  self._callbackTask.cancel()
763
- self._callbackTask = None
778
+ try:
779
+ await self._callbackTask
780
+ except asyncio.CancelledError:
781
+ pass
782
+ except Exception:
783
+ self._logger.debug("Callback task finished with exception during disconnect.", exc_info=True)
784
+ finally:
785
+ self._callbackTask = None
764
786
 
765
787
  if self._gattClient is not None and self._gattClient.is_connected:
766
788
  try:
CasambiBt/_unit.py CHANGED
@@ -111,6 +111,7 @@ class UnitState:
111
111
  self._colorsource: ColorSource | None = None
112
112
  self._xy: tuple[float, float] | None = None
113
113
  self._slider: int | None = None
114
+ self._onoff: bool | None = None
114
115
 
115
116
  def _check_range(
116
117
  self, value: int | float, min: int | float, max: int | float
@@ -269,9 +270,20 @@ class UnitState:
269
270
  def slider(self) -> None:
270
271
  self.slider = None
271
272
 
272
- def __repr__(self) -> str:
273
- return f"UnitState(dimmer={self.dimmer}, vertical={self._vertical}, rgb={self.rgb.__repr__()}, white={self.white}, temperature={self.temperature}, colorsource={self.colorsource}, xy={self.xy}, slider={self.slider})"
273
+ @property
274
+ def onoff(self) -> bool | None:
275
+ return self._onoff
276
+
277
+ @onoff.setter
278
+ def onoff(self, value: bool) -> None:
279
+ self._onoff = value
274
280
 
281
+ @onoff.deleter
282
+ def onoff(self) -> None:
283
+ self._onoff = None
284
+
285
+ def __repr__(self) -> str:
286
+ return f"UnitState(dimmer={self.dimmer}, vertical={self._vertical}, rgb={self.rgb.__repr__()}, white={self.white}, temperature={self.temperature}, colorsource={self.colorsource}, xy={self.xy}, slider={self.slider}, onoff={self.onoff})"
275
287
 
276
288
  # TODO: Make unit immutable (refactor state, on, online out of it)
277
289
  @dataclass(init=True, repr=True)
@@ -308,6 +320,8 @@ class Unit:
308
320
  @property
309
321
  def is_on(self) -> bool:
310
322
  """Determine whether the unit is turned on."""
323
+ if self.unitType.get_control(UnitControlType.ONOFF) and self._state:
324
+ return self._on and self._state.onoff is True
311
325
  if self.unitType.get_control(UnitControlType.DIMMER) and self._state:
312
326
  return (
313
327
  self._on and self._state.dimmer is not None and self._state.dimmer > 0
@@ -385,6 +399,8 @@ class Unit:
385
399
  elif c.type == UnitControlType.SLIDER and state.slider is not None:
386
400
  scale = UnitState.SLIDER_RESOLUTION - c.length
387
401
  scaledValue = state.slider >> scale
402
+ elif c.type == UnitControlType.ONOFF and state.onoff is not None:
403
+ scaledValue = 1 if state.onoff else 0
388
404
 
389
405
  # Use default if unsupported type or unset value in state
390
406
  else:
@@ -477,6 +493,8 @@ class Unit:
477
493
  elif c.type == UnitControlType.SLIDER:
478
494
  scale = UnitState.SLIDER_RESOLUTION - c.length
479
495
  self._state.slider = cInt << scale
496
+ elif c.type == UnitControlType.ONOFF:
497
+ self._state.onoff = cInt != 0
480
498
  elif c.type == UnitControlType.UNKOWN:
481
499
  # Might be useful for implementing more state types
482
500
  _LOGGER.debug(
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: casambi-bt-revamped
3
- Version: 0.3.7.dev14
4
- Summary: Enhanced Casambi Bluetooth client library with switch event support
3
+ Version: 0.3.11
4
+ Summary: Forked Casambi Bluetooth client library with switch event support, use original if no special need. https://github.com/lkempf/casambi-bt
5
5
  Home-page: https://github.com/rankjie/casambi-bt
6
6
  Author: rankjie
7
7
  Author-email: rankjie@gmail.com
@@ -12,19 +12,19 @@ Classifier: Development Status :: 4 - Beta
12
12
  Requires-Python: >=3.11
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
- Requires-Dist: bleak>=0.22
15
+ Requires-Dist: bleak!=2.0.0,>=0.22
16
16
  Requires-Dist: cryptography>=40.0.0
17
17
  Requires-Dist: httpx>=0.25
18
18
  Requires-Dist: bleak_retry_connector>=3.6.0
19
- Requires-Dist: aiopath==0.7.*
19
+ Requires-Dist: anyio>=4.10.0
20
20
  Dynamic: license-file
21
21
 
22
22
  ![PyPI](https://img.shields.io/pypi/v/casambi-bt-revamped)
23
23
  [![Discord](https://img.shields.io/discord/1186445089317326888)](https://discord.gg/jgZVugfx)
24
24
 
25
- # Casambi Bluetooth Revamped - Enhanced Python library for Casambi networks
25
+ # Casambi Bluetooth Revamped - Python library for Casambi networks
26
26
 
27
- This is an enhanced fork of the original [casambi-bt](https://github.com/lkempf/casambi-bt) library with additional features:
27
+ This is a customized fork of the original [casambi-bt](https://github.com/lkempf/casambi-bt) library with additional features and should only be used for special needs:
28
28
 
29
29
  - **Switch event support** - Receive button press/release events from Casambi switches
30
30
  - **Improved relay status handling** - Better support for relay units
@@ -1,18 +1,18 @@
1
1
  CasambiBt/__init__.py,sha256=TW445xSu5PV3TyMjJfwaA1JoWvQQ8LXhZgGdDTfWf3s,302
2
- CasambiBt/_cache.py,sha256=KZ2xbiHAHXUPa8Gw_75Nw9NL4QSY_sTWHbyYXYUDaB0,3865
2
+ CasambiBt/_cache.py,sha256=3bQil8vhSy4f4sf9JusMfEdQC7d3cJuva9qHhyKro-0,3808
3
3
  CasambiBt/_casambi.py,sha256=AfyuzEU2ylJOGLmZ87Qft-aNXI_JK8Ng9Tfk4fWYOwo,34345
4
- CasambiBt/_client.py,sha256=k2VQbmWpH6kOmGxF4zFvN0exFmWyKafgGEK3zORFTmc,31136
4
+ CasambiBt/_client.py,sha256=cmRXPxURJpp8xQM-FJe2NBMtfFFC7NnWExTaN65LQ5c,32142
5
5
  CasambiBt/_constants.py,sha256=_AxkG7Btxl4VeS6mO7GJW5Kc9dFs3s9sDmtJ83ZEKNw,359
6
6
  CasambiBt/_discover.py,sha256=H7HpiFYIy9ELvmPXXd_ck-5O5invJf15dDIRk-vO5IE,1696
7
7
  CasambiBt/_encryption.py,sha256=CLcoOOrggQqhJbnr_emBnEnkizpWDvb_0yFnitq4_FM,3831
8
8
  CasambiBt/_keystore.py,sha256=Jdiq0zMPDmhfpheSojKY6sTUpmVrvX_qOyO7yCYd3kw,2788
9
9
  CasambiBt/_network.py,sha256=Gh0n3FEcOUHUMuBXALwcb3tws-AofpYLegKIquqtZl4,14665
10
10
  CasambiBt/_operation.py,sha256=Q5UccsrtNp_B_wWqwH_3eLFW_yF6A55FMmfUKDk2WrI,1059
11
- CasambiBt/_unit.py,sha256=M-Q8-Xd3qjJSUEvsFtic8E4xDc_gtWYakbTGyoIA-P8,16377
11
+ CasambiBt/_unit.py,sha256=KR_dvVhCH8WIPGJgZYHyAPVA6ru0KmMOL5NgkxYHIUQ,17042
12
12
  CasambiBt/errors.py,sha256=0JgDjaKlAKDes0poWzA8nrTUYQ8qdNfBb8dfaqqzCRA,1664
13
13
  CasambiBt/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- casambi_bt_revamped-0.3.7.dev14.dist-info/licenses/LICENSE,sha256=TAIIitFxpxEDi6Iju7foW4TDQmWvC-IhLVLhl67jKmQ,11341
15
- casambi_bt_revamped-0.3.7.dev14.dist-info/METADATA,sha256=yHhhkhXLzxSnQ-6v5t2ndNjp9Gs-rlx_f_BdIrH6jHI,3049
16
- casambi_bt_revamped-0.3.7.dev14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
- casambi_bt_revamped-0.3.7.dev14.dist-info/top_level.txt,sha256=uNbqLjtecFosoFzpGAC89-5icikWODKI8rOjbi8v_sA,10
18
- casambi_bt_revamped-0.3.7.dev14.dist-info/RECORD,,
14
+ casambi_bt_revamped-0.3.11.dist-info/licenses/LICENSE,sha256=TAIIitFxpxEDi6Iju7foW4TDQmWvC-IhLVLhl67jKmQ,11341
15
+ casambi_bt_revamped-0.3.11.dist-info/METADATA,sha256=iZGwjYA0uCljUzih5kyGU63Fj-EZIE-acg7wE5fWHwM,3154
16
+ casambi_bt_revamped-0.3.11.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
17
+ casambi_bt_revamped-0.3.11.dist-info/top_level.txt,sha256=uNbqLjtecFosoFzpGAC89-5icikWODKI8rOjbi8v_sA,10
18
+ casambi_bt_revamped-0.3.11.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5