From e619e5f41230589485cda2dd2b9a472ea04db8b7 Mon Sep 17 00:00:00 2001 From: Oier Bravo Urtasun Date: Sat, 27 Sep 2025 04:38:06 +0200 Subject: [PATCH] ir listener --- ir_listener.py | 336 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 336 insertions(+) create mode 100755 ir_listener.py diff --git a/ir_listener.py b/ir_listener.py new file mode 100755 index 0000000..4529123 --- /dev/null +++ b/ir_listener.py @@ -0,0 +1,336 @@ +#!/usr/bin/env python3 +""" +IR Remote Listener Script +Listens to IR commands and prints them to console with configuration display +""" + +import os +import sys +import json +import time +import logging +from pathlib import Path +from typing import Dict, Optional + +# Add current directory to path to import local modules +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +try: + from ir_remote import IRRemote, IRCodeMapper + from config_manager import ConfigManager + HARDWARE_AVAILABLE = True +except ImportError as e: + print(f"Warning: Hardware modules not available: {e}") + print("Running in simulation mode (no actual IR hardware)") + HARDWARE_AVAILABLE = False + + # Create mock classes for testing + class MockIRRemote: + def __init__(self, gpio_pin=18): + self.gpio_pin = gpio_pin + self.command_callback = None + + def set_command_callback(self, callback): + self.command_callback = callback + + def cleanup(self): + pass + + class MockIRCodeMapper: + def __init__(self, mapping_file="ir_mapping.json"): + self.mapping_file = mapping_file + self.mapping = {} + + def get_command(self, ir_code): + return self.mapping.get(ir_code) + + class MockConfigManager: + def __init__(self, config_dir="/etc/video_player"): + self.config_dir = config_dir + self.main_config = type('Config', (), { + 'ir_pin': 18, + 'ir_protocols': ['NEC', 'RC5'], + 'ir_repeat_delay': 0.1 + })() + self.channels = {} + self.ir_mapping = {} + self.ir_mapping_file = "ir_mapping.json" + + # Load template mapping for testing + template_file = os.path.join(os.path.dirname(__file__), "templates", "ir_mapping.json.template") + if os.path.exists(template_file): + try: + with open(template_file, 'r') as f: + template_data = json.load(f) + # Convert template to IRMappingConfig objects + for ir_code, mapping_info in template_data.items(): + if isinstance(mapping_info, dict): + self.ir_mapping[ir_code] = type('IRMappingConfig', (), { + 'ir_code': ir_code, + 'command': mapping_info.get('command', ''), + 'description': mapping_info.get('description', ''), + 'repeatable': mapping_info.get('repeatable', True) + })() + except Exception as e: + print(f"Warning: Could not load template mapping: {e}") + +class IRListener: + """IR Remote Listener with Configuration Display""" + + def __init__(self, config_dir: str = "/etc/video_player"): + if HARDWARE_AVAILABLE: + self.config_manager = ConfigManager(config_dir) + else: + self.config_manager = MockConfigManager(config_dir) + self.ir_remote = None + self.mapper = None + self.logger = self._setup_logging() + + def _setup_logging(self) -> logging.Logger: + """Setup logging for the listener""" + logger = logging.getLogger(__name__) + logger.setLevel(logging.INFO) + + # Create console handler + handler = logging.StreamHandler() + formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s' + ) + handler.setFormatter(formatter) + logger.addHandler(handler) + + return logger + + def display_configuration(self): + """Display relevant IR configuration information""" + print("=" * 60) + print("IR REMOTE LISTENER - CONFIGURATION") + print("=" * 60) + + # Display main IR configuration + config = self.config_manager.main_config + print(f"IR GPIO Pin: {config.ir_pin}") + print(f"Supported Protocols: {', '.join(config.ir_protocols)}") + print(f"Repeat Delay: {config.ir_repeat_delay}s") + print() + + # Display IR mapping configuration + ir_mapping = self.config_manager.ir_mapping + print(f"Configured IR Mappings: {len(ir_mapping)}") + print("-" * 40) + + if ir_mapping: + # Group mappings by protocol + nec_mappings = {} + rc5_mappings = {} + other_mappings = {} + + for ir_code, mapping in ir_mapping.items(): + if ir_code.startswith("NEC_"): + nec_mappings[ir_code] = mapping + elif ir_code.startswith("RC5_"): + rc5_mappings[ir_code] = mapping + else: + other_mappings[ir_code] = mapping + + # Display NEC mappings + if nec_mappings: + print("NEC Protocol Mappings:") + for ir_code, mapping in sorted(nec_mappings.items()): + print(f" {ir_code:20} -> {mapping.command:15} ({mapping.description})") + print() + + # Display RC5 mappings + if rc5_mappings: + print("RC5 Protocol Mappings:") + for ir_code, mapping in sorted(rc5_mappings.items()): + print(f" {ir_code:20} -> {mapping.command:15} ({mapping.description})") + print() + + # Display other mappings + if other_mappings: + print("Other Protocol Mappings:") + for ir_code, mapping in sorted(other_mappings.items()): + print(f" {ir_code:20} -> {mapping.command:15} ({mapping.description})") + print() + else: + print("No IR mappings configured") + print() + + # Display channel information + channels = self.config_manager.channels + print(f"Available Channels: {len(channels)}") + if channels: + print("-" * 40) + for channel_num, channel in sorted(channels.items()): + status = "ENABLED" if channel.enabled else "DISABLED" + print(f" Channel {channel_num:2}: {channel.name:20} ({status})") + print() + + print("=" * 60) + if HARDWARE_AVAILABLE: + print("LISTENING FOR IR COMMANDS...") + else: + print("SIMULATION MODE - NO HARDWARE AVAILABLE") + print("Use --simulate flag to test with simulated commands") + print("Press Ctrl+C to exit") + print("=" * 60) + print() + + def setup_ir_remote(self): + """Setup IR remote with configuration""" + try: + config = self.config_manager.main_config + + # Create IR remote instance + if HARDWARE_AVAILABLE: + self.ir_remote = IRRemote(gpio_pin=config.ir_pin) + self.mapper = IRCodeMapper(str(self.config_manager.ir_mapping_file)) + else: + self.ir_remote = MockIRRemote(gpio_pin=config.ir_pin) + self.mapper = MockIRCodeMapper(str(self.config_manager.ir_mapping_file)) + + # Set up command callback + self.ir_remote.set_command_callback(self.handle_ir_command) + + self.logger.info(f"IR Remote setup complete on GPIO pin {config.ir_pin}") + return True + + except Exception as e: + self.logger.error(f"Failed to setup IR remote: {e}") + return False + + def handle_ir_command(self, ir_code: str): + """Handle received IR command""" + timestamp = time.strftime("%H:%M:%S") + + # Get mapped command + mapped_command = self.mapper.get_command(ir_code) if self.mapper else None + + # Print command information + print(f"[{timestamp}] IR Command Received:") + print(f" Raw Code: {ir_code}") + + if mapped_command: + print(f" Mapped Command: {mapped_command}") + + # Get additional info from config manager mapping + if ir_code in self.config_manager.ir_mapping: + mapping_info = self.config_manager.ir_mapping[ir_code] + if mapping_info.description: + print(f" Description: {mapping_info.description}") + print(f" Repeatable: {mapping_info.repeatable}") + else: + print(f" Mapped Command: UNKNOWN (not in mapping)") + + print() + + def run(self, simulate=False): + """Main run loop""" + try: + print("DEBUG: Starting run method") + # Display configuration + print("DEBUG: Displaying configuration") + self.display_configuration() + + # Setup IR remote + print("DEBUG: Setting up IR remote") + if not self.setup_ir_remote(): + print("Failed to setup IR remote. Exiting.") + return False + + print("DEBUG: Starting main loop") + if simulate or not HARDWARE_AVAILABLE: + print("DEBUG: Running simulation") + self._run_simulation() + else: + print("DEBUG: Running hardware mode") + # Main listening loop + while True: + time.sleep(0.1) + + except KeyboardInterrupt: + print("\nShutting down IR listener...") + return True + except Exception as e: + self.logger.error(f"Error in main loop: {e}") + return False + finally: + self.cleanup() + + def _run_simulation(self): + """Run simulation mode for testing""" + print("Simulation mode active. Simulating IR commands...") + print("Available test commands:") + print(" - NEC_00FF_00FF (Power toggle)") + print(" - NEC_00FF_807F (Channel 0)") + print(" - NEC_00FF_40BF (Channel 1)") + print(" - RC5_00_0C_0 (Power toggle RC5)") + print(" - REPEAT (Repeat last command)") + print() + + # Simulate some commands + test_commands = [ + "NEC_00FF_00FF", + "NEC_00FF_807F", + "NEC_00FF_40BF", + "RC5_00_0C_0", + "REPEAT", + "UNKNOWN_CODE_123" + ] + + for i, command in enumerate(test_commands): + print(f"Simulating command {i+1}/{len(test_commands)} in 2 seconds...") + time.sleep(2) + self.handle_ir_command(command) + + print("Simulation complete. Press Ctrl+C to exit...") + + # Keep running for manual testing + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + print("\nSimulation interrupted by user") + + def cleanup(self): + """Cleanup resources""" + if self.ir_remote: + self.ir_remote.cleanup() + self.logger.info("IR Listener cleanup complete") + +def main(): + """Main function""" + import argparse + + parser = argparse.ArgumentParser(description="IR Remote Listener") + parser.add_argument( + "--config-dir", + default="/etc/video_player", + help="Configuration directory path (default: /etc/video_player)" + ) + parser.add_argument( + "--verbose", "-v", + action="store_true", + help="Enable verbose logging" + ) + parser.add_argument( + "--simulate", "-s", + action="store_true", + help="Run in simulation mode (for testing without hardware)" + ) + + args = parser.parse_args() + + # Set logging level + if args.verbose: + logging.getLogger().setLevel(logging.DEBUG) + + # Create and run listener + listener = IRListener(args.config_dir) + success = listener.run(simulate=args.simulate) + + sys.exit(0 if success else 1) + +if __name__ == "__main__": + main()