custom ir
This commit is contained in:
@@ -50,3 +50,4 @@ def check_ir_status():
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
check_ir_status()
|
check_ir_status()
|
||||||
|
|
||||||
|
|||||||
275
custom_ir_protocol.py
Executable file
275
custom_ir_protocol.py
Executable file
@@ -0,0 +1,275 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Custom IR Protocol Decoder Template
|
||||||
|
This is a template for creating custom IR protocol decoders based on signal analysis
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Dict, List, Optional, Tuple
|
||||||
|
from ir_remote import IRProtocol
|
||||||
|
|
||||||
|
class CustomIRProtocol(IRProtocol):
|
||||||
|
"""
|
||||||
|
Custom IR Protocol Decoder
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# Header timing (if your protocol has a header)
|
||||||
|
self.HEADER_PULSE = 9000 # microseconds
|
||||||
|
self.HEADER_SPACE = 4500 # microseconds
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
def decode(self, pulses: List[Tuple[bool, float]]) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
Decode IR pulses to command string
|
||||||
|
|
||||||
|
Args:
|
||||||
|
pulses: List of (is_pulse, duration) tuples
|
||||||
|
is_pulse: True for pulse, False for space
|
||||||
|
duration: Duration in seconds (will be converted to microseconds)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Command string in format "CUSTOM_ADDRESS_COMMAND" or None if decode fails
|
||||||
|
"""
|
||||||
|
if len(pulses) < 2:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Convert durations to microseconds
|
||||||
|
pulse_times = [duration * 1000000 for _, duration in pulses]
|
||||||
|
|
||||||
|
# Check for repeat code first
|
||||||
|
repeat_code = self._check_repeat_code(pulse_times)
|
||||||
|
if repeat_code:
|
||||||
|
return repeat_code
|
||||||
|
|
||||||
|
# Check for normal frame
|
||||||
|
if len(pulse_times) != self.EXPECTED_PULSE_COUNT:
|
||||||
|
self.logger.debug(f"Expected {self.EXPECTED_PULSE_COUNT} pulses, got {len(pulse_times)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Decode the frame
|
||||||
|
return self._decode_frame(pulse_times)
|
||||||
|
|
||||||
|
def _check_repeat_code(self, pulse_times: List[float]) -> Optional[str]:
|
||||||
|
"""Check if this is a repeat code"""
|
||||||
|
if len(pulse_times) == 2:
|
||||||
|
pulse_time = pulse_times[0]
|
||||||
|
space_time = pulse_times[1]
|
||||||
|
|
||||||
|
if (self._is_timing_match(pulse_time, self.REPEAT_PULSE) and
|
||||||
|
self._is_timing_match(space_time, self.REPEAT_SPACE)):
|
||||||
|
return "REPEAT"
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _decode_frame(self, pulse_times: List[float]) -> Optional[str]:
|
||||||
|
"""Decode a complete frame"""
|
||||||
|
# Check header (first two timings)
|
||||||
|
if not self._check_header(pulse_times[:2]):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Decode data bits
|
||||||
|
address, command = self._decode_data_bits(pulse_times[2:])
|
||||||
|
|
||||||
|
if address is None or command is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return f"CUSTOM_{address:04X}_{command:04X}"
|
||||||
|
|
||||||
|
def _check_header(self, header_times: List[float]) -> bool:
|
||||||
|
"""Check if the header matches expected timing"""
|
||||||
|
if len(header_times) < 2:
|
||||||
|
return False
|
||||||
|
|
||||||
|
pulse_time = header_times[0]
|
||||||
|
space_time = header_times[1]
|
||||||
|
|
||||||
|
return (self._is_timing_match(pulse_time, self.HEADER_PULSE) and
|
||||||
|
self._is_timing_match(space_time, self.HEADER_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:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
address = 0
|
||||||
|
command = 0
|
||||||
|
|
||||||
|
# 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):
|
||||||
|
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}")
|
||||||
|
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}")
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
# Set the bit in the appropriate field
|
||||||
|
if bit_index < self.ADDRESS_BITS:
|
||||||
|
if bit_value:
|
||||||
|
address |= (1 << bit_index)
|
||||||
|
else:
|
||||||
|
command_bit_index = bit_index - self.ADDRESS_BITS
|
||||||
|
if bit_value:
|
||||||
|
command |= (1 << command_bit_index)
|
||||||
|
|
||||||
|
return address, command
|
||||||
|
|
||||||
|
def _decode_bit(self, space_time: float) -> Optional[bool]:
|
||||||
|
"""Decode a single bit from space timing"""
|
||||||
|
if self._is_timing_match(space_time, self.BIT_1_SPACE):
|
||||||
|
return True
|
||||||
|
elif self._is_timing_match(space_time, self.BIT_0_SPACE):
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _is_timing_match(self, actual: float, expected: float) -> bool:
|
||||||
|
"""Check if actual timing matches expected timing within tolerance"""
|
||||||
|
min_time = expected * (1 - self.TOLERANCE)
|
||||||
|
max_time = expected * (1 + self.TOLERANCE)
|
||||||
|
return min_time <= actual <= max_time
|
||||||
|
|
||||||
|
def analyze_signal(self, pulse_times: List[float]) -> Dict:
|
||||||
|
"""
|
||||||
|
Analyze a signal to help understand the protocol structure
|
||||||
|
This is useful for debugging and protocol discovery
|
||||||
|
"""
|
||||||
|
analysis = {
|
||||||
|
'pulse_count': len(pulse_times),
|
||||||
|
'total_duration': sum(pulse_times),
|
||||||
|
'min_timing': min(pulse_times) if pulse_times else 0,
|
||||||
|
'max_timing': max(pulse_times) if pulse_times else 0,
|
||||||
|
'unique_timings': len(set(pulse_times)),
|
||||||
|
'timing_analysis': self._analyze_timings(pulse_times),
|
||||||
|
'possible_structure': self._guess_structure(pulse_times)
|
||||||
|
}
|
||||||
|
|
||||||
|
return analysis
|
||||||
|
|
||||||
|
def _analyze_timings(self, pulse_times: List[float]) -> Dict:
|
||||||
|
"""Analyze timing patterns in the signal"""
|
||||||
|
timing_groups = {}
|
||||||
|
tolerance = 0.2
|
||||||
|
|
||||||
|
for timing in pulse_times:
|
||||||
|
grouped = False
|
||||||
|
for group_key in timing_groups:
|
||||||
|
if abs(timing - group_key) / group_key <= tolerance:
|
||||||
|
timing_groups[group_key].append(timing)
|
||||||
|
grouped = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not grouped:
|
||||||
|
timing_groups[timing] = [timing]
|
||||||
|
|
||||||
|
# Find common timings
|
||||||
|
common_timings = {}
|
||||||
|
for group_key, group_timings in timing_groups.items():
|
||||||
|
if len(group_timings) > 1:
|
||||||
|
common_timings[group_key] = {
|
||||||
|
'count': len(group_timings),
|
||||||
|
'avg': sum(group_timings) / len(group_timings),
|
||||||
|
'min': min(group_timings),
|
||||||
|
'max': max(group_timings)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'unique_timings': len(timing_groups),
|
||||||
|
'common_timings': common_timings,
|
||||||
|
'all_groups': timing_groups
|
||||||
|
}
|
||||||
|
|
||||||
|
def _guess_structure(self, pulse_times: List[float]) -> str:
|
||||||
|
"""Guess the protocol structure based on pulse count and timing"""
|
||||||
|
count = len(pulse_times)
|
||||||
|
|
||||||
|
if count == 2:
|
||||||
|
return "Possible repeat code"
|
||||||
|
elif count == 34:
|
||||||
|
return "Possible NEC-like protocol (34 pulses)"
|
||||||
|
elif count == 14:
|
||||||
|
return "Possible RC5-like protocol (14 bits)"
|
||||||
|
elif count % 2 == 0:
|
||||||
|
return f"Even pulse count ({count}) - likely pulse/space encoding"
|
||||||
|
else:
|
||||||
|
return f"Odd pulse count ({count}) - unusual pattern"
|
||||||
|
|
||||||
|
# Example usage and testing
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import json
|
||||||
|
|
||||||
|
# Setup logging
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
# Create custom protocol decoder
|
||||||
|
protocol = CustomIRProtocol("MY_CUSTOM")
|
||||||
|
|
||||||
|
# Example: Load captured signals from analyzer
|
||||||
|
try:
|
||||||
|
with open("ir_analysis_latest.json", 'r') as f:
|
||||||
|
signals = json.load(f)
|
||||||
|
|
||||||
|
print("Analyzing captured signals with custom protocol decoder...")
|
||||||
|
|
||||||
|
for i, signal_data in enumerate(signals):
|
||||||
|
print(f"\nSignal {i+1}:")
|
||||||
|
pulses = [(i % 2 == 0, duration / 1000000) for i, duration in enumerate(signal_data['pulses'])]
|
||||||
|
|
||||||
|
# Try to decode
|
||||||
|
command = protocol.decode(pulses)
|
||||||
|
if command:
|
||||||
|
print(f" Decoded: {command}")
|
||||||
|
else:
|
||||||
|
print(f" Failed to decode")
|
||||||
|
|
||||||
|
# Analyze signal
|
||||||
|
analysis = protocol.analyze_signal(signal_data['pulses'])
|
||||||
|
print(f" Analysis: {analysis['possible_structure']}")
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("No analysis file found. Run ir_signal_analyzer.py first to capture signals.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {e}")
|
||||||
87
develop_custom_protocol.sh
Executable file
87
develop_custom_protocol.sh
Executable file
@@ -0,0 +1,87 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Quick start script for developing custom IR protocols
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Custom IR Protocol Development Tool"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if we're on a Raspberry Pi
|
||||||
|
if ! command -v python3 &> /dev/null; then
|
||||||
|
echo "Error: Python3 not found. Please install Python3."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if RPi.GPIO is available
|
||||||
|
python3 -c "import RPi.GPIO" 2>/dev/null
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Warning: RPi.GPIO not available. This script is designed for Raspberry Pi."
|
||||||
|
echo "You can still use the analysis tools, but hardware testing won't work."
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "This tool will help you develop a custom IR protocol decoder."
|
||||||
|
echo ""
|
||||||
|
echo "Steps:"
|
||||||
|
echo "1. Capture raw IR signals from your unknown remote"
|
||||||
|
echo "2. Analyze the signal patterns"
|
||||||
|
echo "3. Customize the protocol decoder"
|
||||||
|
echo "4. Test and integrate with your IR system"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
read -p "Do you want to start with signal capture? (y/n): " -n 1 -r
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
echo ""
|
||||||
|
echo "Starting IR signal analyzer..."
|
||||||
|
echo "Point your unknown remote at the IR receiver and press buttons."
|
||||||
|
echo "Press Ctrl+C when done capturing signals."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
python3 ir_signal_analyzer.py --gpio-pin 18 --verbose
|
||||||
|
echo ""
|
||||||
|
echo "Signal capture complete!"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if analysis file was created
|
||||||
|
if ls ir_analysis_*.json 1> /dev/null 2>&1; then
|
||||||
|
echo "Analysis file created. Now you can:"
|
||||||
|
echo "1. Review the analysis results"
|
||||||
|
echo "2. Customize custom_ir_protocol.py with your timing constants"
|
||||||
|
echo "3. Run the integration script"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
read -p "Do you want to run the integration script now? (y/n): " -n 1 -r
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
echo ""
|
||||||
|
echo "Running integration script..."
|
||||||
|
python3 integrate_custom_protocol.py
|
||||||
|
echo ""
|
||||||
|
echo "Integration complete!"
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
echo "1. Edit custom_ir_protocol.py with your timing constants"
|
||||||
|
echo "2. Update custom_ir_mapping.json with your command mappings"
|
||||||
|
echo "3. Test with: python3 test_custom_protocol.py"
|
||||||
|
echo "4. Test with real remote: python3 simple_ir_listener_polling.py"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "No analysis file was created. Please check your IR receiver setup."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo "Available tools:"
|
||||||
|
echo "1. ir_signal_analyzer.py - Capture and analyze IR signals"
|
||||||
|
echo "2. custom_ir_protocol.py - Template for custom protocol decoder"
|
||||||
|
echo "3. integrate_custom_protocol.py - Integrate custom protocol into system"
|
||||||
|
echo "4. protocol_development_guide.md - Detailed development guide"
|
||||||
|
echo ""
|
||||||
|
echo "Run this script again when you're ready to start signal capture."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "For detailed instructions, see protocol_development_guide.md"
|
||||||
|
echo "=========================================="
|
||||||
306
integrate_custom_protocol.py
Executable file
306
integrate_custom_protocol.py
Executable file
@@ -0,0 +1,306 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Integration script for custom IR protocols
|
||||||
|
This script helps integrate your custom protocol decoder into the existing IR system
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def backup_existing_files():
|
||||||
|
"""Backup existing IR system files"""
|
||||||
|
backup_dir = Path("backup_ir_system")
|
||||||
|
backup_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
files_to_backup = [
|
||||||
|
"ir_remote.py",
|
||||||
|
"simple_ir_listener.py",
|
||||||
|
"simple_ir_listener_polling.py",
|
||||||
|
"ir_listener.py"
|
||||||
|
]
|
||||||
|
|
||||||
|
for file in files_to_backup:
|
||||||
|
if os.path.exists(file):
|
||||||
|
shutil.copy2(file, backup_dir / file)
|
||||||
|
print(f"Backed up {file} to {backup_dir}")
|
||||||
|
|
||||||
|
return backup_dir
|
||||||
|
|
||||||
|
def update_ir_remote_with_custom_protocol():
|
||||||
|
"""Update ir_remote.py to include custom protocol"""
|
||||||
|
print("Updating ir_remote.py to include custom protocol...")
|
||||||
|
|
||||||
|
# Read current ir_remote.py
|
||||||
|
with open("ir_remote.py", "r") as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Add import for custom protocol
|
||||||
|
import_line = "from custom_ir_protocol import CustomIRProtocol"
|
||||||
|
|
||||||
|
if import_line not in content:
|
||||||
|
# Find the import section and add our import
|
||||||
|
lines = content.split('\n')
|
||||||
|
import_section_end = 0
|
||||||
|
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if line.startswith('import ') or line.startswith('from '):
|
||||||
|
import_section_end = i + 1
|
||||||
|
|
||||||
|
lines.insert(import_section_end, import_line)
|
||||||
|
content = '\n'.join(lines)
|
||||||
|
|
||||||
|
# Update the IRRemote class to include custom protocol
|
||||||
|
if "CustomIRProtocol()" not in content:
|
||||||
|
# Find the protocols initialization line
|
||||||
|
old_line = "self.protocols = protocols or [NECProtocol(), RC5Protocol()]"
|
||||||
|
new_line = "self.protocols = protocols or [NECProtocol(), RC5Protocol(), CustomIRProtocol()]"
|
||||||
|
|
||||||
|
content = content.replace(old_line, new_line)
|
||||||
|
|
||||||
|
# Write updated content
|
||||||
|
with open("ir_remote.py", "w") as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
print("Updated ir_remote.py successfully")
|
||||||
|
|
||||||
|
def update_simple_listeners_with_custom_protocol():
|
||||||
|
"""Update simple listeners to include custom protocol"""
|
||||||
|
listeners = [
|
||||||
|
"simple_ir_listener.py",
|
||||||
|
"simple_ir_listener_polling.py"
|
||||||
|
]
|
||||||
|
|
||||||
|
for listener_file in listeners:
|
||||||
|
if not os.path.exists(listener_file):
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f"Updating {listener_file}...")
|
||||||
|
|
||||||
|
with open(listener_file, "r") as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Add import
|
||||||
|
import_line = "from custom_ir_protocol import CustomIRProtocol"
|
||||||
|
|
||||||
|
if import_line not in content:
|
||||||
|
lines = content.split('\n')
|
||||||
|
import_section_end = 0
|
||||||
|
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if line.startswith('import ') or line.startswith('from '):
|
||||||
|
import_section_end = i + 1
|
||||||
|
|
||||||
|
lines.insert(import_section_end, import_line)
|
||||||
|
content = '\n'.join(lines)
|
||||||
|
|
||||||
|
# Update protocol lists
|
||||||
|
if "CustomIRProtocol()" not in content:
|
||||||
|
# Find and update protocol initialization
|
||||||
|
old_patterns = [
|
||||||
|
"protocols or [NECProtocol(), RC5Protocol()]",
|
||||||
|
"[NECProtocol(), RC5Protocol()]"
|
||||||
|
]
|
||||||
|
|
||||||
|
for old_pattern in old_patterns:
|
||||||
|
if old_pattern in content:
|
||||||
|
new_pattern = old_pattern.replace("RC5Protocol()]", "RC5Protocol(), CustomIRProtocol()]")
|
||||||
|
content = content.replace(old_pattern, new_pattern)
|
||||||
|
break
|
||||||
|
|
||||||
|
with open(listener_file, "w") as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
print(f"Updated {listener_file} successfully")
|
||||||
|
|
||||||
|
def create_custom_protocol_mapping():
|
||||||
|
"""Create a mapping file for custom protocol commands"""
|
||||||
|
mapping_file = "custom_ir_mapping.json"
|
||||||
|
|
||||||
|
if os.path.exists(mapping_file):
|
||||||
|
print(f"{mapping_file} already exists, skipping creation")
|
||||||
|
return mapping_file
|
||||||
|
|
||||||
|
# Create example mapping for custom protocol
|
||||||
|
custom_mapping = {
|
||||||
|
"CUSTOM_0000_0001": {
|
||||||
|
"command": "power_toggle",
|
||||||
|
"description": "Power on/off",
|
||||||
|
"repeatable": True
|
||||||
|
},
|
||||||
|
"CUSTOM_0000_0002": {
|
||||||
|
"command": "channel_1",
|
||||||
|
"description": "Channel 1",
|
||||||
|
"repeatable": False
|
||||||
|
},
|
||||||
|
"CUSTOM_0000_0003": {
|
||||||
|
"command": "channel_2",
|
||||||
|
"description": "Channel 2",
|
||||||
|
"repeatable": False
|
||||||
|
},
|
||||||
|
"CUSTOM_0000_0004": {
|
||||||
|
"command": "volume_up",
|
||||||
|
"description": "Volume up",
|
||||||
|
"repeatable": True
|
||||||
|
},
|
||||||
|
"CUSTOM_0000_0005": {
|
||||||
|
"command": "volume_down",
|
||||||
|
"description": "Volume down",
|
||||||
|
"repeatable": True
|
||||||
|
},
|
||||||
|
"REPEAT": {
|
||||||
|
"command": "repeat_last",
|
||||||
|
"description": "Repeat last command",
|
||||||
|
"repeatable": False
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
with open(mapping_file, "w") as f:
|
||||||
|
json.dump(custom_mapping, f, indent=2)
|
||||||
|
|
||||||
|
print(f"Created {mapping_file} with example mappings")
|
||||||
|
return mapping_file
|
||||||
|
|
||||||
|
def update_main_ir_mapping():
|
||||||
|
"""Update the main IR mapping file to include custom protocol mappings"""
|
||||||
|
main_mapping_files = [
|
||||||
|
"/etc/video_player/ir_mapping.json",
|
||||||
|
"ir_mapping.json"
|
||||||
|
]
|
||||||
|
|
||||||
|
custom_mapping_file = "custom_ir_mapping.json"
|
||||||
|
|
||||||
|
if not os.path.exists(custom_mapping_file):
|
||||||
|
print("Custom mapping file not found, creating it first...")
|
||||||
|
create_custom_protocol_mapping()
|
||||||
|
|
||||||
|
# Load custom mappings
|
||||||
|
with open(custom_mapping_file, "r") as f:
|
||||||
|
custom_mappings = json.load(f)
|
||||||
|
|
||||||
|
# Update main mapping files
|
||||||
|
for mapping_file in main_mapping_files:
|
||||||
|
if os.path.exists(mapping_file):
|
||||||
|
print(f"Updating {mapping_file}...")
|
||||||
|
|
||||||
|
with open(mapping_file, "r") as f:
|
||||||
|
main_mappings = json.load(f)
|
||||||
|
|
||||||
|
# Add custom mappings
|
||||||
|
main_mappings.update(custom_mappings)
|
||||||
|
|
||||||
|
with open(mapping_file, "w") as f:
|
||||||
|
json.dump(main_mappings, f, indent=2)
|
||||||
|
|
||||||
|
print(f"Updated {mapping_file} successfully")
|
||||||
|
|
||||||
|
def create_test_script():
|
||||||
|
"""Create a test script for the custom protocol"""
|
||||||
|
test_script = """#!/usr/bin/env python3
|
||||||
|
'''
|
||||||
|
Test script for custom IR protocol
|
||||||
|
'''
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
from custom_ir_protocol import CustomIRProtocol
|
||||||
|
import json
|
||||||
|
|
||||||
|
def test_custom_protocol():
|
||||||
|
'''Test the custom protocol with captured signals'''
|
||||||
|
|
||||||
|
protocol = CustomIRProtocol("TEST_CUSTOM")
|
||||||
|
|
||||||
|
# Test with example signals (replace with your actual captured data)
|
||||||
|
test_signals = [
|
||||||
|
# Example: 34 pulses for NEC-like protocol
|
||||||
|
[(True, 0.009), (False, 0.0045), (True, 0.00056), (False, 0.00169)] * 8 + [(True, 0.00056), (False, 0.00056)] * 8,
|
||||||
|
# Add more test signals here
|
||||||
|
]
|
||||||
|
|
||||||
|
print("Testing custom protocol decoder...")
|
||||||
|
|
||||||
|
for i, signal in enumerate(test_signals):
|
||||||
|
print(f"\\nTest signal {i+1}:")
|
||||||
|
command = protocol.decode(signal)
|
||||||
|
if command:
|
||||||
|
print(f" Decoded: {command}")
|
||||||
|
else:
|
||||||
|
print(f" Failed to decode")
|
||||||
|
|
||||||
|
# Analyze signal
|
||||||
|
pulse_times = [duration * 1000000 for _, duration in signal]
|
||||||
|
analysis = protocol.analyze_signal(pulse_times)
|
||||||
|
print(f" Analysis: {analysis['possible_structure']}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_custom_protocol()
|
||||||
|
"""
|
||||||
|
|
||||||
|
with open("test_custom_protocol.py", "w") as f:
|
||||||
|
f.write(test_script)
|
||||||
|
|
||||||
|
os.chmod("test_custom_protocol.py", 0o755)
|
||||||
|
print("Created test_custom_protocol.py")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main integration function"""
|
||||||
|
print("=" * 60)
|
||||||
|
print("CUSTOM IR PROTOCOL INTEGRATION")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# Check if custom protocol file exists
|
||||||
|
if not os.path.exists("custom_ir_protocol.py"):
|
||||||
|
print("Error: custom_ir_protocol.py not found!")
|
||||||
|
print("Please create and customize your protocol decoder first.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Backup existing files
|
||||||
|
print("\\n1. Backing up existing files...")
|
||||||
|
backup_dir = backup_existing_files()
|
||||||
|
|
||||||
|
# Update IR remote
|
||||||
|
print("\\n2. Updating IR remote system...")
|
||||||
|
update_ir_remote_with_custom_protocol()
|
||||||
|
|
||||||
|
# Update simple listeners
|
||||||
|
print("\\n3. Updating simple listeners...")
|
||||||
|
update_simple_listeners_with_custom_protocol()
|
||||||
|
|
||||||
|
# Create custom mapping
|
||||||
|
print("\\n4. Creating custom protocol mapping...")
|
||||||
|
create_custom_protocol_mapping()
|
||||||
|
|
||||||
|
# Update main mappings
|
||||||
|
print("\\n5. Updating main IR mappings...")
|
||||||
|
update_main_ir_mapping()
|
||||||
|
|
||||||
|
# Create test script
|
||||||
|
print("\\n6. Creating test script...")
|
||||||
|
create_test_script()
|
||||||
|
|
||||||
|
print("\\n" + "=" * 60)
|
||||||
|
print("INTEGRATION COMPLETE!")
|
||||||
|
print("=" * 60)
|
||||||
|
print("\\nNext steps:")
|
||||||
|
print("1. Customize the timing constants in custom_ir_protocol.py")
|
||||||
|
print("2. Update the command mappings in custom_ir_mapping.json")
|
||||||
|
print("3. Test with: python3 test_custom_protocol.py")
|
||||||
|
print("4. Test with real remote: python3 simple_ir_listener_polling.py")
|
||||||
|
print("\\nBackup files are in:", backup_dir)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\\nError during integration: {e}")
|
||||||
|
print("\\nYou can restore from backup if needed.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
success = main()
|
||||||
|
sys.exit(0 if success else 1)
|
||||||
343
ir_signal_analyzer.py
Executable file
343
ir_signal_analyzer.py
Executable file
@@ -0,0 +1,343 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
IR Signal Analyzer for Unknown Protocols
|
||||||
|
Captures and analyzes raw IR signal timing to help develop custom decoders
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Optional, Tuple
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
|
||||||
|
class IRSignalAnalyzer:
|
||||||
|
"""Analyze IR signals to understand unknown protocols"""
|
||||||
|
|
||||||
|
def __init__(self, gpio_pin: int = 18):
|
||||||
|
self.gpio_pin = gpio_pin
|
||||||
|
self.logger = self._setup_logging()
|
||||||
|
self.running = False
|
||||||
|
self.last_state = GPIO.HIGH
|
||||||
|
self.pulse_start = 0
|
||||||
|
self.pulses = []
|
||||||
|
self.captured_signals = []
|
||||||
|
|
||||||
|
def _setup_logging(self) -> logging.Logger:
|
||||||
|
"""Setup logging for the analyzer"""
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
# Create console handler
|
||||||
|
handler = logging.StreamHandler()
|
||||||
|
formatter = logging.Formatter(
|
||||||
|
'%(asctime)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
|
handler.setFormatter(formatter)
|
||||||
|
logger.addHandler(handler)
|
||||||
|
|
||||||
|
return logger
|
||||||
|
|
||||||
|
def display_startup_info(self):
|
||||||
|
"""Display startup information"""
|
||||||
|
print("=" * 80)
|
||||||
|
print("IR SIGNAL ANALYZER FOR UNKNOWN PROTOCOLS")
|
||||||
|
print("=" * 80)
|
||||||
|
print(f"GPIO Pin: {self.gpio_pin}")
|
||||||
|
print("This tool will capture and analyze raw IR signal timing")
|
||||||
|
print("to help develop custom protocol decoders.")
|
||||||
|
print()
|
||||||
|
print("INSTRUCTIONS:")
|
||||||
|
print("1. Point your unknown remote at the IR receiver")
|
||||||
|
print("2. Press buttons to capture signals")
|
||||||
|
print("3. Press the same button multiple times to check consistency")
|
||||||
|
print("4. Press different buttons to understand the protocol structure")
|
||||||
|
print("5. Press Ctrl+C to stop and save analysis")
|
||||||
|
print("=" * 80)
|
||||||
|
print("LISTENING FOR IR SIGNALS...")
|
||||||
|
print("=" * 80)
|
||||||
|
print()
|
||||||
|
|
||||||
|
def setup_gpio(self):
|
||||||
|
"""Setup GPIO for IR receiver"""
|
||||||
|
try:
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
GPIO.setup(self.gpio_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
||||||
|
self.logger.info(f"GPIO setup complete on pin {self.gpio_pin}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"GPIO setup failed: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def poll_ir_signal(self):
|
||||||
|
"""Poll for IR signal changes"""
|
||||||
|
while self.running:
|
||||||
|
try:
|
||||||
|
current_state = GPIO.input(self.gpio_pin)
|
||||||
|
current_time = time.time()
|
||||||
|
|
||||||
|
# Detect state change
|
||||||
|
if current_state != self.last_state:
|
||||||
|
if self.pulse_start > 0:
|
||||||
|
# Calculate pulse/space duration
|
||||||
|
duration = (current_time - self.pulse_start) * 1000000 # Convert to microseconds
|
||||||
|
self.pulses.append(duration)
|
||||||
|
|
||||||
|
self.pulse_start = current_time
|
||||||
|
self.last_state = current_state
|
||||||
|
|
||||||
|
# Check for end of signal (no change for 100ms)
|
||||||
|
if self.pulse_start > 0 and (current_time - self.pulse_start) > 0.1:
|
||||||
|
if len(self.pulses) > 0:
|
||||||
|
self.process_signal(self.pulses.copy())
|
||||||
|
self.pulses = []
|
||||||
|
self.pulse_start = 0
|
||||||
|
|
||||||
|
time.sleep(0.0001) # 0.1ms polling interval
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error in polling loop: {e}")
|
||||||
|
time.sleep(0.01)
|
||||||
|
|
||||||
|
def process_signal(self, pulses: List[float]):
|
||||||
|
"""Process captured signal"""
|
||||||
|
if len(pulses) < 2:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Store the captured signal
|
||||||
|
signal_data = {
|
||||||
|
'timestamp': time.time(),
|
||||||
|
'pulse_count': len(pulses),
|
||||||
|
'pulses': pulses.copy(),
|
||||||
|
'total_duration': sum(pulses),
|
||||||
|
'analysis': self.analyze_signal(pulses)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.captured_signals.append(signal_data)
|
||||||
|
|
||||||
|
# Display signal information
|
||||||
|
self.display_signal_info(signal_data)
|
||||||
|
|
||||||
|
def analyze_signal(self, pulses: List[float]) -> Dict:
|
||||||
|
"""Analyze signal characteristics"""
|
||||||
|
analysis = {
|
||||||
|
'pulse_count': len(pulses),
|
||||||
|
'total_duration_us': sum(pulses),
|
||||||
|
'min_pulse': min(pulses),
|
||||||
|
'max_pulse': max(pulses),
|
||||||
|
'avg_pulse': sum(pulses) / len(pulses),
|
||||||
|
'unique_timings': list(set(pulses)),
|
||||||
|
'timing_pattern': self.identify_timing_pattern(pulses),
|
||||||
|
'possible_protocol': self.guess_protocol(pulses)
|
||||||
|
}
|
||||||
|
|
||||||
|
return analysis
|
||||||
|
|
||||||
|
def identify_timing_pattern(self, pulses: List[float]) -> Dict:
|
||||||
|
"""Identify common timing patterns"""
|
||||||
|
# Group similar timings (within 20% tolerance)
|
||||||
|
timing_groups = {}
|
||||||
|
tolerance = 0.2
|
||||||
|
|
||||||
|
for pulse in pulses:
|
||||||
|
grouped = False
|
||||||
|
for group_key in timing_groups:
|
||||||
|
if abs(pulse - group_key) / group_key <= tolerance:
|
||||||
|
timing_groups[group_key].append(pulse)
|
||||||
|
grouped = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not grouped:
|
||||||
|
timing_groups[pulse] = [pulse]
|
||||||
|
|
||||||
|
# Find the most common timings
|
||||||
|
common_timings = {}
|
||||||
|
for group_key, group_pulses in timing_groups.items():
|
||||||
|
if len(group_pulses) > 1: # Only groups with multiple occurrences
|
||||||
|
common_timings[group_key] = {
|
||||||
|
'count': len(group_pulses),
|
||||||
|
'avg': sum(group_pulses) / len(group_pulses),
|
||||||
|
'min': min(group_pulses),
|
||||||
|
'max': max(group_pulses)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'unique_timings': len(timing_groups),
|
||||||
|
'common_timings': common_timings,
|
||||||
|
'all_groups': timing_groups
|
||||||
|
}
|
||||||
|
|
||||||
|
def guess_protocol(self, pulses: List[float]) -> str:
|
||||||
|
"""Make educated guesses about the protocol"""
|
||||||
|
pulse_count = len(pulses)
|
||||||
|
|
||||||
|
# Check for known protocol patterns
|
||||||
|
if pulse_count == 2:
|
||||||
|
return "Possible repeat code or simple protocol"
|
||||||
|
elif pulse_count == 34:
|
||||||
|
return "Possible NEC protocol (34 pulses)"
|
||||||
|
elif pulse_count == 14:
|
||||||
|
return "Possible RC5 protocol (14 bits)"
|
||||||
|
elif pulse_count == 16:
|
||||||
|
return "Possible RC6 protocol (16 bits)"
|
||||||
|
elif pulse_count % 2 == 0:
|
||||||
|
return f"Even number of pulses ({pulse_count}) - likely pulse/space encoding"
|
||||||
|
else:
|
||||||
|
return f"Odd number of pulses ({pulse_count}) - unusual pattern"
|
||||||
|
|
||||||
|
def display_signal_info(self, signal_data: Dict):
|
||||||
|
"""Display information about captured signal"""
|
||||||
|
timestamp = time.strftime("%H:%M:%S")
|
||||||
|
analysis = signal_data['analysis']
|
||||||
|
|
||||||
|
print(f"[{timestamp}] Signal Captured:")
|
||||||
|
print(f" Pulse Count: {analysis['pulse_count']}")
|
||||||
|
print(f" Total Duration: {analysis['total_duration_us']:.0f} μs")
|
||||||
|
print(f" Min/Max/Avg Pulse: {analysis['min_pulse']:.0f}/{analysis['max_pulse']:.0f}/{analysis['avg_pulse']:.0f} μs")
|
||||||
|
print(f" Unique Timings: {len(analysis['unique_timings'])}")
|
||||||
|
print(f" Possible Protocol: {analysis['possible_protocol']}")
|
||||||
|
|
||||||
|
# Show common timings
|
||||||
|
if analysis['timing_pattern']['common_timings']:
|
||||||
|
print(" Common Timings:")
|
||||||
|
for timing, info in analysis['timing_pattern']['common_timings'].items():
|
||||||
|
print(f" {timing:.0f}μs (x{info['count']}, avg: {info['avg']:.0f}μs)")
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
def save_analysis(self, filename: str = None):
|
||||||
|
"""Save captured signals to file for analysis"""
|
||||||
|
if not filename:
|
||||||
|
timestamp = time.strftime("%Y%m%d_%H%M%S")
|
||||||
|
filename = f"ir_analysis_{timestamp}.json"
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
json.dump(self.captured_signals, f, indent=2)
|
||||||
|
|
||||||
|
print(f"Analysis saved to: {filename}")
|
||||||
|
print(f"Captured {len(self.captured_signals)} signals")
|
||||||
|
|
||||||
|
# Generate summary
|
||||||
|
self.generate_summary()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error saving analysis: {e}")
|
||||||
|
|
||||||
|
def generate_summary(self):
|
||||||
|
"""Generate analysis summary"""
|
||||||
|
if not self.captured_signals:
|
||||||
|
return
|
||||||
|
|
||||||
|
print("\n" + "=" * 80)
|
||||||
|
print("ANALYSIS SUMMARY")
|
||||||
|
print("=" * 80)
|
||||||
|
|
||||||
|
# Group signals by pulse count
|
||||||
|
pulse_count_groups = {}
|
||||||
|
for signal in self.captured_signals:
|
||||||
|
count = signal['pulse_count']
|
||||||
|
if count not in pulse_count_groups:
|
||||||
|
pulse_count_groups[count] = []
|
||||||
|
pulse_count_groups[count].append(signal)
|
||||||
|
|
||||||
|
print("Signals by pulse count:")
|
||||||
|
for count, signals in sorted(pulse_count_groups.items()):
|
||||||
|
print(f" {count} pulses: {len(signals)} signals")
|
||||||
|
|
||||||
|
# Show timing analysis for this group
|
||||||
|
all_timings = []
|
||||||
|
for signal in signals:
|
||||||
|
all_timings.extend(signal['pulses'])
|
||||||
|
|
||||||
|
if all_timings:
|
||||||
|
unique_timings = list(set(all_timings))
|
||||||
|
print(f" Unique timings: {len(unique_timings)}")
|
||||||
|
print(f" Timing range: {min(all_timings):.0f} - {max(all_timings):.0f} μs")
|
||||||
|
|
||||||
|
print("\nRecommendations for protocol decoder:")
|
||||||
|
print("1. Look for consistent timing patterns across signals")
|
||||||
|
print("2. Identify header, data, and footer sections")
|
||||||
|
print("3. Determine bit encoding method (pulse width, space width, or both)")
|
||||||
|
print("4. Check for repeat codes (usually 2 pulses)")
|
||||||
|
print("5. Analyze data length and structure")
|
||||||
|
|
||||||
|
print("=" * 80)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""Main run loop"""
|
||||||
|
try:
|
||||||
|
# Display startup information
|
||||||
|
self.display_startup_info()
|
||||||
|
|
||||||
|
# Setup GPIO
|
||||||
|
if not self.setup_gpio():
|
||||||
|
print("Failed to setup GPIO. Exiting.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Start polling
|
||||||
|
self.running = True
|
||||||
|
self.poll_ir_signal()
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nStopping IR analyzer...")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error in main loop: {e}")
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
self.cleanup()
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
"""Cleanup resources"""
|
||||||
|
self.running = False
|
||||||
|
try:
|
||||||
|
GPIO.cleanup()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Save analysis
|
||||||
|
if self.captured_signals:
|
||||||
|
self.save_analysis()
|
||||||
|
|
||||||
|
self.logger.info("IR Analyzer cleanup complete")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main function"""
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="IR Signal Analyzer for Unknown Protocols")
|
||||||
|
parser.add_argument(
|
||||||
|
"--gpio-pin",
|
||||||
|
type=int,
|
||||||
|
default=18,
|
||||||
|
help="GPIO pin for IR receiver (default: 18)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--output", "-o",
|
||||||
|
type=str,
|
||||||
|
help="Output filename for analysis data"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--verbose", "-v",
|
||||||
|
action="store_true",
|
||||||
|
help="Enable verbose logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Set logging level
|
||||||
|
if args.verbose:
|
||||||
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
# Create and run analyzer
|
||||||
|
analyzer = IRSignalAnalyzer(args.gpio_pin)
|
||||||
|
success = analyzer.run()
|
||||||
|
|
||||||
|
sys.exit(0 if success else 1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -40,3 +40,4 @@ def monitor_ir_listener():
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
monitor_ir_listener()
|
monitor_ir_listener()
|
||||||
|
|
||||||
|
|||||||
198
protocol_development_guide.md
Normal file
198
protocol_development_guide.md
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
# Custom IR Protocol Development Guide
|
||||||
|
|
||||||
|
This guide will help you develop a custom decoder for an unknown IR protocol using the tools provided.
|
||||||
|
|
||||||
|
## Step 1: Capture Raw Signal Data
|
||||||
|
|
||||||
|
First, use the IR signal analyzer to capture raw timing data from your unknown remote:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 ir_signal_analyzer.py --gpio-pin 18 --verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
### Instructions:
|
||||||
|
1. Point your unknown remote at the IR receiver
|
||||||
|
2. Press buttons to capture signals
|
||||||
|
3. Press the same button multiple times to check consistency
|
||||||
|
4. Press different buttons to understand the protocol structure
|
||||||
|
5. Press Ctrl+C to stop and save analysis
|
||||||
|
|
||||||
|
The analyzer will save a JSON file with all captured signals and generate an analysis summary.
|
||||||
|
|
||||||
|
## Step 2: Analyze the Captured Data
|
||||||
|
|
||||||
|
Examine the generated JSON file and analysis summary to understand:
|
||||||
|
|
||||||
|
### Key Questions to Answer:
|
||||||
|
1. **How many pulses does each signal have?**
|
||||||
|
- Consistent pulse count indicates a structured protocol
|
||||||
|
- Variable pulse count might indicate variable-length data
|
||||||
|
|
||||||
|
2. **What are the common timing values?**
|
||||||
|
- Look for repeated timing values across different buttons
|
||||||
|
- These likely represent bit 0, bit 1, header, footer, etc.
|
||||||
|
|
||||||
|
3. **Is there a header pattern?**
|
||||||
|
- First few pulses often form a header
|
||||||
|
- Headers are usually longer than data bits
|
||||||
|
|
||||||
|
4. **How are bits encoded?**
|
||||||
|
- **Pulse Width Modulation**: Different pulse lengths for 0/1
|
||||||
|
- **Space Width Modulation**: Different space lengths for 0/1
|
||||||
|
- **Both**: Different combinations of pulse and space lengths
|
||||||
|
|
||||||
|
5. **Is there a repeat code?**
|
||||||
|
- Usually 2 pulses with specific timing
|
||||||
|
- Much shorter than normal frames
|
||||||
|
|
||||||
|
## Step 3: Customize the Protocol Decoder
|
||||||
|
|
||||||
|
Edit `custom_ir_protocol.py` and update the timing constants based on your analysis:
|
||||||
|
|
||||||
|
### Example Analysis Results:
|
||||||
|
```python
|
||||||
|
# If your analysis shows these common timings:
|
||||||
|
# 9000μs, 4500μs, 560μs, 1690μs, 560μs
|
||||||
|
|
||||||
|
# Update the constants:
|
||||||
|
self.HEADER_PULSE = 9000 # Long pulse at start
|
||||||
|
self.HEADER_SPACE = 4500 # Long space after header
|
||||||
|
self.BIT_1_PULSE = 560 # Short pulse for all bits
|
||||||
|
self.BIT_1_SPACE = 1690 # Long space for bit 1
|
||||||
|
self.BIT_0_PULSE = 560 # Short pulse for all bits
|
||||||
|
self.BIT_0_SPACE = 560 # Short space for bit 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common Protocol Patterns:
|
||||||
|
|
||||||
|
#### NEC-like Protocol:
|
||||||
|
- 34 pulses total (header + 32 data bits)
|
||||||
|
- Header: 9000μs pulse + 4500μs space
|
||||||
|
- Data: 560μs pulse + (560μs or 1690μs) space
|
||||||
|
- Repeat: 9000μs pulse + 2250μs space
|
||||||
|
|
||||||
|
#### RC5-like Protocol:
|
||||||
|
- 14 bits total
|
||||||
|
- Manchester encoding
|
||||||
|
- 889μs bit time
|
||||||
|
- Start bits: 11
|
||||||
|
|
||||||
|
#### Custom Protocol Example:
|
||||||
|
- 20 pulses total
|
||||||
|
- Header: 8000μs pulse + 4000μs space
|
||||||
|
- Data: 500μs pulse + (500μs or 1500μs) space
|
||||||
|
- Footer: 500μs pulse + 100000μs space
|
||||||
|
|
||||||
|
## Step 4: Test Your Decoder
|
||||||
|
|
||||||
|
Test your custom decoder with the captured signals:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 custom_ir_protocol.py
|
||||||
|
```
|
||||||
|
|
||||||
|
This will attempt to decode all captured signals using your custom protocol.
|
||||||
|
|
||||||
|
## Step 5: Integrate with IR System
|
||||||
|
|
||||||
|
Once your decoder works, integrate it into your IR system:
|
||||||
|
|
||||||
|
1. **Add to protocol list** in your IR listeners
|
||||||
|
2. **Update IR mapping** to include your custom protocol codes
|
||||||
|
3. **Test with real remote** to ensure it works correctly
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues:
|
||||||
|
|
||||||
|
1. **No signals decoded:**
|
||||||
|
- Check timing constants match your analysis
|
||||||
|
- Verify pulse count expectations
|
||||||
|
- Check tolerance settings (try increasing to 0.3)
|
||||||
|
|
||||||
|
2. **Inconsistent decoding:**
|
||||||
|
- Remote might have timing variations
|
||||||
|
- Increase tolerance or add timing ranges
|
||||||
|
- Check for different button types (some might be repeats)
|
||||||
|
|
||||||
|
3. **Wrong data extracted:**
|
||||||
|
- Verify bit order (LSB vs MSB)
|
||||||
|
- Check address vs command bit allocation
|
||||||
|
- Ensure proper bit indexing
|
||||||
|
|
||||||
|
### Debug Tips:
|
||||||
|
|
||||||
|
1. **Enable debug logging:**
|
||||||
|
```python
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Add print statements** in decode methods to see what's happening
|
||||||
|
|
||||||
|
3. **Compare with known protocols** to understand similarities
|
||||||
|
|
||||||
|
4. **Use the analyzer's timing analysis** to identify patterns
|
||||||
|
|
||||||
|
## Advanced Features
|
||||||
|
|
||||||
|
### Variable-Length Protocols:
|
||||||
|
Some protocols have variable data lengths. Modify the decoder to handle this:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def _determine_data_length(self, pulse_times):
|
||||||
|
# Analyze pulse count to determine data length
|
||||||
|
# Return appropriate bit counts
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multiple Protocol Variants:
|
||||||
|
If your remote uses multiple similar protocols:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class CustomIRProtocolVariant1(CustomIRProtocol):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__("CUSTOM_V1")
|
||||||
|
# Different timing constants
|
||||||
|
|
||||||
|
class CustomIRProtocolVariant2(CustomIRProtocol):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__("CUSTOM_V2")
|
||||||
|
# Different timing constants
|
||||||
|
```
|
||||||
|
|
||||||
|
### Checksum Validation:
|
||||||
|
Some protocols include checksums:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def _validate_checksum(self, address, command):
|
||||||
|
# Calculate and validate checksum
|
||||||
|
# Return True if valid, False otherwise
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example: Complete Custom Protocol
|
||||||
|
|
||||||
|
Here's an example of a complete custom protocol based on analysis:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class MyCustomProtocol(CustomIRProtocol):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__("MY_CUSTOM")
|
||||||
|
|
||||||
|
# Based on analysis of captured signals
|
||||||
|
self.HEADER_PULSE = 8500
|
||||||
|
self.HEADER_SPACE = 4200
|
||||||
|
self.BIT_1_PULSE = 580
|
||||||
|
self.BIT_1_SPACE = 1650
|
||||||
|
self.BIT_0_PULSE = 580
|
||||||
|
self.BIT_0_SPACE = 580
|
||||||
|
self.REPEAT_PULSE = 8500
|
||||||
|
self.REPEAT_SPACE = 2100
|
||||||
|
|
||||||
|
self.EXPECTED_PULSE_COUNT = 36 # 2 header + 34 data
|
||||||
|
self.DATA_BITS = 32
|
||||||
|
self.ADDRESS_BITS = 16
|
||||||
|
self.COMMAND_BITS = 16
|
||||||
|
```
|
||||||
|
|
||||||
|
This protocol would decode signals as `MY_CUSTOM_1234_5678` format.
|
||||||
@@ -295,3 +295,4 @@ def main():
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|||||||
@@ -29,3 +29,4 @@ def test_ir_listener():
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
test_ir_listener()
|
test_ir_listener()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user