casambi-bt-revamped 0.3.7.dev4__py3-none-any.whl → 0.3.7.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 CHANGED
@@ -512,6 +512,9 @@ class CasambiClient:
512
512
  self._logger.debug(f"Ignoring message type 0x29 (not a switch event): {b2a(data)}")
513
513
  return
514
514
 
515
+ # NEW PARSING: Android-style protocol parsing (for comparison only)
516
+ self._parseAndroidProtocol(data, packet_seq, raw_packet)
517
+
515
518
  pos = 0
516
519
  oldPos = 0
517
520
  switch_events_found = 0
@@ -551,18 +554,41 @@ class CasambiClient:
551
554
  # Process based on message type
552
555
  if message_type == 0x08 or message_type == 0x10: # Switch/button events
553
556
  switch_events_found += 1
554
- # Extract button ID - try both upper and lower nibbles
555
- button_lower = parameter & 0x0F
556
- button_upper = (parameter >> 4) & 0x0F
557
557
 
558
- # Use upper 4 bits if lower 4 bits are 0, otherwise use lower 4 bits
559
- if button_lower == 0 and button_upper != 0:
560
- button = button_upper
561
- self._logger.debug(f"EVO button extraction: parameter=0x{parameter:02x}, using upper nibble, button={button}")
558
+ # Enhanced button extraction for battery-powered switches
559
+ # For type 0x08, the parameter byte encodes the button differently
560
+ if message_type == 0x08:
561
+ # Type 0x08: Check if this might be using Android-style encoding
562
+ # where button is in upper nibble when lower nibble is 0
563
+ button_lower = parameter & 0x0F
564
+ button_upper = (parameter >> 4) & 0x0F
565
+
566
+ # Log both interpretations for comparison
567
+ self._logger.debug(
568
+ f"[TYPE 0x08 ANALYSIS] parameter=0x{parameter:02x}, "
569
+ f"lower_nibble={button_lower}, upper_nibble={button_upper}"
570
+ )
571
+
572
+ # If lower nibble is 0 and upper nibble is not, use upper
573
+ # This handles cases like 0x20 (button 2), 0x30 (button 3), etc.
574
+ if button_lower == 0 and button_upper != 0:
575
+ button = button_upper
576
+ self._logger.info(
577
+ f"[TYPE 0x08 FIX] Using upper nibble for button: {button} "
578
+ f"(parameter=0x{parameter:02x})"
579
+ )
580
+ else:
581
+ button = button_lower
582
+ self._logger.debug(
583
+ f"[TYPE 0x08] Using lower nibble for button: {button} "
584
+ f"(parameter=0x{parameter:02x})"
585
+ )
562
586
  else:
563
- button = button_lower
564
- self._logger.debug(f"EVO button extraction: parameter=0x{parameter:02x}, using lower nibble, button={button}")
565
-
587
+ # Type 0x10: Standard extraction from lower nibble
588
+ button = parameter & 0x0F
589
+ self._logger.debug(
590
+ f"[TYPE 0x10] Button extraction: parameter=0x{parameter:02x}, button={button}"
591
+ )
566
592
  # For type 0x10 messages, we need to pass additional data beyond the declared payload
567
593
  if message_type == 0x10:
568
594
  # Extend to include at least 10 bytes from message start for state byte
@@ -598,8 +624,91 @@ class CasambiClient:
598
624
  if switch_events_found == 0:
599
625
  self._logger.debug(f"No switch events found in packet: {b2a(data)}")
600
626
 
601
- def _processSwitchMessage(self, message_type: int, flags: int, button: int, payload: bytes, full_data: bytes, start_pos: int, packet_seq: int = None, raw_packet: bytes = None, android_switch_event: dict = None) -> None:
602
- """Process a switch/button message (types 0x08 or 0x10)"""
627
+ def _parseAndroidProtocol(
628
+ self, data: bytes, packet_seq: int = None, raw_packet: bytes = None
629
+ ) -> None:
630
+ """
631
+ Parse using Android app's protocol structure (for comparison only).
632
+ This doesn't generate events, just logs what it would detect.
633
+
634
+ Android protocol structure:
635
+ - 2 bytes: Header/flags
636
+ - 1 byte: Command type (29-36 for ButtonEvent0-7)
637
+ - 2 bytes: Origin
638
+ - 2 bytes: Target (lower byte = type, upper byte = unit ID)
639
+ - 2 bytes: Age
640
+ - Variable: Payload
641
+ """
642
+ try:
643
+ # Check if data looks like Android protocol format
644
+ if len(data) < 9:
645
+ return
646
+
647
+ # Try to parse as Android protocol
648
+ pos = 0
649
+ if data[0] in [0x08, 0x10]: # This is BLE protocol, not Android protocol
650
+ return
651
+
652
+ # Attempt Android protocol parsing
653
+ try:
654
+ import struct
655
+ # Read header
656
+ header = struct.unpack_from(">H", data, 0)[0]
657
+ command_type = data[2] if len(data) > 2 else 0
658
+ origin = struct.unpack_from(">H", data, 3)[0] if len(data) > 4 else 0
659
+ target = struct.unpack_from(">H", data, 5)[0] if len(data) > 6 else 0
660
+ age = struct.unpack_from(">H", data, 7)[0] if len(data) > 8 else 0
661
+
662
+ payload_length = (header & 0x3F) # Lower 6 bits
663
+ lifetime = (header >> 11) & 0x0F # Bits 11-14
664
+
665
+ target_type = target & 0xFF # Lower byte
666
+ unit_id = target >> 8 # Upper byte
667
+
668
+ # Check if this is a switch event (target type = 6)
669
+ if target_type == 6 and 29 <= command_type <= 36:
670
+ button_index = command_type - 29
671
+
672
+ if len(data) >= 10:
673
+ payload_start = 9
674
+ if len(data) >= payload_start + 1:
675
+ first_payload_byte = data[payload_start]
676
+ param_p = (first_payload_byte >> 3) & 0x0F
677
+ param_s = first_payload_byte & 0x07
678
+ state = (first_payload_byte & 0x80) >> 7
679
+
680
+ self._logger.info(
681
+ f"[ANDROID PROTOCOL] Unit {unit_id} Switch event: "
682
+ f"button #{button_index} (P{param_p} S{param_s}) = {state} "
683
+ f"(1=pressed, 0=released)"
684
+ )
685
+
686
+ # Compare with what current parsing would detect
687
+ self._logger.debug(
688
+ f"[ANDROID PROTOCOL] Full parse: header=0x{header:04x}, "
689
+ f"cmd={command_type}, origin={origin}, target=0x{target:04x}, "
690
+ f"lifetime={lifetime}, age={age}, payload_len={payload_length}"
691
+ )
692
+ except:
693
+ # Not Android protocol format, skip
694
+ pass
695
+
696
+ except Exception as e:
697
+ # Silently ignore - this is just for comparison
698
+ pass
699
+
700
+ def _processSwitchMessage(
701
+ self,
702
+ message_type: int,
703
+ flags: int,
704
+ button: int,
705
+ payload: bytes,
706
+ full_data: bytes,
707
+ start_pos: int,
708
+ packet_seq: int = None,
709
+ raw_packet: bytes = None,
710
+ ) -> None:
711
+ """Process a switch/button message (types 0x08 or 0x10)."""
603
712
  if not payload:
604
713
  self._logger.error("Switch message has empty payload")
605
714
  return
@@ -634,13 +743,25 @@ class CasambiClient:
634
743
  if action is not None:
635
744
  is_release = (action >> 1) & 1
636
745
  event_string = "button_release" if is_release else "button_press"
746
+
747
+ # NEW: Enhanced logging for battery-powered switches
748
+ self._logger.debug(
749
+ f"[TYPE 0x08 STATE] action=0x{action:02x}, release_bit={is_release}, "
750
+ f"event={event_string}, unit_id={unit_id}, button={button}"
751
+ )
637
752
  elif message_type == 0x10:
638
753
  # Type 0x10: The state byte is at position 9 (0-indexed) from message start
639
- # This applies to all units, not just unit 31
754
+ # This applies to all units, including battery-powered switches
640
755
  # full_data for type 0x10 is the message data starting from position 0
641
756
  state_pos = 9
642
757
  if len(full_data) > state_pos:
643
758
  state_byte = full_data[state_pos]
759
+
760
+ # NEW: Enhanced state detection with logging
761
+ self._logger.debug(
762
+ f"[TYPE 0x10 STATE] State byte at pos {state_pos}: 0x{state_byte:02x}"
763
+ )
764
+
644
765
  if state_byte == 0x01:
645
766
  event_string = "button_press"
646
767
  elif state_byte == 0x02:
@@ -650,21 +771,56 @@ class CasambiClient:
650
771
  elif state_byte == 0x0c:
651
772
  event_string = "button_release_after_hold"
652
773
  else:
653
- self._logger.debug(f"Type 0x10: Unknown state byte 0x{state_byte:02x} at message pos {state_pos}")
654
- # Fallback: check if extra_data starts with 0x12 (indicates release)
655
- if len(extra_data) >= 1 and extra_data[0] == 0x12:
656
- event_string = "button_release"
774
+ # More detailed logging for unknown states
775
+ self._logger.info(
776
+ f"[TYPE 0x10 STATE] Unknown state byte 0x{state_byte:02x} at pos {state_pos}, "
777
+ f"unit_id={unit_id}, button={button}, full_data={b2a(full_data)}"
778
+ )
779
+
780
+ # Enhanced fallback logic for battery-powered switches
781
+ # Check multiple patterns that indicate release
782
+ if len(extra_data) >= 1:
783
+ first_extra = extra_data[0]
784
+ if first_extra == 0x12:
785
+ event_string = "button_release"
786
+ self._logger.debug("[TYPE 0x10 STATE] Detected release via 0x12 pattern")
787
+ elif first_extra in [0x00, 0x10]:
788
+ event_string = "button_press"
789
+ self._logger.debug(f"[TYPE 0x10 STATE] Detected press via 0x{first_extra:02x} pattern")
790
+ else:
791
+ # Check action byte patterns
792
+ if action is not None:
793
+ # Common patterns: 0xf8/0xfb = press, 0xfa/0xfc = release
794
+ if action in [0xf8, 0xfb, 0xf7, 0xf9]:
795
+ event_string = "button_press"
796
+ self._logger.debug(f"[TYPE 0x10 STATE] Detected press via action 0x{action:02x}")
797
+ elif action in [0xfa, 0xfc, 0xfd, 0xfe]:
798
+ event_string = "button_release"
799
+ self._logger.debug(f"[TYPE 0x10 STATE] Detected release via action 0x{action:02x}")
800
+ else:
801
+ event_string = "button_press" # Default to press
802
+ self._logger.debug(f"[TYPE 0x10 STATE] Defaulting to press for action 0x{action:02x}")
803
+ else:
804
+ event_string = "button_press" # Default to press
657
805
  else:
658
- event_string = "button_press"
806
+ event_string = "button_press" # Default to press when no extra data
659
807
  else:
660
808
  # Fallback when message is too short
809
+ self._logger.warning(
810
+ f"[TYPE 0x10 STATE] Message too short for state byte (len={len(full_data)}), "
811
+ f"unit_id={unit_id}, button={button}, payload={b2a(payload)}"
812
+ )
813
+
814
+ # Use enhanced fallback detection
661
815
  if len(extra_data) >= 1 and extra_data[0] == 0x12:
662
816
  event_string = "button_release"
663
- self._logger.debug(f"Type 0x10: Using extra_data pattern for release detection")
817
+ self._logger.debug("[TYPE 0x10 STATE] Using 0x12 pattern for release (short message)")
818
+ elif action is not None and action in [0xfa, 0xfc, 0xfd, 0xfe]:
819
+ event_string = "button_release"
820
+ self._logger.debug(f"[TYPE 0x10 STATE] Using action pattern 0x{action:02x} for release (short message)")
664
821
  else:
665
- # Cannot determine state
666
- self._logger.warning(f"Type 0x10 message missing state info, unit_id={unit_id}, payload={b2a(payload)}")
667
- event_string = "unknown"
822
+ event_string = "button_press" # Default to press
823
+ self._logger.debug("[TYPE 0x10 STATE] Defaulting to press (short message)")
668
824
 
669
825
  action_display = f"{action:#04x}" if action is not None else "N/A"
670
826
 
CasambiBt/_network.py CHANGED
@@ -382,7 +382,6 @@ class Network:
382
382
  unitTypeJson["mode"],
383
383
  unitTypeJson["stateLength"],
384
384
  controls,
385
- unitTypeJson.get("pushButtonCount"),
386
385
  )
387
386
 
388
387
  # Chache unit type
CasambiBt/_unit.py CHANGED
@@ -75,7 +75,6 @@ class UnitType:
75
75
  :ivar model: The model name of this unit type.
76
76
  :ivar manufacturer: The manufacturer of this unit type.
77
77
  :ivar controls: The different types of controls this unit type is capable of.
78
- :ivar pushButtonCount: The number of push buttons this unit type has (optional).
79
78
  """
80
79
 
81
80
  id: int
@@ -84,7 +83,6 @@ class UnitType:
84
83
  mode: str
85
84
  stateLength: int
86
85
  controls: list[UnitControl]
87
- pushButtonCount: int | None = None
88
86
 
89
87
  def get_control(self, controlType: UnitControlType) -> UnitControl | None:
90
88
  """Return the control description if the unit type supports the given type of control.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: casambi-bt-revamped
3
- Version: 0.3.7.dev4
3
+ Version: 0.3.7.dev5
4
4
  Summary: Enhanced Casambi Bluetooth client library with switch event support
5
5
  Home-page: https://github.com/rankjie/casambi-bt
6
6
  Author: rankjie
@@ -1,19 +1,19 @@
1
1
  CasambiBt/__init__.py,sha256=TW445xSu5PV3TyMjJfwaA1JoWvQQ8LXhZgGdDTfWf3s,302
2
2
  CasambiBt/_cache.py,sha256=KZ2xbiHAHXUPa8Gw_75Nw9NL4QSY_sTWHbyYXYUDaB0,3865
3
3
  CasambiBt/_casambi.py,sha256=gLLkhEcObgapqTx5Mk7WRClyG29UyfZYZCCIhhOg4H4,23101
4
- CasambiBt/_client.py,sha256=8a1sYxvuNStb7ldgeEHDFyonPIfPTIjFtqZQIbDo62U,30511
4
+ CasambiBt/_client.py,sha256=zjenWQE6EuedFJS87HunDvFLYZnnIO-BmM-zqqXfX1Q,38235
5
5
  CasambiBt/_client_android_parser.py,sha256=1lVkVYMO0Nhh9_nkNwgb68hlCS_uD8WxYQDir5hOdHs,7744
6
6
  CasambiBt/_constants.py,sha256=_AxkG7Btxl4VeS6mO7GJW5Kc9dFs3s9sDmtJ83ZEKNw,359
7
7
  CasambiBt/_discover.py,sha256=H7HpiFYIy9ELvmPXXd_ck-5O5invJf15dDIRk-vO5IE,1696
8
8
  CasambiBt/_encryption.py,sha256=CLcoOOrggQqhJbnr_emBnEnkizpWDvb_0yFnitq4_FM,3831
9
9
  CasambiBt/_keystore.py,sha256=Jdiq0zMPDmhfpheSojKY6sTUpmVrvX_qOyO7yCYd3kw,2788
10
- CasambiBt/_network.py,sha256=B5uVDrfwFKZ3UQgf5THUwasBxh1nzl7KJlq9CqoIyqU,14466
10
+ CasambiBt/_network.py,sha256=qcsWn_EsBexzXCv14JcpSIymhuR6Eaf479lZdzpfYBM,14417
11
11
  CasambiBt/_operation.py,sha256=-BuC1Bvtg-G-zSN_b_0JMvXdHZaR6LbTw0S425jg96c,842
12
- CasambiBt/_unit.py,sha256=B1ce5MaNdoF9ljHAhoiRfV1Tn6CBM4oD3OPDmShIMm8,17167
12
+ CasambiBt/_unit.py,sha256=YiQWoHmMDWHHo4XAjtW8rHsBqIqpmp9MVdv1Mbu2xw4,17043
13
13
  CasambiBt/errors.py,sha256=0JgDjaKlAKDes0poWzA8nrTUYQ8qdNfBb8dfaqqzCRA,1664
14
14
  CasambiBt/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- casambi_bt_revamped-0.3.7.dev4.dist-info/licenses/LICENSE,sha256=TAIIitFxpxEDi6Iju7foW4TDQmWvC-IhLVLhl67jKmQ,11341
16
- casambi_bt_revamped-0.3.7.dev4.dist-info/METADATA,sha256=7DgCkhLn7OMDycRzDzB7EevVTt1gZqnGr5UVCvbemZo,3049
17
- casambi_bt_revamped-0.3.7.dev4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
18
- casambi_bt_revamped-0.3.7.dev4.dist-info/top_level.txt,sha256=uNbqLjtecFosoFzpGAC89-5icikWODKI8rOjbi8v_sA,10
19
- casambi_bt_revamped-0.3.7.dev4.dist-info/RECORD,,
15
+ casambi_bt_revamped-0.3.7.dev5.dist-info/licenses/LICENSE,sha256=TAIIitFxpxEDi6Iju7foW4TDQmWvC-IhLVLhl67jKmQ,11341
16
+ casambi_bt_revamped-0.3.7.dev5.dist-info/METADATA,sha256=bVPa_6JqEvAHMjmUZbdjpb2T0bElv2uA69OIQQvZ7Aw,3049
17
+ casambi_bt_revamped-0.3.7.dev5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
18
+ casambi_bt_revamped-0.3.7.dev5.dist-info/top_level.txt,sha256=uNbqLjtecFosoFzpGAC89-5icikWODKI8rOjbi8v_sA,10
19
+ casambi_bt_revamped-0.3.7.dev5.dist-info/RECORD,,