casambi-bt-revamped 0.3.12.dev3__py3-none-any.whl → 0.3.12.dev5__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/_client.py +214 -47
- {casambi_bt_revamped-0.3.12.dev3.dist-info → casambi_bt_revamped-0.3.12.dev5.dist-info}/METADATA +1 -1
- {casambi_bt_revamped-0.3.12.dev3.dist-info → casambi_bt_revamped-0.3.12.dev5.dist-info}/RECORD +6 -6
- {casambi_bt_revamped-0.3.12.dev3.dist-info → casambi_bt_revamped-0.3.12.dev5.dist-info}/WHEEL +0 -0
- {casambi_bt_revamped-0.3.12.dev3.dist-info → casambi_bt_revamped-0.3.12.dev5.dist-info}/licenses/LICENSE +0 -0
- {casambi_bt_revamped-0.3.12.dev3.dist-info → casambi_bt_revamped-0.3.12.dev5.dist-info}/top_level.txt +0 -0
CasambiBt/_client.py
CHANGED
|
@@ -100,6 +100,8 @@ class CasambiClient:
|
|
|
100
100
|
# Determined at runtime by inspecting GATT services/characteristics.
|
|
101
101
|
self._protocolMode: ProtocolMode | None = None
|
|
102
102
|
self._dataCharUuid: str | None = None
|
|
103
|
+
# EVO only: protocolVersion from the device-provided NodeInfo (byte1).
|
|
104
|
+
self._deviceProtocolVersion: int | None = None
|
|
103
105
|
|
|
104
106
|
# Classic protocol state
|
|
105
107
|
self._classicConnHash8: bytes | None = None
|
|
@@ -119,14 +121,29 @@ class CasambiClient:
|
|
|
119
121
|
def protocolMode(self) -> ProtocolMode | None:
|
|
120
122
|
return self._protocolMode
|
|
121
123
|
|
|
122
|
-
def _checkProtocolVersion(self, version: int) -> None:
|
|
124
|
+
def _checkProtocolVersion(self, version: int, *, source: str = "unknown") -> None:
|
|
125
|
+
strict = os.getenv("CASAMBI_BT_STRICT_PROTOCOL_VERSION", "").strip() in {
|
|
126
|
+
"1",
|
|
127
|
+
"true",
|
|
128
|
+
"TRUE",
|
|
129
|
+
"yes",
|
|
130
|
+
"YES",
|
|
131
|
+
}
|
|
123
132
|
if version < MIN_VERSION:
|
|
124
|
-
|
|
125
|
-
|
|
133
|
+
# Legacy protocol versions are intentionally allowed. We keep this check as a warning
|
|
134
|
+
# because packet layouts/handshakes may differ and we want actionable tester logs.
|
|
135
|
+
msg = (
|
|
136
|
+
f"Legacy protocol version detected ({source}={version}). "
|
|
137
|
+
f"Versions < {MIN_VERSION} are not fully verified; attempting to continue."
|
|
126
138
|
)
|
|
139
|
+
if strict:
|
|
140
|
+
raise UnsupportedProtocolVersion(msg)
|
|
141
|
+
self._logger.warning(msg)
|
|
142
|
+
return
|
|
127
143
|
if version > MAX_VERSION:
|
|
128
144
|
self._logger.warning(
|
|
129
|
-
"Version too new
|
|
145
|
+
"Version too new (%s=%i). Highest supported version is %i. Continue at your own risk.",
|
|
146
|
+
source,
|
|
130
147
|
version,
|
|
131
148
|
MAX_VERSION,
|
|
132
149
|
)
|
|
@@ -207,19 +224,57 @@ class CasambiClient:
|
|
|
207
224
|
self._logger.info(f"Connected to {self.address}")
|
|
208
225
|
self._connectionState = ConnectionState.CONNECTED
|
|
209
226
|
|
|
210
|
-
# Detect protocol mode
|
|
211
|
-
|
|
227
|
+
# Detect protocol mode.
|
|
228
|
+
#
|
|
229
|
+
# Important: Home Assistant wraps BleakClient (HaBleakClientWrapper) which does not implement
|
|
230
|
+
# `get_services()`. Therefore we use "try-read" probing instead of enumerating GATT services.
|
|
231
|
+
#
|
|
232
|
+
# Order:
|
|
233
|
+
# 1) Classic "non-conformant": CA51 (hash) + CA52 (data channel)
|
|
234
|
+
# 2) EVO: auth char read starts with 0x01 (NodeInfo)
|
|
235
|
+
# 3) Classic "conformant": auth char read returns connection hash (first 8 bytes used)
|
|
236
|
+
|
|
237
|
+
cloud_protocol = getattr(self._network, "protocolVersion", None)
|
|
238
|
+
ca51_prefix: bytes | None = None
|
|
239
|
+
ca51_err: str | None = None
|
|
240
|
+
auth_prefix: bytes | None = None
|
|
241
|
+
auth_err: str | None = None
|
|
242
|
+
device_nodeinfo_protocol: int | None = None
|
|
243
|
+
|
|
244
|
+
def _log_probe_summary(mode: str) -> None:
|
|
245
|
+
# One stable, high-signal line for testers.
|
|
246
|
+
self._logger.info(
|
|
247
|
+
"[CASAMBI_PROTOCOL_PROBE] address=%s mode=%s cloud_protocol=%s device_nodeinfo_protocol=%s "
|
|
248
|
+
"data_uuid=%s classic_hash8_present=%s auth_read_prefix=%s ca51_read_prefix=%s ca51_read_error=%s auth_read_error=%s",
|
|
249
|
+
self.address,
|
|
250
|
+
mode,
|
|
251
|
+
cloud_protocol,
|
|
252
|
+
device_nodeinfo_protocol,
|
|
253
|
+
self._dataCharUuid,
|
|
254
|
+
bool(classic_hash and len(classic_hash) >= 8),
|
|
255
|
+
auth_prefix,
|
|
256
|
+
ca51_prefix,
|
|
257
|
+
ca51_err,
|
|
258
|
+
auth_err,
|
|
259
|
+
)
|
|
212
260
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
261
|
+
classic_hash: bytes | None = None
|
|
262
|
+
try:
|
|
263
|
+
classic_hash = await self._gattClient.read_gatt_char(CASA_CLASSIC_HASH_CHAR_UUID)
|
|
264
|
+
ca51_prefix = b2a(classic_hash[:10]) if classic_hash else None
|
|
265
|
+
if self._logger.isEnabledFor(logging.DEBUG):
|
|
266
|
+
self._logger.debug(
|
|
267
|
+
"[CASAMBI_GATT_PROBE] read ca51 ok len=%d prefix=%s",
|
|
268
|
+
0 if classic_hash is None else len(classic_hash),
|
|
269
|
+
ca51_prefix,
|
|
270
|
+
)
|
|
271
|
+
except Exception as e:
|
|
272
|
+
classic_hash = None
|
|
273
|
+
ca51_err = type(e).__name__
|
|
274
|
+
if self._logger.isEnabledFor(logging.DEBUG):
|
|
275
|
+
self._logger.debug("[CASAMBI_GATT_PROBE] read ca51 fail err=%s", ca51_err)
|
|
220
276
|
|
|
221
|
-
|
|
222
|
-
if _has_char(CASA_CLASSIC_HASH_CHAR_UUID) and _has_char(CASA_CLASSIC_DATA_CHAR_UUID):
|
|
277
|
+
if classic_hash and len(classic_hash) >= 8:
|
|
223
278
|
if os.getenv("CASAMBI_BT_DISABLE_CLASSIC", "").strip() in {"1", "true", "TRUE", "yes", "YES"}:
|
|
224
279
|
raise ProtocolError("Classic protocol detected but disabled via CASAMBI_BT_DISABLE_CLASSIC=1")
|
|
225
280
|
|
|
@@ -232,7 +287,7 @@ class CasambiClient:
|
|
|
232
287
|
self._dataCharUuid = CASA_CLASSIC_DATA_CHAR_UUID
|
|
233
288
|
|
|
234
289
|
# Read connection hash (first 8 bytes are used for CMAC signing).
|
|
235
|
-
raw_hash =
|
|
290
|
+
raw_hash = classic_hash
|
|
236
291
|
if raw_hash is None or len(raw_hash) < 8:
|
|
237
292
|
raise ClassicHandshakeError(
|
|
238
293
|
f"Classic connection hash read failed/too short (len={0 if raw_hash is None else len(raw_hash)})."
|
|
@@ -247,34 +302,105 @@ class CasambiClient:
|
|
|
247
302
|
notify_params = inspect.signature(self._gattClient.start_notify).parameters
|
|
248
303
|
if "bluez" in notify_params:
|
|
249
304
|
notify_kwargs["bluez"] = {"use_start_notify": True}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
305
|
+
try:
|
|
306
|
+
await self._gattClient.start_notify(
|
|
307
|
+
CASA_CLASSIC_DATA_CHAR_UUID,
|
|
308
|
+
self._queueCallback,
|
|
309
|
+
**notify_kwargs,
|
|
310
|
+
)
|
|
311
|
+
except Exception as e:
|
|
312
|
+
# Some firmwares may expose Classic signing on the EVO UUID instead.
|
|
313
|
+
# Fall through to auth-char probing if CA52 isn't available.
|
|
314
|
+
if self._logger.isEnabledFor(logging.DEBUG):
|
|
315
|
+
self._logger.debug(
|
|
316
|
+
"[CASAMBI_GATT_PROBE] start_notify ca52 fail err=%s; trying auth UUID probing.",
|
|
317
|
+
type(e).__name__,
|
|
318
|
+
exc_info=True,
|
|
319
|
+
)
|
|
320
|
+
self._protocolMode = None
|
|
321
|
+
self._dataCharUuid = None
|
|
322
|
+
self._classicConnHash8 = None
|
|
323
|
+
# continue detection below
|
|
324
|
+
else:
|
|
325
|
+
# Classic has no EVO-style key exchange/auth; we can send immediately.
|
|
326
|
+
self._connectionState = ConnectionState.AUTHENTICATED
|
|
327
|
+
self._logger.info("Protocol mode selected: CLASSIC")
|
|
328
|
+
if self._logger.isEnabledFor(logging.DEBUG):
|
|
329
|
+
self._logger.debug("[CASAMBI_GATT_PROBE] start_notify ca52 ok")
|
|
330
|
+
self._logger.debug(
|
|
331
|
+
"[CASAMBI_CLASSIC_CONN_HASH] len=%d hash=%s",
|
|
332
|
+
len(self._classicConnHash8),
|
|
333
|
+
b2a(self._classicConnHash8),
|
|
334
|
+
)
|
|
335
|
+
_log_probe_summary("CLASSIC")
|
|
336
|
+
return
|
|
255
337
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
338
|
+
# Conformant devices can expose the Classic signed channel on the EVO-style UUID too.
|
|
339
|
+
first: bytes | None = None
|
|
340
|
+
try:
|
|
341
|
+
first = await self._gattClient.read_gatt_char(CASA_AUTH_CHAR_UUID)
|
|
342
|
+
auth_prefix = b2a(first[:10]) if first else None
|
|
259
343
|
if self._logger.isEnabledFor(logging.DEBUG):
|
|
260
344
|
self._logger.debug(
|
|
261
|
-
"[
|
|
262
|
-
len(
|
|
263
|
-
|
|
345
|
+
"[CASAMBI_GATT_PROBE] read auth ok len=%d first_byte=%s prefix=%s",
|
|
346
|
+
0 if first is None else len(first),
|
|
347
|
+
None if not first else f"0x{first[0]:02x}",
|
|
348
|
+
auth_prefix,
|
|
264
349
|
)
|
|
265
|
-
|
|
350
|
+
except Exception as e:
|
|
351
|
+
first = None
|
|
352
|
+
auth_err = type(e).__name__
|
|
353
|
+
if self._logger.isEnabledFor(logging.DEBUG):
|
|
354
|
+
self._logger.debug("[CASAMBI_GATT_PROBE] read auth fail err=%s", auth_err)
|
|
355
|
+
|
|
356
|
+
if first and len(first) >= 2 and first[0] == 0x01:
|
|
357
|
+
# EVO NodeInfo packet starts with 0x01.
|
|
358
|
+
device_nodeinfo_protocol = first[1]
|
|
359
|
+
self._deviceProtocolVersion = device_nodeinfo_protocol
|
|
360
|
+
mtu = unit = flags = None
|
|
361
|
+
nonce_prefix = None
|
|
362
|
+
if len(first) >= 23:
|
|
363
|
+
try:
|
|
364
|
+
mtu, unit, flags, nonce = struct.unpack_from(">BHH16s", first, 2)
|
|
365
|
+
nonce_prefix = b2a(nonce[:8])
|
|
366
|
+
except Exception:
|
|
367
|
+
if self._logger.isEnabledFor(logging.DEBUG):
|
|
368
|
+
self._logger.debug("Failed to parse NodeInfo fields for logging.", exc_info=True)
|
|
266
369
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
370
|
+
self._logger.info(
|
|
371
|
+
"[CASAMBI_EVO_NODEINFO] cloud_protocol=%s device_protocol=%s mtu=%s unit=%s flags=%s nonce_prefix=%s len=%d prefix=%s",
|
|
372
|
+
cloud_protocol,
|
|
373
|
+
device_nodeinfo_protocol,
|
|
374
|
+
mtu,
|
|
375
|
+
unit,
|
|
376
|
+
None if flags is None else f"0x{flags:04x}",
|
|
377
|
+
nonce_prefix,
|
|
378
|
+
len(first),
|
|
379
|
+
b2a(first[: min(len(first), 32)]),
|
|
380
|
+
)
|
|
381
|
+
if cloud_protocol is not None and device_nodeinfo_protocol != cloud_protocol:
|
|
382
|
+
self._logger.warning(
|
|
383
|
+
"[CASAMBI_EVO_NODEINFO_MISMATCH] cloud_protocol=%s device_protocol=%s",
|
|
384
|
+
cloud_protocol,
|
|
385
|
+
device_nodeinfo_protocol,
|
|
386
|
+
)
|
|
387
|
+
if len(first) < 23:
|
|
388
|
+
self._logger.warning(
|
|
389
|
+
"[CASAMBI_EVO_NODEINFO_SHORT] len=%d cloud_protocol=%s device_protocol=%s prefix=%s",
|
|
390
|
+
len(first),
|
|
391
|
+
cloud_protocol,
|
|
392
|
+
device_nodeinfo_protocol,
|
|
393
|
+
b2a(first[: min(len(first), 32)]),
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
self._protocolMode = ProtocolMode.EVO
|
|
397
|
+
self._dataCharUuid = CASA_AUTH_CHAR_UUID
|
|
398
|
+
self._checkProtocolVersion(device_nodeinfo_protocol, source="device_nodeinfo")
|
|
399
|
+
self._logger.info("Protocol mode selected: EVO")
|
|
400
|
+
_log_probe_summary("EVO")
|
|
401
|
+
return
|
|
277
402
|
|
|
403
|
+
if first is not None:
|
|
278
404
|
# Otherwise, treat as Classic conformant: read provides connection hash.
|
|
279
405
|
if os.getenv("CASAMBI_BT_DISABLE_CLASSIC", "").strip() in {"1", "true", "TRUE", "yes", "YES"}:
|
|
280
406
|
raise ProtocolError("Classic protocol detected but disabled via CASAMBI_BT_DISABLE_CLASSIC=1")
|
|
@@ -282,9 +408,9 @@ class CasambiClient:
|
|
|
282
408
|
raise ClassicKeysMissingError(
|
|
283
409
|
"Classic protocol detected but network has no visitorKey/managerKey."
|
|
284
410
|
)
|
|
285
|
-
if
|
|
411
|
+
if len(first) < 8:
|
|
286
412
|
raise ClassicHandshakeError(
|
|
287
|
-
f"Classic connection hash read failed/too short (len={
|
|
413
|
+
f"Classic connection hash read failed/too short (len={len(first)})."
|
|
288
414
|
)
|
|
289
415
|
|
|
290
416
|
self._protocolMode = ProtocolMode.CLASSIC
|
|
@@ -305,15 +431,17 @@ class CasambiClient:
|
|
|
305
431
|
self._connectionState = ConnectionState.AUTHENTICATED
|
|
306
432
|
self._logger.info("Protocol mode selected: CLASSIC")
|
|
307
433
|
if self._logger.isEnabledFor(logging.DEBUG):
|
|
434
|
+
self._logger.debug("[CASAMBI_GATT_PROBE] start_notify auth ok (classic conformant)")
|
|
308
435
|
self._logger.debug(
|
|
309
436
|
"[CASAMBI_CLASSIC_CONN_HASH] len=%d hash=%s",
|
|
310
437
|
len(self._classicConnHash8),
|
|
311
438
|
b2a(self._classicConnHash8),
|
|
312
439
|
)
|
|
440
|
+
_log_probe_summary("CLASSIC")
|
|
313
441
|
return
|
|
314
442
|
|
|
315
443
|
raise ProtocolError(
|
|
316
|
-
"No supported Casambi characteristics found (Classic ca51/ca52 or EVO/Classic
|
|
444
|
+
"No supported Casambi characteristics found (Classic ca51/ca52 or EVO/Classic-conformant auth char)."
|
|
317
445
|
)
|
|
318
446
|
|
|
319
447
|
def _on_disconnect(self, client: BleakClient) -> None:
|
|
@@ -333,15 +461,54 @@ class CasambiClient:
|
|
|
333
461
|
try:
|
|
334
462
|
# Initiate communication with device
|
|
335
463
|
firstResp = await self._gattClient.read_gatt_char(CASA_AUTH_CHAR_UUID)
|
|
336
|
-
self._logger.
|
|
464
|
+
if self._logger.isEnabledFor(logging.DEBUG):
|
|
465
|
+
self._logger.debug(
|
|
466
|
+
"[CASAMBI_EVO_NODEINFO_RAW] len=%d prefix=%s",
|
|
467
|
+
len(firstResp),
|
|
468
|
+
b2a(firstResp[: min(len(firstResp), 32)]),
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
cloud_protocol = getattr(self._network, "protocolVersion", None)
|
|
472
|
+
expected_protocol = self._deviceProtocolVersion or cloud_protocol
|
|
473
|
+
|
|
474
|
+
# EVO key exchange expects the NodeInfo packet (0x01 ...).
|
|
475
|
+
if len(firstResp) < 2 or firstResp[0] != 0x01:
|
|
476
|
+
self._logger.error(
|
|
477
|
+
"[CASAMBI_EVO_NODEINFO_UNEXPECTED] expected_prefix=01 len=%d prefix=%s",
|
|
478
|
+
len(firstResp),
|
|
479
|
+
b2a(firstResp[: min(len(firstResp), 32)]),
|
|
480
|
+
)
|
|
481
|
+
raise ProtocolError("Unexpected NodeInfo response while starting key exchange.")
|
|
482
|
+
|
|
483
|
+
device_protocol = firstResp[1]
|
|
484
|
+
self._deviceProtocolVersion = device_protocol
|
|
485
|
+
self._checkProtocolVersion(device_protocol, source="device_nodeinfo")
|
|
486
|
+
|
|
487
|
+
if expected_protocol is not None and device_protocol != expected_protocol:
|
|
488
|
+
self._logger.warning(
|
|
489
|
+
"[CASAMBI_EVO_NODEINFO_MISMATCH] expected_protocol=%s cloud_protocol=%s device_protocol=%s",
|
|
490
|
+
expected_protocol,
|
|
491
|
+
cloud_protocol,
|
|
492
|
+
device_protocol,
|
|
493
|
+
)
|
|
494
|
+
elif cloud_protocol is not None and device_protocol != cloud_protocol:
|
|
495
|
+
# Keep this separate to catch cloud/device mismatches even if we didn't have an expected protocol set.
|
|
496
|
+
self._logger.warning(
|
|
497
|
+
"[CASAMBI_EVO_NODEINFO_MISMATCH] expected_protocol=%s cloud_protocol=%s device_protocol=%s",
|
|
498
|
+
expected_protocol,
|
|
499
|
+
cloud_protocol,
|
|
500
|
+
device_protocol,
|
|
501
|
+
)
|
|
337
502
|
|
|
338
|
-
|
|
339
|
-
if not (
|
|
340
|
-
firstResp[0] == 0x1 and firstResp[1] == self._network.protocolVersion
|
|
341
|
-
):
|
|
503
|
+
if len(firstResp) < 23:
|
|
342
504
|
self._logger.error(
|
|
343
|
-
"
|
|
505
|
+
"[CASAMBI_EVO_NODEINFO_SHORT] len=%d cloud_protocol=%s device_protocol=%s prefix=%s",
|
|
506
|
+
len(firstResp),
|
|
507
|
+
cloud_protocol,
|
|
508
|
+
device_protocol,
|
|
509
|
+
b2a(firstResp[: min(len(firstResp), 32)]),
|
|
344
510
|
)
|
|
511
|
+
raise ProtocolError("NodeInfo response too short while starting key exchange.")
|
|
345
512
|
|
|
346
513
|
# Parse device info
|
|
347
514
|
self._mtu, self._unit, self._flags, self._nonce = struct.unpack_from(
|
{casambi_bt_revamped-0.3.12.dev3.dist-info → casambi_bt_revamped-0.3.12.dev5.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: casambi-bt-revamped
|
|
3
|
-
Version: 0.3.12.
|
|
3
|
+
Version: 0.3.12.dev5
|
|
4
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
|
{casambi_bt_revamped-0.3.12.dev3.dist-info → casambi_bt_revamped-0.3.12.dev5.dist-info}/RECORD
RENAMED
|
@@ -2,7 +2,7 @@ CasambiBt/__init__.py,sha256=TW445xSu5PV3TyMjJfwaA1JoWvQQ8LXhZgGdDTfWf3s,302
|
|
|
2
2
|
CasambiBt/_cache.py,sha256=3bQil8vhSy4f4sf9JusMfEdQC7d3cJuva9qHhyKro-0,3808
|
|
3
3
|
CasambiBt/_casambi.py,sha256=TN4ecgjm95nSJ4h9TsKayNn577Y82fdsGK4IGUZF23Q,40666
|
|
4
4
|
CasambiBt/_classic_crypto.py,sha256=6DcCOdjLQo7k2cOOutNdUKupykOG_E2TDDwg6fH-ODM,998
|
|
5
|
-
CasambiBt/_client.py,sha256=
|
|
5
|
+
CasambiBt/_client.py,sha256=AASUN9OvmTIg9IeYMEvLI8kBEYbV9FapIuyDXGZMpME,57883
|
|
6
6
|
CasambiBt/_constants.py,sha256=sbElg5W8eeQvvL1rHn_E0jhP1wOrrabc7dFLLnlDMsU,810
|
|
7
7
|
CasambiBt/_discover.py,sha256=jLc6H69JddrCURgtANZEjws6_UbSzXJtvJkbKTaIUHY,1849
|
|
8
8
|
CasambiBt/_encryption.py,sha256=CLcoOOrggQqhJbnr_emBnEnkizpWDvb_0yFnitq4_FM,3831
|
|
@@ -14,8 +14,8 @@ CasambiBt/_switch_events.py,sha256=S8OD0dBcw5T4J2C7qfmOQMnTJ7omIXRUYv4PqDOB87E,1
|
|
|
14
14
|
CasambiBt/_unit.py,sha256=KIpvUT_Wm-O2Lmb1JVnNO625-j5j7GqufmZzfTR-jW0,18587
|
|
15
15
|
CasambiBt/errors.py,sha256=1L_Q8og_N_BRYEKizghAQXr6tihlHykFgtcCHUDcBas,1961
|
|
16
16
|
CasambiBt/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
-
casambi_bt_revamped-0.3.12.
|
|
18
|
-
casambi_bt_revamped-0.3.12.
|
|
19
|
-
casambi_bt_revamped-0.3.12.
|
|
20
|
-
casambi_bt_revamped-0.3.12.
|
|
21
|
-
casambi_bt_revamped-0.3.12.
|
|
17
|
+
casambi_bt_revamped-0.3.12.dev5.dist-info/licenses/LICENSE,sha256=TAIIitFxpxEDi6Iju7foW4TDQmWvC-IhLVLhl67jKmQ,11341
|
|
18
|
+
casambi_bt_revamped-0.3.12.dev5.dist-info/METADATA,sha256=mNRrJjPdZBbSvEJp9RBAYkv7wU0-znKwBhpR5XEXtLo,5877
|
|
19
|
+
casambi_bt_revamped-0.3.12.dev5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
20
|
+
casambi_bt_revamped-0.3.12.dev5.dist-info/top_level.txt,sha256=uNbqLjtecFosoFzpGAC89-5icikWODKI8rOjbi8v_sA,10
|
|
21
|
+
casambi_bt_revamped-0.3.12.dev5.dist-info/RECORD,,
|
{casambi_bt_revamped-0.3.12.dev3.dist-info → casambi_bt_revamped-0.3.12.dev5.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|