casambi-bt-revamped 0.3.7.dev4__tar.gz → 0.3.7.dev5__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.
- {casambi_bt_revamped-0.3.7.dev4/src/casambi_bt_revamped.egg-info → casambi_bt_revamped-0.3.7.dev5}/PKG-INFO +1 -1
- {casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5}/setup.cfg +1 -1
- {casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5}/src/CasambiBt/_client.py +178 -22
- {casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5}/src/CasambiBt/_network.py +0 -1
- {casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5}/src/CasambiBt/_unit.py +0 -2
- {casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5/src/casambi_bt_revamped.egg-info}/PKG-INFO +1 -1
- {casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5}/LICENSE +0 -0
- {casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5}/README.md +0 -0
- {casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5}/pyproject.toml +0 -0
- {casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5}/src/CasambiBt/__init__.py +0 -0
- {casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5}/src/CasambiBt/_cache.py +0 -0
- {casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5}/src/CasambiBt/_casambi.py +0 -0
- {casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5}/src/CasambiBt/_client_android_parser.py +0 -0
- {casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5}/src/CasambiBt/_constants.py +0 -0
- {casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5}/src/CasambiBt/_discover.py +0 -0
- {casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5}/src/CasambiBt/_encryption.py +0 -0
- {casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5}/src/CasambiBt/_keystore.py +0 -0
- {casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5}/src/CasambiBt/_operation.py +0 -0
- {casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5}/src/CasambiBt/errors.py +0 -0
- {casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5}/src/CasambiBt/py.typed +0 -0
- {casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5}/src/casambi_bt_revamped.egg-info/SOURCES.txt +0 -0
- {casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5}/src/casambi_bt_revamped.egg-info/dependency_links.txt +0 -0
- {casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5}/src/casambi_bt_revamped.egg-info/requires.txt +0 -0
- {casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5}/src/casambi_bt_revamped.egg-info/top_level.txt +0 -0
|
@@ -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
|
-
#
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
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
|
-
|
|
564
|
-
|
|
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
|
|
602
|
-
|
|
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,
|
|
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
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
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(
|
|
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
|
-
#
|
|
666
|
-
self._logger.
|
|
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
|
|
|
@@ -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.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5}/src/CasambiBt/_constants.py
RENAMED
|
File without changes
|
{casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5}/src/CasambiBt/_discover.py
RENAMED
|
File without changes
|
{casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5}/src/CasambiBt/_encryption.py
RENAMED
|
File without changes
|
{casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5}/src/CasambiBt/_keystore.py
RENAMED
|
File without changes
|
{casambi_bt_revamped-0.3.7.dev4 → casambi_bt_revamped-0.3.7.dev5}/src/CasambiBt/_operation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|