custom IR
This commit is contained in:
@@ -6,49 +6,59 @@ This is a template for creating custom IR protocol decoders based on signal anal
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
from ir_remote import IRProtocol
|
||||
|
||||
class IRProtocol:
|
||||
"""Base class for IR protocol decoding"""
|
||||
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
self.logger = logging.getLogger(f"{__name__}.{name}")
|
||||
|
||||
def decode(self, pulses: List[Tuple[bool, float]]) -> Optional[str]:
|
||||
"""Decode IR pulses to command string"""
|
||||
raise NotImplementedError
|
||||
|
||||
class CustomIRProtocol(IRProtocol):
|
||||
"""
|
||||
Custom IR Protocol Decoder
|
||||
Custom IR Protocol Decoder for Unknown Remote
|
||||
|
||||
This template provides a framework for implementing custom IR protocol decoders.
|
||||
You need to customize the timing constants and decode logic based on your
|
||||
signal analysis results.
|
||||
Based on signal analysis showing:
|
||||
- Most common: 71 pulses
|
||||
- Header: ~8843μs pulse + ~4507μs space
|
||||
- Bit pulse: ~484μs
|
||||
- Bit 0 space: ~645μs
|
||||
- Bit 1 space: ~1770μs
|
||||
"""
|
||||
|
||||
def __init__(self, name: str = "CUSTOM"):
|
||||
super().__init__(name)
|
||||
|
||||
# TODO: Update these timing constants based on your signal analysis
|
||||
# These are example values - replace with your actual protocol timings
|
||||
# Timing constants based on signal analysis
|
||||
# Header timing
|
||||
self.HEADER_PULSE = 8843 # microseconds (from analysis)
|
||||
self.HEADER_SPACE = 4507 # microseconds (from analysis)
|
||||
|
||||
# Header timing (if your protocol has a header)
|
||||
self.HEADER_PULSE = 9000 # microseconds
|
||||
self.HEADER_SPACE = 4500 # microseconds
|
||||
# Bit timing - this protocol uses space width modulation
|
||||
self.BIT_PULSE = 484 # microseconds (consistent pulse width)
|
||||
self.BIT_0_SPACE = 645 # microseconds (short space = bit 0)
|
||||
self.BIT_1_SPACE = 1770 # microseconds (long space = bit 1)
|
||||
|
||||
# Bit timing (adjust based on your protocol's bit encoding)
|
||||
self.BIT_1_PULSE = 560 # microseconds
|
||||
self.BIT_1_SPACE = 1690 # microseconds
|
||||
self.BIT_0_PULSE = 560 # microseconds
|
||||
self.BIT_0_SPACE = 560 # microseconds
|
||||
# Repeat code timing (if supported)
|
||||
self.REPEAT_PULSE = 8843 # microseconds
|
||||
self.REPEAT_SPACE = 2093 # microseconds (from analysis)
|
||||
|
||||
# Footer timing (if your protocol has a footer)
|
||||
self.FOOTER_PULSE = 560 # microseconds
|
||||
self.FOOTER_SPACE = 100000 # microseconds (long gap)
|
||||
|
||||
# Repeat code timing (if your protocol supports repeats)
|
||||
self.REPEAT_PULSE = 9000 # microseconds
|
||||
self.REPEAT_SPACE = 2250 # microseconds
|
||||
|
||||
# Tolerance for timing matching (20% is usually good)
|
||||
self.TOLERANCE = 0.2
|
||||
# Tolerance for timing matching
|
||||
self.TOLERANCE = 0.25 # 25% tolerance for this protocol
|
||||
|
||||
# Expected frame structure
|
||||
self.EXPECTED_PULSE_COUNT = 34 # Adjust based on your analysis
|
||||
self.DATA_BITS = 32 # Number of data bits
|
||||
self.ADDRESS_BITS = 16 # Number of address bits
|
||||
self.COMMAND_BITS = 16 # Number of command bits
|
||||
self.EXPECTED_PULSE_COUNT = 71 # Most common pulse count
|
||||
self.DATA_BITS = 32 # Standard 32-bit data
|
||||
self.ADDRESS_BITS = 16 # 16-bit address
|
||||
self.COMMAND_BITS = 16 # 16-bit command
|
||||
|
||||
# Footer timing (long gap before repeat)
|
||||
self.FOOTER_PULSE = 41949 # Very long pulse at end
|
||||
self.FOOTER_SPACE = 8997 # Space after footer
|
||||
|
||||
def decode(self, pulses: List[Tuple[bool, float]]) -> Optional[str]:
|
||||
"""
|
||||
@@ -99,8 +109,14 @@ class CustomIRProtocol(IRProtocol):
|
||||
if not self._check_header(pulse_times[:2]):
|
||||
return None
|
||||
|
||||
# Decode data bits
|
||||
address, command = self._decode_data_bits(pulse_times[2:])
|
||||
# Find where the data ends (look for the footer)
|
||||
data_end = self._find_data_end(pulse_times[2:])
|
||||
if data_end is None:
|
||||
return None
|
||||
|
||||
# Decode data bits (skip the footer)
|
||||
data_pulses = pulse_times[2:2+data_end]
|
||||
address, command = self._decode_data_bits(data_pulses)
|
||||
|
||||
if address is None or command is None:
|
||||
return None
|
||||
@@ -118,9 +134,23 @@ class CustomIRProtocol(IRProtocol):
|
||||
return (self._is_timing_match(pulse_time, self.HEADER_PULSE) and
|
||||
self._is_timing_match(space_time, self.HEADER_SPACE))
|
||||
|
||||
def _find_data_end(self, data_times: List[float]) -> Optional[int]:
|
||||
"""Find where the data section ends by looking for the footer"""
|
||||
# Look for the very long pulse that indicates end of data
|
||||
for i in range(0, len(data_times), 2):
|
||||
if i < len(data_times):
|
||||
pulse_time = data_times[i]
|
||||
# Check if this is the footer pulse (very long)
|
||||
if self._is_timing_match(pulse_time, self.FOOTER_PULSE):
|
||||
return i # Return the index where data ends
|
||||
|
||||
# If no footer found, assume it's a standard 32-bit protocol
|
||||
return 64 # 32 bits * 2 (pulse + space)
|
||||
|
||||
def _decode_data_bits(self, data_times: List[float]) -> Tuple[Optional[int], Optional[int]]:
|
||||
"""Decode data bits from timing data"""
|
||||
if len(data_times) < self.DATA_BITS * 2:
|
||||
print(f"Not enough data: {len(data_times)} < {self.DATA_BITS * 2}")
|
||||
return None, None
|
||||
|
||||
address = 0
|
||||
@@ -129,21 +159,22 @@ class CustomIRProtocol(IRProtocol):
|
||||
# Process data bits in pairs (pulse, space)
|
||||
for i in range(0, min(len(data_times), self.DATA_BITS * 2), 2):
|
||||
if i + 1 >= len(data_times):
|
||||
print(f"Not enough data at bit {i//2}")
|
||||
break
|
||||
|
||||
pulse_time = data_times[i]
|
||||
space_time = data_times[i + 1]
|
||||
|
||||
# Check if pulse timing is valid
|
||||
if not self._is_timing_match(pulse_time, self.BIT_0_PULSE):
|
||||
self.logger.debug(f"Invalid pulse timing at bit {i//2}: {pulse_time}")
|
||||
# Check if pulse timing is valid (should be ~484μs)
|
||||
if not self._is_timing_match(pulse_time, self.BIT_PULSE):
|
||||
print(f"Invalid pulse timing at bit {i//2}: {pulse_time}μs (expected ~{self.BIT_PULSE}μs)")
|
||||
return None, None
|
||||
|
||||
bit_index = i // 2
|
||||
bit_value = self._decode_bit(space_time)
|
||||
|
||||
if bit_value is None:
|
||||
self.logger.debug(f"Invalid space timing at bit {bit_index}: {space_time}")
|
||||
print(f"Invalid space timing at bit {bit_index}: {space_time}μs (expected ~{self.BIT_0_SPACE}μs or ~{self.BIT_1_SPACE}μs)")
|
||||
return None, None
|
||||
|
||||
# Set the bit in the appropriate field
|
||||
|
||||
Reference in New Issue
Block a user