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>
This commit is contained in:
2026-03-12 16:38:23 +07:00
co-authored by Claude Opus 4.6
parent 3f65b5f2f2
commit e7a23a3c7e
151 changed files with 231098 additions and 0 deletions
+100
View File
@@ -0,0 +1,100 @@
"""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)