casambi-bt-revamped 0.3.12.dev6__py3-none-any.whl → 0.3.12.dev8__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 +142 -8
- CasambiBt/_network.py +28 -8
- CasambiBt/_version.py +1 -1
- {casambi_bt_revamped-0.3.12.dev6.dist-info → casambi_bt_revamped-0.3.12.dev8.dist-info}/METADATA +1 -1
- {casambi_bt_revamped-0.3.12.dev6.dist-info → casambi_bt_revamped-0.3.12.dev8.dist-info}/RECORD +8 -8
- {casambi_bt_revamped-0.3.12.dev6.dist-info → casambi_bt_revamped-0.3.12.dev8.dist-info}/WHEEL +0 -0
- {casambi_bt_revamped-0.3.12.dev6.dist-info → casambi_bt_revamped-0.3.12.dev8.dist-info}/licenses/LICENSE +0 -0
- {casambi_bt_revamped-0.3.12.dev6.dist-info → casambi_bt_revamped-0.3.12.dev8.dist-info}/top_level.txt +0 -0
CasambiBt/_client.py
CHANGED
|
@@ -142,6 +142,13 @@ class CasambiClient:
|
|
|
142
142
|
self._classicRxVerified = 0
|
|
143
143
|
self._classicRxUnverifiable = 0
|
|
144
144
|
self._classicRxParseFail = 0
|
|
145
|
+
self._classicRxType6 = 0
|
|
146
|
+
self._classicRxType7 = 0
|
|
147
|
+
self._classicRxType9 = 0
|
|
148
|
+
self._classicRxCmdStream = 0
|
|
149
|
+
self._classicRxUnknown = 0
|
|
150
|
+
# Per-kind sample counters to ensure we emit at least a few examples for reverse engineering.
|
|
151
|
+
self._classicRxKindSamples: dict[str, int] = {}
|
|
145
152
|
self._classicRxLastStatsTs = time.monotonic()
|
|
146
153
|
|
|
147
154
|
@property
|
|
@@ -259,17 +266,20 @@ class CasambiClient:
|
|
|
259
266
|
auth_err: str | None = None
|
|
260
267
|
device_nodeinfo_protocol: int | None = None
|
|
261
268
|
|
|
262
|
-
def _log_probe_summary(mode: str) -> None:
|
|
269
|
+
def _log_probe_summary(mode: str, *, classic_variant: str | None = None) -> None:
|
|
263
270
|
# One stable, high-signal line for testers.
|
|
264
271
|
self._logger.warning(
|
|
265
|
-
"[CASAMBI_PROTOCOL_PROBE] address=%s mode=%s cloud_protocol=%s nodeinfo_b1=%s "
|
|
266
|
-
"
|
|
272
|
+
"[CASAMBI_PROTOCOL_PROBE] address=%s mode=%s cloud_protocol=%s nodeinfo_b1=%s data_uuid=%s "
|
|
273
|
+
"classic_variant=%s ca51_hash8_present=%s conn_hash8_ready=%s "
|
|
274
|
+
"auth_read_prefix=%s ca51_read_prefix=%s ca51_read_error=%s auth_read_error=%s",
|
|
267
275
|
self.address,
|
|
268
276
|
mode,
|
|
269
277
|
cloud_protocol,
|
|
270
278
|
device_nodeinfo_protocol,
|
|
271
279
|
self._dataCharUuid,
|
|
280
|
+
classic_variant,
|
|
272
281
|
bool(classic_hash and len(classic_hash) >= 8),
|
|
282
|
+
self._classicConnHash8 is not None,
|
|
273
283
|
auth_prefix,
|
|
274
284
|
ca51_prefix,
|
|
275
285
|
ca51_err,
|
|
@@ -343,7 +353,21 @@ class CasambiClient:
|
|
|
343
353
|
len(self._classicConnHash8),
|
|
344
354
|
b2a(self._classicConnHash8),
|
|
345
355
|
)
|
|
346
|
-
|
|
356
|
+
self._logger.warning(
|
|
357
|
+
"[CASAMBI_CLASSIC_SELECTED] address=%s variant=ca52_legacy data_uuid=%s start_notify_uuid=%s header_mode=%s conn_hash8_prefix=%s",
|
|
358
|
+
self.address,
|
|
359
|
+
self._dataCharUuid,
|
|
360
|
+
CASA_CLASSIC_DATA_CHAR_UUID,
|
|
361
|
+
self._classicHeaderMode,
|
|
362
|
+
b2a(self._classicConnHash8),
|
|
363
|
+
)
|
|
364
|
+
self._logger.warning(
|
|
365
|
+
"[CASAMBI_CLASSIC_KEYS] visitor=%s manager=%s cloud_session_is_manager=%s",
|
|
366
|
+
self._network.classicVisitorKey() is not None,
|
|
367
|
+
self._network.classicManagerKey() is not None,
|
|
368
|
+
getattr(self._network, "isManager", lambda: False)(),
|
|
369
|
+
)
|
|
370
|
+
_log_probe_summary("CLASSIC", classic_variant="ca52_legacy")
|
|
347
371
|
return
|
|
348
372
|
|
|
349
373
|
# Conformant devices can expose the Classic signed channel on the EVO-style UUID too.
|
|
@@ -437,7 +461,21 @@ class CasambiClient:
|
|
|
437
461
|
len(self._classicConnHash8),
|
|
438
462
|
b2a(self._classicConnHash8),
|
|
439
463
|
)
|
|
440
|
-
|
|
464
|
+
self._logger.warning(
|
|
465
|
+
"[CASAMBI_CLASSIC_SELECTED] address=%s variant=auth_uuid_conformant data_uuid=%s start_notify_uuid=%s header_mode=%s conn_hash8_prefix=%s",
|
|
466
|
+
self.address,
|
|
467
|
+
self._dataCharUuid,
|
|
468
|
+
CASA_AUTH_CHAR_UUID,
|
|
469
|
+
self._classicHeaderMode,
|
|
470
|
+
b2a(self._classicConnHash8),
|
|
471
|
+
)
|
|
472
|
+
self._logger.warning(
|
|
473
|
+
"[CASAMBI_CLASSIC_KEYS] visitor=%s manager=%s cloud_session_is_manager=%s",
|
|
474
|
+
self._network.classicVisitorKey() is not None,
|
|
475
|
+
self._network.classicManagerKey() is not None,
|
|
476
|
+
getattr(self._network, "isManager", lambda: False)(),
|
|
477
|
+
)
|
|
478
|
+
_log_probe_summary("CLASSIC", classic_variant="auth_uuid_conformant")
|
|
441
479
|
return
|
|
442
480
|
|
|
443
481
|
_log_probe_summary("UNKNOWN")
|
|
@@ -832,6 +870,34 @@ class CasambiClient:
|
|
|
832
870
|
visitor_key = self._network.classicVisitorKey()
|
|
833
871
|
manager_key = self._network.classicManagerKey()
|
|
834
872
|
|
|
873
|
+
# Parse the command record for logs (u1.C1753e export format).
|
|
874
|
+
cmd_ordinal: int | None = None
|
|
875
|
+
cmd_div: int | None = None
|
|
876
|
+
cmd_target: int | None = None
|
|
877
|
+
cmd_lifetime: int | None = None
|
|
878
|
+
cmd_payload_len: int | None = None
|
|
879
|
+
try:
|
|
880
|
+
if len(command_bytes) >= 2:
|
|
881
|
+
typ = command_bytes[1]
|
|
882
|
+
cmd_ordinal = typ & 0x3F
|
|
883
|
+
has_div = (typ & 0x40) != 0
|
|
884
|
+
has_target = (typ & 0x80) != 0
|
|
885
|
+
p = 2
|
|
886
|
+
if has_div and p < len(command_bytes):
|
|
887
|
+
cmd_div = command_bytes[p]
|
|
888
|
+
p += 1
|
|
889
|
+
if has_target and p < len(command_bytes):
|
|
890
|
+
cmd_target = command_bytes[p]
|
|
891
|
+
p += 1
|
|
892
|
+
if p < len(command_bytes):
|
|
893
|
+
cmd_lifetime = command_bytes[p]
|
|
894
|
+
p += 1
|
|
895
|
+
if p <= len(command_bytes):
|
|
896
|
+
cmd_payload_len = len(command_bytes) - p
|
|
897
|
+
except Exception:
|
|
898
|
+
# If parsing fails, keep fields as None.
|
|
899
|
+
pass
|
|
900
|
+
|
|
835
901
|
# Key selection mirrors Android's intent:
|
|
836
902
|
# - Use manager key if our cloud session is manager and a managerKey exists.
|
|
837
903
|
# - Else use visitor key if present.
|
|
@@ -895,17 +961,34 @@ class CasambiClient:
|
|
|
895
961
|
else:
|
|
896
962
|
raise ProtocolError(f"Unknown Classic header mode: {header_mode}")
|
|
897
963
|
|
|
964
|
+
signed = key is not None
|
|
965
|
+
if not signed and self._logLimiter.allow("classic_tx_unsigned", burst=10, window_s=300.0):
|
|
966
|
+
self._logger.warning(
|
|
967
|
+
"[CASAMBI_CLASSIC_TX_UNSIGNED] reason=keys_missing visitor=%s manager=%s",
|
|
968
|
+
visitor_key is not None,
|
|
969
|
+
manager_key is not None,
|
|
970
|
+
)
|
|
971
|
+
|
|
898
972
|
# WARNING-level TX logs are intentional: they are needed for Classic reverse engineering.
|
|
899
973
|
# Keep payload logging minimal (prefix only).
|
|
900
974
|
if self._logLimiter.allow("classic_tx", burst=50, window_s=60.0):
|
|
975
|
+
auth_str = f"0x{auth_level:02x}" if header_mode == "conformant" else None
|
|
901
976
|
self._logger.warning(
|
|
902
|
-
"[CASAMBI_CLASSIC_TX] header=%s key=%s auth
|
|
977
|
+
"[CASAMBI_CLASSIC_TX] header=%s key=%s signed=%s auth=%s sig_len=%d seq=%s "
|
|
978
|
+
"cmd_len=%d cmd_ord=%s target=%s div=%s lifetime=%s payload_len=%s "
|
|
979
|
+
"total_len=%d prefix=%s",
|
|
903
980
|
header_mode,
|
|
904
981
|
key_name,
|
|
905
|
-
|
|
982
|
+
signed,
|
|
983
|
+
auth_str,
|
|
906
984
|
sig_len,
|
|
907
985
|
None if seq is None else f"0x{seq:04x}",
|
|
908
986
|
len(command_bytes),
|
|
987
|
+
cmd_ordinal,
|
|
988
|
+
cmd_target,
|
|
989
|
+
cmd_div,
|
|
990
|
+
cmd_lifetime,
|
|
991
|
+
cmd_payload_len,
|
|
909
992
|
len(pkt),
|
|
910
993
|
b2a(bytes(pkt[: min(len(pkt), 24)])),
|
|
911
994
|
)
|
|
@@ -1213,17 +1296,47 @@ class CasambiClient:
|
|
|
1213
1296
|
):
|
|
1214
1297
|
self._classicRxLastStatsTs = now
|
|
1215
1298
|
self._logger.warning(
|
|
1216
|
-
"[CASAMBI_CLASSIC_RX_STATS] frames=%d verified=%d unverifiable=%d parse_fail=%d header=%s"
|
|
1299
|
+
"[CASAMBI_CLASSIC_RX_STATS] frames=%d verified=%d unverifiable=%d parse_fail=%d header=%s "
|
|
1300
|
+
"type6=%d type7=%d type9=%d cmdstream=%d unknown=%d",
|
|
1217
1301
|
self._classicRxFrames,
|
|
1218
1302
|
self._classicRxVerified,
|
|
1219
1303
|
self._classicRxUnverifiable,
|
|
1220
1304
|
self._classicRxParseFail,
|
|
1221
1305
|
self._classicHeaderMode,
|
|
1306
|
+
self._classicRxType6,
|
|
1307
|
+
self._classicRxType7,
|
|
1308
|
+
self._classicRxType9,
|
|
1309
|
+
self._classicRxCmdStream,
|
|
1310
|
+
self._classicRxUnknown,
|
|
1222
1311
|
)
|
|
1223
1312
|
|
|
1224
1313
|
# If the payload starts with a known EVO packet type, reuse existing parsers.
|
|
1225
1314
|
packet_type = payload[0]
|
|
1226
1315
|
if packet_type in (IncommingPacketType.UnitState, IncommingPacketType.SwitchEvent, IncommingPacketType.NetworkConfig):
|
|
1316
|
+
kind = f"type{int(packet_type)}"
|
|
1317
|
+
if packet_type == IncommingPacketType.UnitState:
|
|
1318
|
+
self._classicRxType6 += 1
|
|
1319
|
+
kind = "type6_unitstate"
|
|
1320
|
+
elif packet_type == IncommingPacketType.SwitchEvent:
|
|
1321
|
+
self._classicRxType7 += 1
|
|
1322
|
+
kind = "type7_switch"
|
|
1323
|
+
else:
|
|
1324
|
+
self._classicRxType9 += 1
|
|
1325
|
+
kind = "type9_netconf"
|
|
1326
|
+
|
|
1327
|
+
# Emit a few per-kind examples for reverse engineering.
|
|
1328
|
+
if self._classicRxKindSamples.get(kind, 0) < 3:
|
|
1329
|
+
self._classicRxKindSamples[kind] = self._classicRxKindSamples.get(kind, 0) + 1
|
|
1330
|
+
self._logger.warning(
|
|
1331
|
+
"[CASAMBI_CLASSIC_RX_KIND] kind=%s header=%s verified=%s sig_len=%d seq=%s payload_prefix=%s",
|
|
1332
|
+
kind,
|
|
1333
|
+
best["mode"],
|
|
1334
|
+
verified,
|
|
1335
|
+
best["sig_len"],
|
|
1336
|
+
None if best["seq"] is None else f"0x{best['seq']:04x}",
|
|
1337
|
+
b2a(payload[: min(len(payload), 32)]),
|
|
1338
|
+
)
|
|
1339
|
+
|
|
1227
1340
|
if self._logger.isEnabledFor(logging.DEBUG):
|
|
1228
1341
|
self._logger.debug(
|
|
1229
1342
|
"[CASAMBI_CLASSIC_RX_PAYLOAD] type=%d len=%d hex=%s",
|
|
@@ -1244,6 +1357,7 @@ class CasambiClient:
|
|
|
1244
1357
|
# Otherwise, attempt to parse a stream of Classic "command" records:
|
|
1245
1358
|
# record[0] = (len + 239) mod 256, so len = (b0 - 239) & 0xFF.
|
|
1246
1359
|
pos = 0
|
|
1360
|
+
parsed_any = False
|
|
1247
1361
|
while pos + 2 <= len(payload):
|
|
1248
1362
|
enc_len = payload[pos]
|
|
1249
1363
|
rec_len = (enc_len - 239) & 0xFF
|
|
@@ -1251,6 +1365,7 @@ class CasambiClient:
|
|
|
1251
1365
|
break
|
|
1252
1366
|
rec = payload[pos : pos + rec_len]
|
|
1253
1367
|
pos += rec_len
|
|
1368
|
+
parsed_any = True
|
|
1254
1369
|
|
|
1255
1370
|
typ = rec[1]
|
|
1256
1371
|
ordinal = typ & 0x3F
|
|
@@ -1278,6 +1393,25 @@ class CasambiClient:
|
|
|
1278
1393
|
b2a(rec_payload),
|
|
1279
1394
|
)
|
|
1280
1395
|
|
|
1396
|
+
if parsed_any:
|
|
1397
|
+
self._classicRxCmdStream += 1
|
|
1398
|
+
kind = "cmdstream"
|
|
1399
|
+
else:
|
|
1400
|
+
self._classicRxUnknown += 1
|
|
1401
|
+
kind = "unknown"
|
|
1402
|
+
|
|
1403
|
+
if self._classicRxKindSamples.get(kind, 0) < 3:
|
|
1404
|
+
self._classicRxKindSamples[kind] = self._classicRxKindSamples.get(kind, 0) + 1
|
|
1405
|
+
self._logger.warning(
|
|
1406
|
+
"[CASAMBI_CLASSIC_RX_KIND] kind=%s header=%s verified=%s sig_len=%d seq=%s payload_prefix=%s",
|
|
1407
|
+
kind,
|
|
1408
|
+
best["mode"],
|
|
1409
|
+
verified,
|
|
1410
|
+
best["sig_len"],
|
|
1411
|
+
None if best["seq"] is None else f"0x{best['seq']:04x}",
|
|
1412
|
+
b2a(payload[: min(len(payload), 32)]),
|
|
1413
|
+
)
|
|
1414
|
+
|
|
1281
1415
|
# Any trailing bytes that don't form a full record are logged for analysis.
|
|
1282
1416
|
if self._logger.isEnabledFor(logging.DEBUG) and pos < len(payload):
|
|
1283
1417
|
self._logger.debug(
|
CasambiBt/_network.py
CHANGED
|
@@ -262,16 +262,16 @@ class Network:
|
|
|
262
262
|
getNetworkUrl = f"https://api.casambi.com/network/{self._id}/"
|
|
263
263
|
|
|
264
264
|
try:
|
|
265
|
+
payload = {
|
|
266
|
+
"formatVersion": 1,
|
|
267
|
+
"deviceName": DEVICE_NAME,
|
|
268
|
+
"revision": self._networkRevision,
|
|
269
|
+
}
|
|
270
|
+
|
|
265
271
|
# **SECURITY**: Do not set session header for client! This could leak the session with external clients.
|
|
266
272
|
res = await self._httpClient.put(
|
|
267
273
|
getNetworkUrl,
|
|
268
|
-
json=
|
|
269
|
-
"formatVersion": 1,
|
|
270
|
-
"token": self._token,
|
|
271
|
-
"deviceName": DEVICE_NAME,
|
|
272
|
-
"clientInfo": self._clientInfo,
|
|
273
|
-
"revision": self._networkRevision,
|
|
274
|
-
},
|
|
274
|
+
json=payload,
|
|
275
275
|
headers={"X-Casambi-Session": self._session.session}, # type: ignore[union-attr]
|
|
276
276
|
)
|
|
277
277
|
|
|
@@ -284,8 +284,28 @@ class Network:
|
|
|
284
284
|
)
|
|
285
285
|
await self._cache.invalidateCache()
|
|
286
286
|
|
|
287
|
+
if res.status_code == httpx.codes.BAD_REQUEST:
|
|
288
|
+
# Some backend variants may reject the minimal update payload.
|
|
289
|
+
# Retry once with Android-like fields (token/clientInfo) for diagnostics/testing.
|
|
290
|
+
self._logger.warning(
|
|
291
|
+
"[CASAMBI_CLOUD_UPDATE_RETRY] status=400 retry_with_token_clientInfo=true body_prefix=%r",
|
|
292
|
+
(res.text or "")[:200],
|
|
293
|
+
)
|
|
294
|
+
payload2 = dict(payload)
|
|
295
|
+
payload2["token"] = self._token
|
|
296
|
+
payload2["clientInfo"] = self._clientInfo
|
|
297
|
+
res = await self._httpClient.put(
|
|
298
|
+
getNetworkUrl,
|
|
299
|
+
json=payload2,
|
|
300
|
+
headers={"X-Casambi-Session": self._session.session}, # type: ignore[union-attr]
|
|
301
|
+
)
|
|
302
|
+
|
|
287
303
|
if res.status_code != httpx.codes.OK:
|
|
288
|
-
self._logger.error(
|
|
304
|
+
self._logger.error(
|
|
305
|
+
"Update failed: %s body_prefix=%r",
|
|
306
|
+
res.status_code,
|
|
307
|
+
(res.text or "")[:500],
|
|
308
|
+
)
|
|
289
309
|
raise NetworkUpdateError("Could not update network!")
|
|
290
310
|
|
|
291
311
|
self._logger.debug(f"Network: {res.text}")
|
CasambiBt/_version.py
CHANGED
{casambi_bt_revamped-0.3.12.dev6.dist-info → casambi_bt_revamped-0.3.12.dev8.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.dev8
|
|
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.dev6.dist-info → casambi_bt_revamped-0.3.12.dev8.dist-info}/RECORD
RENAMED
|
@@ -2,21 +2,21 @@ CasambiBt/__init__.py,sha256=iJdTF4oeXfj5d5gfGxQkacqUjtnQo0IW-zFPJvFjWWk,336
|
|
|
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=dG-VRlZ0n7Eng8ORc-Xk8rifCVAcXBexFroA4BLQ_w8,69657
|
|
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
|
|
9
9
|
CasambiBt/_invocation.py,sha256=fkG4R0Gv5_amFfD_P6DKuIEe3oKWZW0v8RSU8zDjPdI,2985
|
|
10
10
|
CasambiBt/_keystore.py,sha256=Jdiq0zMPDmhfpheSojKY6sTUpmVrvX_qOyO7yCYd3kw,2788
|
|
11
|
-
CasambiBt/_network.py,sha256=
|
|
11
|
+
CasambiBt/_network.py,sha256=nB_pRB9dZL6P7THeuOce7ctWd0wXyCWF13h67SauZVQ,20714
|
|
12
12
|
CasambiBt/_operation.py,sha256=Q5UccsrtNp_B_wWqwH_3eLFW_yF6A55FMmfUKDk2WrI,1059
|
|
13
13
|
CasambiBt/_switch_events.py,sha256=S8OD0dBcw5T4J2C7qfmOQMnTJ7omIXRUYv4PqDOB87E,13137
|
|
14
14
|
CasambiBt/_unit.py,sha256=nxbg_8UCCVB9WI8dUS21g2JrGyPKcefqKMSusMOhLOo,18721
|
|
15
|
-
CasambiBt/_version.py,sha256=
|
|
15
|
+
CasambiBt/_version.py,sha256=RkpM6Fp6uH7xKTYzqUnnINOKTs0TrFqLrkU4nloEFrU,337
|
|
16
16
|
CasambiBt/errors.py,sha256=1L_Q8og_N_BRYEKizghAQXr6tihlHykFgtcCHUDcBas,1961
|
|
17
17
|
CasambiBt/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
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.
|
|
22
|
-
casambi_bt_revamped-0.3.12.
|
|
18
|
+
casambi_bt_revamped-0.3.12.dev8.dist-info/licenses/LICENSE,sha256=TAIIitFxpxEDi6Iju7foW4TDQmWvC-IhLVLhl67jKmQ,11341
|
|
19
|
+
casambi_bt_revamped-0.3.12.dev8.dist-info/METADATA,sha256=d0oJkqNgiNr_ACzBbE_6Z2i93Wsa1oG_gZi54xgNiJo,5877
|
|
20
|
+
casambi_bt_revamped-0.3.12.dev8.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
21
|
+
casambi_bt_revamped-0.3.12.dev8.dist-info/top_level.txt,sha256=uNbqLjtecFosoFzpGAC89-5icikWODKI8rOjbi8v_sA,10
|
|
22
|
+
casambi_bt_revamped-0.3.12.dev8.dist-info/RECORD,,
|
{casambi_bt_revamped-0.3.12.dev6.dist-info → casambi_bt_revamped-0.3.12.dev8.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|