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 +9 -9
- CasambiBt/_client.py +36 -14
- CasambiBt/_unit.py +20 -2
- {casambi_bt_revamped-0.3.7.dev14.dist-info → casambi_bt_revamped-0.3.11.dist-info}/METADATA +6 -6
- {casambi_bt_revamped-0.3.7.dev14.dist-info → casambi_bt_revamped-0.3.11.dist-info}/RECORD +8 -8
- {casambi_bt_revamped-0.3.7.dev14.dist-info → casambi_bt_revamped-0.3.11.dist-info}/WHEEL +1 -1
- {casambi_bt_revamped-0.3.7.dev14.dist-info → casambi_bt_revamped-0.3.11.dist-info}/licenses/LICENSE +0 -0
- {casambi_bt_revamped-0.3.7.dev14.dist-info → casambi_bt_revamped-0.3.11.dist-info}/top_level.txt +0 -0
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
|
|
9
|
+
from anyio import Path
|
|
10
10
|
|
|
11
11
|
_LOGGER = logging.getLogger(__name__)
|
|
12
12
|
|
|
13
|
-
CACHE_PATH_DEFAULT: Final =
|
|
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
|
|
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:
|
|
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:
|
|
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,
|
|
30
|
-
self._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) ->
|
|
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 =
|
|
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,
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
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
|
-
|
|
273
|
-
|
|
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.
|
|
4
|
-
Summary:
|
|
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
|
|
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:
|
|
19
|
+
Requires-Dist: anyio>=4.10.0
|
|
20
20
|
Dynamic: license-file
|
|
21
21
|
|
|
22
22
|

|
|
23
23
|
[](https://discord.gg/jgZVugfx)
|
|
24
24
|
|
|
25
|
-
# Casambi Bluetooth Revamped -
|
|
25
|
+
# Casambi Bluetooth Revamped - Python library for Casambi networks
|
|
26
26
|
|
|
27
|
-
This is
|
|
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=
|
|
2
|
+
CasambiBt/_cache.py,sha256=3bQil8vhSy4f4sf9JusMfEdQC7d3cJuva9qHhyKro-0,3808
|
|
3
3
|
CasambiBt/_casambi.py,sha256=AfyuzEU2ylJOGLmZ87Qft-aNXI_JK8Ng9Tfk4fWYOwo,34345
|
|
4
|
-
CasambiBt/_client.py,sha256=
|
|
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=
|
|
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.
|
|
15
|
-
casambi_bt_revamped-0.3.
|
|
16
|
-
casambi_bt_revamped-0.3.
|
|
17
|
-
casambi_bt_revamped-0.3.
|
|
18
|
-
casambi_bt_revamped-0.3.
|
|
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,,
|
{casambi_bt_revamped-0.3.7.dev14.dist-info → casambi_bt_revamped-0.3.11.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{casambi_bt_revamped-0.3.7.dev14.dist-info → casambi_bt_revamped-0.3.11.dist-info}/top_level.txt
RENAMED
|
File without changes
|