diff --git a/can_sniffer/src/handlers/flipper_handler.py b/can_sniffer/src/handlers/flipper_handler.py index a33c2b4..bb16d93 100644 --- a/can_sniffer/src/handlers/flipper_handler.py +++ b/can_sniffer/src/handlers/flipper_handler.py @@ -8,10 +8,15 @@ import threading import time from typing import Dict, Any, Optional, Callable +try: + import serial +except ImportError: + serial = None # type: ignore + from logger import get_logger from config import config from obd2.pids import OBD2Reading -from flipper.protocol import FlipperProtocol +from flipper.protocol import Protocol, Command from flipper.page_manager import PageManager from flipper.pages import OBD2StatsPage, OBD2CommPage, UPSStatusPage, SystemInfoPage, AppStatusPage from flipper.providers.obd2_provider import OBD2Provider @@ -49,12 +54,14 @@ class FlipperHandler(BaseHandler): self._baudrate = baudrate self._update_interval = update_interval - self._protocol: Optional[FlipperProtocol] = None + self._serial: Optional[serial.Serial] = None self._page_manager: Optional[PageManager] = None self._obd2_provider: Optional[OBD2Provider] = None self._update_thread: Optional[threading.Thread] = None + self._rx_thread: Optional[threading.Thread] = None self._running = False + self._connected = False # Callbacks for data access self._state_callback: Optional[Callable[[], Any]] = None @@ -78,20 +85,22 @@ class FlipperHandler(BaseHandler): logger.info("Flipper handler disabled") return False - try: - # Initialize UART protocol - self._protocol = FlipperProtocol( - device=self._device, - baudrate=self._baudrate, - ) + if serial is None: + logger.warning("pyserial not installed, Flipper handler disabled") + return False - if not self._protocol.connect(): - logger.warning(f"Failed to connect to Flipper on {self._device}") - self._available = False - return False + try: + # Initialize UART connection + self._serial = serial.Serial( + port=self._device, + baudrate=self._baudrate, + timeout=0.1, + write_timeout=1.0, + ) + self._connected = True # Initialize page manager with OBD2 pages - self._page_manager = PageManager(self._protocol) + self._page_manager = PageManager() # Create and register pages obd2_page = OBD2StatsPage() @@ -115,15 +124,23 @@ class FlipperHandler(BaseHandler): if self._stats_callback: self._obd2_provider.set_stats_callback(self._stats_callback) - # Start update thread + # Start threads self._running = True + self._update_thread = threading.Thread( target=self._update_loop, - name="Flipper-Update", + name="Flipper-TX", daemon=True, ) self._update_thread.start() + self._rx_thread = threading.Thread( + target=self._rx_loop, + name="Flipper-RX", + daemon=True, + ) + self._rx_thread.start() + self._initialized = True logger.info( "Flipper handler initialized", @@ -134,6 +151,10 @@ class FlipperHandler(BaseHandler): ) return True + except serial.SerialException as e: + logger.warning(f"Failed to open Flipper UART: {e}") + self._connected = False + return False except Exception as e: logger.error(f"Failed to initialize Flipper handler: {e}") return False @@ -170,9 +191,19 @@ class FlipperHandler(BaseHandler): if self._update_thread and self._update_thread.is_alive(): self._update_thread.join(timeout=2.0) - if self._protocol: - self._protocol.disconnect() + if self._rx_thread and self._rx_thread.is_alive(): + self._rx_thread.join(timeout=2.0) + if self._page_manager: + self._page_manager.shutdown() + + if self._serial and self._serial.is_open: + try: + self._serial.close() + except Exception as e: + logger.warning(f"Error closing serial: {e}") + + self._connected = False logger.info("Flipper handler shutdown") self._initialized = False @@ -183,30 +214,79 @@ class FlipperHandler(BaseHandler): "enabled": self._enabled, "initialized": self._initialized, "device": self._device, - "connected": self._protocol.is_connected() if self._protocol else False, + "connected": self._connected, } if self._page_manager: - stats["current_page"] = self._page_manager.get_current_page_name() + stats["current_page"] = self._page_manager.get_stats().get("current_page") return stats + def get_current_page_name(self) -> Optional[str]: + """Get name of current page.""" + if self._page_manager: + page = self._page_manager.get_current_page() + return page.name if page else None + return None + + def _send(self, data: str) -> bool: + """Send data to Flipper via UART.""" + if not self._serial or not self._serial.is_open: + return False + + try: + self._serial.write(data.encode('utf-8')) + return True + except Exception as e: + logger.error(f"Flipper send error: {e}") + self._connected = False + return False + def _update_loop(self) -> None: """Update loop for sending data to Flipper.""" - logger.debug("Flipper update loop started") + logger.debug("Flipper TX loop started") while self._running: try: - if self._page_manager and self._protocol and self._protocol.is_connected(): - # Update current page - self._page_manager.update() - - # Handle input from Flipper - self._page_manager.handle_input() + if self._page_manager and self._connected: + content = self._page_manager.get_current_content() + if content: + self._send(content) except Exception as e: logger.error(f"Flipper update error: {e}") time.sleep(self._update_interval) - logger.debug("Flipper update loop stopped") + logger.debug("Flipper TX loop stopped") + + def _rx_loop(self) -> None: + """Receive loop for commands from Flipper.""" + logger.debug("Flipper RX loop started") + + while self._running: + try: + if not self._serial or not self._serial.is_open: + time.sleep(0.1) + continue + + # Read line from Flipper + line = self._serial.readline() + if not line: + continue + + line_str = line.decode('utf-8', errors='ignore').strip() + if not line_str: + continue + + # Parse command + command = Protocol.decode_command(line_str) + if command and self._page_manager: + result = self._page_manager.process_command(command) + if result: + self._send(result) + + except Exception as e: + logger.error(f"Flipper RX error: {e}") + + logger.debug("Flipper RX loop stopped")