add scroll up/down flipper zero
This commit is contained in:
@@ -5,9 +5,12 @@ from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
import socket
|
||||
import os
|
||||
import logging
|
||||
|
||||
from .protocol import Page, PageType
|
||||
|
||||
_logger = logging.getLogger("obd2_client.pages")
|
||||
|
||||
|
||||
class ActionID(Enum):
|
||||
"""Action identifiers for menu items."""
|
||||
@@ -31,7 +34,7 @@ class PageDefinition:
|
||||
class PageManager:
|
||||
"""Manages page content and navigation."""
|
||||
|
||||
MAX_VISIBLE_LINES = 5 # Flipper display limit
|
||||
MAX_VISIBLE_LINES = 4 # Flipper display limit (5 lines overlap with footer)
|
||||
|
||||
def __init__(self):
|
||||
self._pages: List[PageDefinition] = []
|
||||
@@ -170,16 +173,21 @@ class PageManager:
|
||||
True if scroll occurred
|
||||
"""
|
||||
if self._current_index not in range(len(self._pages)):
|
||||
_logger.debug(f"scroll_up: invalid page index {self._current_index}")
|
||||
return False
|
||||
|
||||
page_def = self._pages[self._current_index]
|
||||
if page_def.page_type != PageType.INFO:
|
||||
_logger.debug(f"scroll_up: page type is {page_def.page_type}, not INFO")
|
||||
return False
|
||||
|
||||
current_offset = self._scroll_offsets.get(self._current_index, 0)
|
||||
_logger.debug(f"scroll_up: current_offset={current_offset}")
|
||||
if current_offset > 0:
|
||||
self._scroll_offsets[self._current_index] = current_offset - 1
|
||||
_logger.debug(f"scroll_up: new offset={current_offset - 1}")
|
||||
return True
|
||||
_logger.debug("scroll_up: already at top")
|
||||
return False
|
||||
|
||||
def scroll_down(self) -> bool:
|
||||
@@ -189,20 +197,27 @@ class PageManager:
|
||||
True if scroll occurred
|
||||
"""
|
||||
if self._current_index not in range(len(self._pages)):
|
||||
_logger.debug(f"scroll_down: invalid page index {self._current_index}")
|
||||
return False
|
||||
|
||||
page_def = self._pages[self._current_index]
|
||||
if page_def.page_type != PageType.INFO:
|
||||
_logger.debug(f"scroll_down: page type is {page_def.page_type}, not INFO")
|
||||
return False
|
||||
|
||||
# Get full page content to know total lines
|
||||
page = page_def.generator(self)
|
||||
max_offset = max(0, len(page.lines) - self.MAX_VISIBLE_LINES)
|
||||
total_lines = len(page.lines)
|
||||
max_offset = max(0, total_lines - self.MAX_VISIBLE_LINES)
|
||||
|
||||
current_offset = self._scroll_offsets.get(self._current_index, 0)
|
||||
_logger.debug(f"scroll_down: total_lines={total_lines}, max_offset={max_offset}, current_offset={current_offset}")
|
||||
|
||||
if current_offset < max_offset:
|
||||
self._scroll_offsets[self._current_index] = current_offset + 1
|
||||
_logger.debug(f"scroll_down: new offset={current_offset + 1}")
|
||||
return True
|
||||
_logger.debug("scroll_down: already at bottom")
|
||||
return False
|
||||
|
||||
def navigate_next(self) -> bool:
|
||||
@@ -318,6 +333,7 @@ class PageManager:
|
||||
if state:
|
||||
# Get all values - returns dict of PID -> PIDValue
|
||||
all_values = state.get_all()
|
||||
_logger.debug(f"Live data: got {len(all_values)} PIDs from state")
|
||||
|
||||
if all_values:
|
||||
# Priority order for display
|
||||
@@ -339,8 +355,9 @@ class PageManager:
|
||||
if not lines:
|
||||
lines = ["Waiting for data...", "", "Polling active"]
|
||||
else:
|
||||
lines = ["No connection", "to OBD2", "", "Use Actions menu", "to reconnect"]
|
||||
lines = ["No OBD2 connection", "", "Use Actions menu", "to reconnect"]
|
||||
|
||||
_logger.debug(f"Live data page: {len(lines)} lines")
|
||||
return Page(PageType.INFO, "Live Data", lines)
|
||||
|
||||
def _generate_stats_page(self, mgr: "PageManager") -> Page:
|
||||
|
||||
@@ -199,12 +199,13 @@ class FlipperServer:
|
||||
|
||||
def _handle_line(self, line: str) -> None:
|
||||
"""Handle received line."""
|
||||
self._logger.debug(f"RX raw: '{line.strip()}'")
|
||||
cmd = FlipperProtocol.parse_command(line)
|
||||
if cmd is None:
|
||||
self._logger.debug(f"Unknown command: {line.strip()}")
|
||||
return
|
||||
|
||||
self._logger.debug(f"Received: {cmd.cmd_type.name}")
|
||||
self._logger.info(f"CMD: {cmd.cmd_type.name}" + (f" param={cmd.param}" if cmd.param else ""))
|
||||
|
||||
# INIT can be received at any time (reconnection support)
|
||||
if cmd.cmd_type == CommandType.INIT:
|
||||
@@ -310,13 +311,21 @@ class FlipperServer:
|
||||
|
||||
def _handle_scroll_up(self) -> None:
|
||||
"""Handle scroll up on info pages."""
|
||||
self._logger.debug(f"Scroll UP requested, page {self._page_manager.current_index}")
|
||||
if self._page_manager.scroll_up():
|
||||
self._logger.debug(f"Scroll UP success, offset now {self._page_manager._scroll_offsets.get(self._page_manager.current_index, 0)}")
|
||||
self._send_current_page()
|
||||
else:
|
||||
self._logger.debug("Scroll UP - already at top")
|
||||
|
||||
def _handle_scroll_down(self) -> None:
|
||||
"""Handle scroll down on info pages."""
|
||||
self._logger.debug(f"Scroll DOWN requested, page {self._page_manager.current_index}")
|
||||
if self._page_manager.scroll_down():
|
||||
self._logger.debug(f"Scroll DOWN success, offset now {self._page_manager._scroll_offsets.get(self._page_manager.current_index, 0)}")
|
||||
self._send_current_page()
|
||||
else:
|
||||
self._logger.debug("Scroll DOWN - already at bottom")
|
||||
|
||||
def _handle_refresh(self) -> None:
|
||||
"""Handle refresh request."""
|
||||
|
||||
@@ -179,24 +179,29 @@ class OBD2Client:
|
||||
|
||||
if monitor:
|
||||
self._monitor_loop()
|
||||
else:
|
||||
# Keep running without monitor display
|
||||
self._logger.info("Running without monitor (use Ctrl+C to stop)")
|
||||
while self._running:
|
||||
time.sleep(1.0)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
self._shutdown()
|
||||
|
||||
def _monitor_loop(self) -> None:
|
||||
def _monitor_loop(self, clear_screen: bool = True) -> None:
|
||||
"""Display live values in console."""
|
||||
print("\n" + "=" * 50)
|
||||
print(" OBD2 Live Monitor (Ctrl+C to stop)")
|
||||
print("=" * 50 + "\n")
|
||||
|
||||
while self._running:
|
||||
self._clear_screen()
|
||||
self._print_header()
|
||||
self._print_values()
|
||||
self._print_stats()
|
||||
|
||||
if clear_screen:
|
||||
self._clear_screen()
|
||||
self._print_header()
|
||||
self._print_values()
|
||||
self._print_stats()
|
||||
time.sleep(0.5)
|
||||
|
||||
def _clear_screen(self) -> None:
|
||||
@@ -314,6 +319,12 @@ def main():
|
||||
help="Enable Flipper Zero server on specified serial port (e.g., /dev/serial0)",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--no-monitor",
|
||||
action="store_true",
|
||||
help="Disable live monitor display (useful with --debug)",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--debug",
|
||||
action="store_true",
|
||||
@@ -334,7 +345,7 @@ def main():
|
||||
config.can.virtual = True
|
||||
|
||||
client = OBD2Client(config, flipper_port=args.flipper)
|
||||
client.run(scan_only=args.scan_only)
|
||||
client.run(scan_only=args.scan_only, monitor=not args.no_monitor)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user