custom IR

This commit is contained in:
2025-09-27 22:35:56 +02:00
parent 98780e5d7f
commit f7d16e0f33
12 changed files with 9712 additions and 39 deletions

View File

@@ -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