@@ -385,6 +385,122 @@ def build_iotbt_state_query() -> bytearray:
385385 return wrap_command (raw_cmd , cmd_family = 0x0a )
386386
387387
388+ # =============================================================================
389+ # IOTBT SEGMENT-BASED COMMANDS (IOTBT devices with addressable segments)
390+ # These devices use 0xE1 0x03 for color (not 0xE2) and 0x3B for power (not 0x71)
391+ # =============================================================================
392+
393+ def build_iotbt_segment_color_command (
394+ r : int , g : int , b : int , brightness : int = 100 , segment_count : int = 20
395+ ) -> bytearray :
396+ """
397+ Build IOTBT segment-based color command (0xE1 0x03 format).
398+
399+ Source: User protocol capture (Dec 2025) - IOTBT devices with addressable segments.
400+
401+ Format: [0xE1, 0x03, 0x00, segment_count, 0x00, 0x00, segment_count, ...segment_data...]
402+
403+ Each segment (4 bytes): [0xA1, hue, saturation, brightness]
404+ - 0xA1: Segment marker
405+ - hue: 0-255 (0=red, 85=green, 170=blue, wrapping)
406+ - saturation: 0-100 (0=white, 100=full color)
407+ - brightness: 0-100 (0=off, 100=full)
408+
409+ This sets all segments to the same color. For individual segment control,
410+ the segment_data would contain different values per segment.
411+
412+ Args:
413+ r, g, b: RGB color values (0-255)
414+ brightness: Overall brightness percentage (0-100)
415+ segment_count: Number of segments (default 20)
416+
417+ Returns:
418+ Wrapped command packet
419+ """
420+ # Convert RGB to HSV
421+ h , s , v = rgb_to_hsv (r , g , b )
422+
423+ # Convert hue from 0-360 to 0-255 scale (device uses 256-step hue)
424+ # 0=red, ~85=green, ~170=blue
425+ hue_255 = int (h * 255 / 360 ) & 0xFF
426+
427+ # Saturation stays 0-100
428+ sat = max (0 , min (100 , s ))
429+
430+ # Combine brightness from both sources
431+ # RGB value gives us "color intensity", brightness param is overall
432+ combined_bright = max (1 , min (100 , int (brightness * v / 100 )))
433+
434+ # Build header: E1 03 00 {segment_count} 00 00 {segment_count}
435+ raw_cmd = bytearray ([
436+ 0xE1 , 0x03 ,
437+ 0x00 , # Unknown/reserved
438+ segment_count & 0xFF , # Segment count (first occurrence)
439+ 0x00 , 0x00 , # Reserved
440+ segment_count & 0xFF , # Segment count (repeated)
441+ ])
442+
443+ # Add segment data - all segments get same color
444+ for _ in range (segment_count ):
445+ raw_cmd .extend ([
446+ 0xA1 , # Segment marker
447+ hue_255 & 0xFF , # Hue (0-255)
448+ sat & 0xFF , # Saturation (0-100)
449+ combined_bright & 0xFF # Brightness (0-100)
450+ ])
451+
452+ # No checksum for this command format
453+ return wrap_command (raw_cmd , cmd_family = 0x0a )
454+
455+
456+ def build_iotbt_segment_effect_command (
457+ effect_id : int , speed : int = 50 , brightness : int = 100 , segment_count : int = 20
458+ ) -> bytearray :
459+ """
460+ Build IOTBT segment-based effect command (0xE1 0x01 format).
461+
462+ Source: User protocol capture (Dec 2025) - IOTBT segment-based effects.
463+
464+ Format: [0xE1, 0x01, effect_id, speed, brightness, 0x00, ...palette_data...]
465+
466+ The palette data contains color entries for the effect. For simplicity,
467+ we use a rainbow palette (7 colors) which works for most effects.
468+
469+ Args:
470+ effect_id: Effect number (1-99)
471+ speed: Effect speed (0-100)
472+ brightness: Effect brightness (0-100)
473+ segment_count: Number of segments
474+
475+ Returns:
476+ Wrapped command packet
477+ """
478+ effect_id = max (1 , min (99 , effect_id ))
479+ speed = max (0 , min (100 , speed ))
480+ brightness = max (1 , min (100 , brightness ))
481+
482+ # Build header
483+ raw_cmd = bytearray ([
484+ 0xE1 , 0x01 ,
485+ effect_id & 0xFF ,
486+ speed & 0xFF ,
487+ brightness & 0xFF ,
488+ 0x00 , # Reserved
489+ ])
490+
491+ # Add rainbow palette (7 colors, similar to user's capture)
492+ # Format: [0xA1, hue, saturation, brightness] per color
493+ # Rainbow hues at roughly: 0, 43, 85, 128, 170, 213, 255 (wrapping)
494+ rainbow_hues = [0 , 43 , 85 , 128 , 170 , 213 , 240 ]
495+ for hue in rainbow_hues :
496+ raw_cmd .extend ([0xA1 , hue , 100 , brightness ])
497+
498+ # Pad remaining palette slots if needed (usually 7 is enough)
499+
500+ # No checksum for this command format
501+ return wrap_command (raw_cmd , cmd_family = 0x0a )
502+
503+
388504# =============================================================================
389505# COLOR COMMANDS
390506# =============================================================================
@@ -864,8 +980,12 @@ def build_effect_command(
864980 - Music effects (encoded as effect_num << 8): 0xE1 0x05 command format
865981 Speed parameter is used as mic sensitivity for music mode
866982 """
867- if effect_type == EffectType .IOTBT :
868- # IOTBT devices use different commands for regular effects vs music effects
983+ if effect_type == EffectType .IOTBT_SEGMENT :
984+ # IOTBT segment-based variant uses 0xE1 0x01 command with palette
985+ # Source: User protocol capture (Dec 2025) - IOTBT65C device
986+ return build_iotbt_segment_effect_command (effect_id , speed , brightness )
987+ elif effect_type == EffectType .IOTBT :
988+ # Standard IOTBT devices use different commands for regular effects vs music effects
869989 if effect_id >= 0x100 :
870990 # Music reactive effect (encoded as effect_num << 8)
871991 # Decode the effect ID and use music command
@@ -2130,3 +2250,40 @@ def get_service_data_from_advertisement(
21302250 return service_data_dict [key ]
21312251
21322252 return None
2253+
2254+
2255+ def is_iotbt_segment_variant (service_data_dict : dict [str , bytes ]) -> bool :
2256+ """
2257+ Check if device is an IOTBT segment-based variant.
2258+
2259+ Segment-based IOTBT devices are identified by:
2260+ - Service UUID 0x5A00 (ZengGe manufacturer-specific)
2261+ - Status byte (byte 0) is 0x56
2262+
2263+ Standard IOTBT devices (status byte 0x80) use Telink mesh protocol.
2264+ Unknown status bytes default to standard Telink protocol for safety.
2265+
2266+ Segment-based variants (status 0x56) use different commands:
2267+ - Power: 0x3B (standard LEDnetWF, not 0x71 Telink)
2268+ - Color: 0xE1 0x03 (segment-based HSB, not 0xE2 hue)
2269+ - Effects: 0xE1 0x01 (palette-based, not 0xE0 0x02)
2270+
2271+ Args:
2272+ service_data_dict: Dict from BluetoothServiceInfoBleak.service_data
2273+
2274+ Returns:
2275+ True if segment-based IOTBT variant (status 0x56), False otherwise
2276+ """
2277+ uuid_5a00 = "00005a00-0000-1000-8000-00805f9b34fb"
2278+ if uuid_5a00 not in service_data_dict :
2279+ return False
2280+
2281+ data = service_data_dict [uuid_5a00 ]
2282+ if len (data ) < 1 :
2283+ return False
2284+
2285+ # Status byte 0x80 = standard IOTBT (Telink mesh protocol) - DEFAULT
2286+ # Status byte 0x56 = segment-based variant
2287+ # Unknown values default to standard Telink for safety
2288+ status_byte = data [0 ] & 0xFF
2289+ return status_byte == 0x56
0 commit comments