Files
mppt-testbench/code64/debug_console/serial_worker.py
grabowski e7a23a3c7e Add LVSolarBuck64 firmware and debug console with uv support
STM32G474RB firmware for solar buck converter with MPPT, CC control,
Vfly compensation, and adaptive deadtime. Includes Textual TUI debug
console for real-time telemetry, parameter tuning, and SQLite logging.

Added pyproject.toml for uv: `cd code64 && uv run debug-console`

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 16:38:23 +07:00

101 lines
3.0 KiB
Python

"""Threading-based serial reader/writer."""
import threading
import queue
import time
from typing import Callable, Optional
import serial
from .protocol import FrameParser, build_ping
class SerialWorker:
"""Manages serial port in a background thread."""
def __init__(self, port: str, baudrate: int = 460800):
self.port = port
self.baudrate = baudrate
self.ser: Optional[serial.Serial] = None
self._rx_thread: Optional[threading.Thread] = None
self._running = False
self._parser = FrameParser()
self._tx_queue: queue.Queue = queue.Queue()
self._frame_callback: Optional[Callable] = None
self._status_callback: Optional[Callable] = None
self.connected = False
self.pkt_count = 0
self.drop_count = 0
self._last_seq = -1
def set_frame_callback(self, cb: Callable):
self._frame_callback = cb
def set_status_callback(self, cb: Callable):
self._status_callback = cb
def start(self):
self._running = True
self._rx_thread = threading.Thread(target=self._run, daemon=True)
self._rx_thread.start()
def stop(self):
self._running = False
if self._rx_thread:
self._rx_thread.join(timeout=2)
if self.ser and self.ser.is_open:
self.ser.close()
self.connected = False
def send(self, data: bytes):
self._tx_queue.put(data)
def _notify_status(self, connected: bool):
self.connected = connected
if self._status_callback:
self._status_callback(connected)
def _run(self):
while self._running:
# Connect
if not self.ser or not self.ser.is_open:
try:
self.ser = serial.Serial(
self.port, self.baudrate, timeout=0.1
)
self._parser = FrameParser()
self._notify_status(True)
except serial.SerialException:
self._notify_status(False)
time.sleep(1)
continue
# TX
try:
while not self._tx_queue.empty():
data = self._tx_queue.get_nowait()
self.ser.write(data)
except (serial.SerialException, OSError):
self._handle_disconnect()
continue
# RX
try:
data = self.ser.read(256)
if data:
for cmd, payload in self._parser.feed(data):
self.pkt_count += 1
if self._frame_callback:
self._frame_callback(cmd, payload)
except (serial.SerialException, OSError):
self._handle_disconnect()
def _handle_disconnect(self):
try:
if self.ser:
self.ser.close()
except Exception:
pass
self.ser = None
self._notify_status(False)
time.sleep(1)