casambi-bt-revamped 0.3.12.dev15__tar.gz → 0.4.0__tar.gz

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.
Files changed (31) hide show
  1. {casambi_bt_revamped-0.3.12.dev15/src/casambi_bt_revamped.egg-info → casambi_bt_revamped-0.4.0}/PKG-INFO +1 -1
  2. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/setup.cfg +1 -1
  3. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/src/CasambiBt/_client.py +52 -13
  4. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/src/CasambiBt/_version.py +1 -1
  5. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0/src/casambi_bt_revamped.egg-info}/PKG-INFO +1 -1
  6. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/LICENSE +0 -0
  7. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/README.md +0 -0
  8. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/pyproject.toml +0 -0
  9. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/src/CasambiBt/__init__.py +0 -0
  10. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/src/CasambiBt/_cache.py +0 -0
  11. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/src/CasambiBt/_casambi.py +0 -0
  12. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/src/CasambiBt/_classic_crypto.py +0 -0
  13. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/src/CasambiBt/_constants.py +0 -0
  14. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/src/CasambiBt/_discover.py +0 -0
  15. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/src/CasambiBt/_encryption.py +0 -0
  16. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/src/CasambiBt/_invocation.py +0 -0
  17. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/src/CasambiBt/_keystore.py +0 -0
  18. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/src/CasambiBt/_network.py +0 -0
  19. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/src/CasambiBt/_operation.py +0 -0
  20. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/src/CasambiBt/_switch_events.py +0 -0
  21. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/src/CasambiBt/_unit.py +0 -0
  22. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/src/CasambiBt/errors.py +0 -0
  23. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/src/CasambiBt/py.typed +0 -0
  24. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/src/casambi_bt_revamped.egg-info/SOURCES.txt +0 -0
  25. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/src/casambi_bt_revamped.egg-info/dependency_links.txt +0 -0
  26. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/src/casambi_bt_revamped.egg-info/requires.txt +0 -0
  27. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/src/casambi_bt_revamped.egg-info/top_level.txt +0 -0
  28. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/tests/test_classic_protocol.py +0 -0
  29. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/tests/test_legacy_protocol_handling.py +0 -0
  30. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/tests/test_switch_event_logs.py +0 -0
  31. {casambi_bt_revamped-0.3.12.dev15 → casambi_bt_revamped-0.4.0}/tests/test_unit_state_logs.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: casambi-bt-revamped
3
- Version: 0.3.12.dev15
3
+ Version: 0.4.0
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
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = casambi-bt-revamped
3
- version = 0.3.12.dev15
3
+ version = 0.4.0
4
4
  author = rankjie
5
5
  author_email = rankjie@gmail.com
6
6
  description = Forked Casambi Bluetooth client library with switch event support, use original if no special need. https://github.com/lkempf/casambi-bt
@@ -1386,7 +1386,7 @@ class CasambiClient:
1386
1386
  # Android n0():998 uses WRITE_TYPE_NO_RESPONSE for classic.
1387
1387
  response=False,
1388
1388
  )
1389
- self._logger.warning("[CASAMBI_CLASSIC_INIT] version sent successfully")
1389
+ self._logger.debug("[CASAMBI_CLASSIC_INIT] version sent successfully")
1390
1390
  except Exception:
1391
1391
  self._logger.warning(
1392
1392
  "[CASAMBI_CLASSIC_INIT] version send failed (continuing with time-sync)",
@@ -1486,7 +1486,7 @@ class CasambiClient:
1486
1486
  # Android X():314 uses WRITE_TYPE_DEFAULT (2) = with-response.
1487
1487
  response=True,
1488
1488
  )
1489
- self._logger.warning("[CASAMBI_CLASSIC_INIT] time-sync sent successfully")
1489
+ self._logger.debug("[CASAMBI_CLASSIC_INIT] time-sync sent successfully")
1490
1490
  except Exception:
1491
1491
  self._logger.warning(
1492
1492
  "[CASAMBI_CLASSIC_INIT] time-sync send failed",
@@ -1601,7 +1601,7 @@ class CasambiClient:
1601
1601
  except Exception:
1602
1602
  handle_uuid = "unknown"
1603
1603
 
1604
- self._logger.warning(
1604
+ self._logger.debug(
1605
1605
  "[CLASSIC_DIAG_RX] #%d handle=%s len=%d hex=%s",
1606
1606
  self._classicRxFrames,
1607
1607
  handle_uuid,
@@ -1618,12 +1618,45 @@ class CasambiClient:
1618
1618
 
1619
1619
  if self._classicConnHash8 is None:
1620
1620
  if self._logLimiter.allow("classic_rx_no_hash", burst=5, window_s=60.0):
1621
- self._logger.warning("[CASAMBI_CLASSIC_RX] missing_connection_hash len=%d", len(raw))
1621
+ self._logger.debug("[CASAMBI_CLASSIC_RX] missing_connection_hash len=%d", len(raw))
1622
1622
  return
1623
1623
 
1624
1624
  visitor_key = self._network.classicVisitorKey()
1625
1625
  manager_key = self._network.classicManagerKey()
1626
1626
 
1627
+ def _walk_classic_records(data: bytes) -> bool:
1628
+ """Check if data is a plausible Classic unit state record stream.
1629
+
1630
+ Walks the same record structure as _parseClassicUnitStates:
1631
+ - unit_id(1) + flags(1) + optional extras + state(state_len)
1632
+ - unit_id 0xF0 is a command response (no extras, state_len bytes consumed)
1633
+ Accepts if >=1 record parsed AND pos ends exactly at len(data).
1634
+ """
1635
+ pos = 0
1636
+ count = 0
1637
+ while pos + 3 <= len(data): # Match Android available()>=3
1638
+ unit_id = data[pos]
1639
+ flags = data[pos + 1]
1640
+ state_len = flags & 0x0F
1641
+ # 0 and 255 are Classic control bytes, not valid unit records
1642
+ # inside a record stream (handled at dispatch level).
1643
+ if unit_id == 0 or unit_id == 255:
1644
+ return False
1645
+ pos += 2
1646
+ if unit_id == 0xF0:
1647
+ # Command response: cmd_id(1) + seq(1) + payload(state_len-2).
1648
+ if state_len < 2:
1649
+ return False
1650
+ pos += state_len
1651
+ else:
1652
+ has_extra1 = (flags & 0x20) != 0
1653
+ has_extra2 = (flags & 0x40) != 0
1654
+ pos += int(has_extra1) + int(has_extra2) + state_len
1655
+ if pos > len(data):
1656
+ return False
1657
+ count += 1
1658
+ return count >= 1 and pos == len(data)
1659
+
1627
1660
  def _plausible_payload(payload: bytes) -> bool:
1628
1661
  if not payload:
1629
1662
  return False
@@ -1638,6 +1671,12 @@ class CasambiClient:
1638
1671
  rec_len = (payload[0] - 239) & 0xFF
1639
1672
  if 2 <= rec_len <= len(payload):
1640
1673
  return True
1674
+ # Classic unit state record stream or control byte.
1675
+ if self._protocolMode == ProtocolMode.CLASSIC:
1676
+ if payload[0] in (0, 255):
1677
+ return True
1678
+ if _walk_classic_records(payload):
1679
+ return True
1641
1680
  return False
1642
1681
 
1643
1682
  def _score(verified: bool | None, payload: bytes) -> int:
@@ -1784,7 +1823,7 @@ class CasambiClient:
1784
1823
  if not parsed_candidates:
1785
1824
  self._classicRxParseFail += 1
1786
1825
  if self._logLimiter.allow("classic_rx_parse_fail", burst=5, window_s=60.0):
1787
- self._logger.warning(
1826
+ self._logger.debug(
1788
1827
  "[CASAMBI_CLASSIC_RX_PARSE_FAIL] len=%d prefix=%s",
1789
1828
  len(raw),
1790
1829
  b2a(raw[: min(len(raw), 32)]),
@@ -1808,7 +1847,7 @@ class CasambiClient:
1808
1847
  if best["score"] == 0:
1809
1848
  self._classicRxParseFail += 1
1810
1849
  if self._logLimiter.allow("classic_rx_unplausible", burst=5, window_s=60.0):
1811
- self._logger.warning(
1850
+ self._logger.debug(
1812
1851
  "[CASAMBI_CLASSIC_RX_UNPLAUSIBLE] preferred=%s len=%d prefix=%s",
1813
1852
  preferred,
1814
1853
  len(raw),
@@ -1842,7 +1881,7 @@ class CasambiClient:
1842
1881
  self._classicRxHistory = self._classicRxHistory[-self._classicDiagMaxHistory:]
1843
1882
 
1844
1883
  # Enhanced RX parse result log
1845
- self._logger.warning(
1884
+ self._logger.debug(
1846
1885
  "[CLASSIC_DIAG_RX_PARSE] mode=%s verified=%s auth=%s sig_len=%d seq=%s score=%d payload_len=%d",
1847
1886
  best["mode"],
1848
1887
  verified,
@@ -1858,7 +1897,7 @@ class CasambiClient:
1858
1897
  if best["mode"] != preferred and best["mode"] in ("conformant", "legacy"):
1859
1898
  # Only switch if we got a stronger signal (verified or plausible payload with fewer assumptions).
1860
1899
  if best["score"] >= 50 and self._logLimiter.allow("classic_rx_mode_switch", burst=3, window_s=3600.0):
1861
- self._logger.warning(
1900
+ self._logger.debug(
1862
1901
  "[CASAMBI_CLASSIC_RX_MODE] switching %s -> %s (score=%d verified=%s sig_len=%d)",
1863
1902
  preferred,
1864
1903
  best["mode"],
@@ -1870,7 +1909,7 @@ class CasambiClient:
1870
1909
 
1871
1910
  # Sample RX logs (limited) + periodic stats (limited).
1872
1911
  if self._logLimiter.allow("classic_rx_sample", burst=10, window_s=60.0):
1873
- self._logger.warning(
1912
+ self._logger.debug(
1874
1913
  "[CASAMBI_CLASSIC_RX] header=%s verified=%s auth=%s sig_len=%d seq=%s payload_prefix=%s",
1875
1914
  best["mode"],
1876
1915
  verified,
@@ -1884,7 +1923,7 @@ class CasambiClient:
1884
1923
  "classic_rx_stats", burst=2, window_s=60.0
1885
1924
  ):
1886
1925
  self._classicRxLastStatsTs = now
1887
- self._logger.warning(
1926
+ self._logger.debug(
1888
1927
  "[CASAMBI_CLASSIC_RX_STATS] frames=%d verified=%d unverifiable=%d parse_fail=%d header=%s "
1889
1928
  "type6=%d type7=%d type9=%d cmdstream=%d unknown=%d classic_states=%d",
1890
1929
  self._classicRxFrames,
@@ -2000,7 +2039,7 @@ class CasambiClient:
2000
2039
 
2001
2040
  if self._classicRxKindSamples.get(kind, 0) < 3:
2002
2041
  self._classicRxKindSamples[kind] = self._classicRxKindSamples.get(kind, 0) + 1
2003
- self._logger.warning(
2042
+ self._logger.debug(
2004
2043
  "[CASAMBI_CLASSIC_RX_KIND] kind=%s header=%s verified=%s sig_len=%d seq=%s payload_prefix=%s",
2005
2044
  kind,
2006
2045
  best["mode"],
@@ -2033,7 +2072,7 @@ class CasambiClient:
2033
2072
 
2034
2073
  # Log full payload for the first 10 Classic payloads regardless of type.
2035
2074
  if self._classicRxClassicStates < 10:
2036
- self._logger.warning(
2075
+ self._logger.debug(
2037
2076
  "[CASAMBI_CLASSIC_DISPATCH] #%d type_byte=%d len=%d hex=%s",
2038
2077
  self._classicRxClassicStates,
2039
2078
  first_byte,
@@ -2121,7 +2160,7 @@ class CasambiClient:
2121
2160
 
2122
2161
  # Log the first few parsed records at WARNING level for tester visibility.
2123
2162
  if records_parsed <= 10 or self._logger.isEnabledFor(logging.DEBUG):
2124
- self._logger.warning(
2163
+ self._logger.debug(
2125
2164
  "[CASAMBI_CLASSIC_STATE_PARSED] unit=%d flags=0x%02x state_len=%d "
2126
2165
  "online=%s extra1=%d extra2=%d state=%s",
2127
2166
  unit_id,
@@ -7,4 +7,4 @@ Avoid using importlib.metadata in hot paths by providing a static version string
7
7
  __all__ = ["__version__"]
8
8
 
9
9
  # NOTE: Must match `casambi-bt/setup.cfg` [metadata] version.
10
- __version__ = "0.3.12.dev15"
10
+ __version__ = "0.4.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: casambi-bt-revamped
3
- Version: 0.3.12.dev15
3
+ Version: 0.4.0
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