Major refactor of Ameba Control Panel v3.1.0: - Three-column layout: icon sidebar, config+history, log view - Dracula PRO theme with light/dark toggle - DTR/RTS GPIO control (replaces ASCII commands) - Multi-CDC firmware support for AmebaSmart control device - Dynamic DUT tabs with +/- management - NN Model flash image support - Settings dialog (Font, Serial, Flash, Command tabs) - Background port scanning, debounced session store - Adaptive log flush rate, format cache optimization - Smooth sidebar animation, deferred startup - pytest test framework with session/log/settings tests - Thread safety fixes: _alive guards, parented timers, safe baud parsing - Find highlight: needle-only highlighting with focused match color - Partial line buffering for table output - PyInstaller packaging with version stamp and module exclusions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
54 lines
1.5 KiB
Python
54 lines
1.5 KiB
Python
from __future__ import annotations
|
|
|
|
from collections import deque
|
|
from dataclasses import dataclass
|
|
from typing import Deque, Sequence
|
|
|
|
from ameba_control_panel import config
|
|
from ameba_control_panel.config import Direction
|
|
from ameba_control_panel.utils.timeutils import timestamp_ms
|
|
|
|
|
|
@dataclass
|
|
class LogLine:
|
|
text: str
|
|
direction: Direction
|
|
timestamp: str
|
|
|
|
def as_display(self) -> str:
|
|
return f"{self.timestamp} {self.text}"
|
|
|
|
|
|
class LogBuffer:
|
|
"""Keeps a bounded archive plus a bounded UI tail."""
|
|
|
|
def __init__(self, max_tail: int = config.UI_LOG_TAIL_LINES) -> None:
|
|
self._max_tail = max_tail
|
|
self._tail: Deque[LogLine] = deque(maxlen=max_tail)
|
|
self._archive: Deque[LogLine] = deque(maxlen=config.LOG_ARCHIVE_MAX)
|
|
|
|
def append(self, text: str, direction: Direction) -> LogLine:
|
|
line = LogLine(text=text.rstrip("\n"), direction=direction, timestamp=timestamp_ms())
|
|
self._tail.append(line)
|
|
self._archive.append(line)
|
|
return line
|
|
|
|
def extend(self, items: Sequence[LogLine]) -> None:
|
|
for line in items:
|
|
self._tail.append(line)
|
|
self._archive.append(line)
|
|
|
|
def tail(self) -> Deque[LogLine]:
|
|
return self._tail
|
|
|
|
def archive(self) -> Deque[LogLine]:
|
|
return self._archive
|
|
|
|
def clear(self) -> None:
|
|
self._tail.clear()
|
|
self._archive.clear()
|
|
|
|
def as_text(self, full: bool = False) -> str:
|
|
source = self._archive if full else self._tail
|
|
return "\n".join(line.as_display() for line in source)
|