Simple serial monitor implementation

This commit is contained in:
2026-05-12 20:02:42 +02:00
parent d61ad11428
commit 5c840538fb
13 changed files with 337 additions and 88 deletions

View File

@@ -2,12 +2,17 @@
# Copyright (C) 2026 Association Exergie <association.exergie@gmail.com>
# SPDX-License-Identifier: GPL-3.0-or-later
from concurrent.futures import thread
import os
from queue import Empty
from sys import base_exec_prefix
from threading import Thread
import time
from serial import Serial
import serial.tools.list_ports
import dearpygui.dearpygui as dpg
from dataflux import telemetry_common
from dataflux.tags import TEXT_SERIAL_CONSOLE
import dataflux.telemetry_common.telemetry_common
import dataflux.ui.routines.status
import dataflux.ui.routines
@@ -24,39 +29,96 @@ def list_serial_ports() -> list[str]:
return valid_ports
def connect_lora(state: AppState, device: str) -> None:
if state.lora_port is not None:
state.lora_port.close()
state.lora_port = None
state.lora_port = Serial(port=device, baudrate=115200)
state.lora_thread = Thread(target=lora_reader_worker, args=(state,), daemon=True)
state.lora_status_thread = Thread(
target=lora_status_worker, args=(state,), daemon=True
)
state.lora_thread_running = True
state.lora_status_thread.start()
state.lora_thread.start()
def connect_serial(state: AppState, device: str) -> None:
if state.serial_port is not None:
state.serial_port.close()
state.serial_port = None
state.serial_port = Serial(port=device, baudrate=115200)
state.serial_thread = Thread(target=serial_reader_worker, args=(state,), daemon=True)
state.serial_status_thread = Thread(target=serial_status_worker, args=(state,), daemon=True)
state.serial_thread = Thread(
target=serial_reader_worker, args=(state,), daemon=True
)
state.serial_status_thread = Thread(
target=serial_status_worker, args=(state,), daemon=True
)
state.serial_thread_running = True
state.serial_status_thread.start()
state.serial_thread.start()
def disconnect_lora(state: AppState) -> None:
if state.lora_port is not None:
state.lora_thread_running = False
state.lora_port.close()
state.lora_port = None
def disconnect_serial(state: AppState) -> None:
if state.serial_port is not None:
state.serial_thread_running = False
state.serial_port.close()
state.serial_port = None
def serial_status_worker(state: AppState) -> None:
while state.serial_thread_running:
def lora_status_worker(state: AppState) -> None:
while state.lora_thread_running:
try:
duration = state.serial_status_queue.get(timeout=0.1)
duration = state.lora_status_queue.get(timeout=0.1)
except Empty:
continue
dataflux.ui.routines.status.flash_status_connection_status(duration)
def serial_reader_worker(state: AppState) -> None:
while state.serial_thread_running:
port = state.serial_port
if port is None:
break
if port.closed:
print("Port closed")
break
if port.port is not None and not os.path.exists(port.port):
break
line = port.readline()
if line:
text = line.decode("utf-8", errors="replace")
print(text, end="")
old = dpg.get_value(TEXT_SERIAL_CONSOLE)
dpg.set_value(TEXT_SERIAL_CONSOLE, old + text)
disconnect_serial(state)
dataflux.ui.routines.update_global_connection_status(state)
def serial_status_worker(state: AppState) -> None:
while state.serial_thread_running:
time.sleep(1)
def lora_reader_worker(state: AppState) -> None:
while state.lora_thread_running:
port = state.lora_port
if port is None:
break
if port.closed:
@@ -71,14 +133,14 @@ def serial_reader_worker(state: AppState) -> None:
parsed = parse_uart_packet(packet)
if parsed is not None:
state.packet_queue.put(parsed)
state.serial_status_queue.put(0.1)
state.lora_status_queue.put(0.1)
except Exception as e:
print(f"Serial parser error: {e}")
break
disconnect_serial(state)
disconnect_lora(state)
dataflux.ui.routines.update_global_connection_status(state)
def read_one_uart_packet(port: Serial) -> bytes | None:
first = port.read(1)
@@ -107,19 +169,24 @@ def read_one_uart_packet(port: Serial) -> bytes | None:
return body
def parse_uart_packet(body: bytes) -> dict | None:
if len(body) < dataflux.telemetry_common.telemetry_common.LORA_HEADER_SIZE:
return None
lora = dataflux.telemetry_common.telemetry_common.unpack_lora_header(body[:dataflux.telemetry_common.telemetry_common.LORA_HEADER_SIZE])
payload = body[dataflux.telemetry_common.telemetry_common.LORA_HEADER_SIZE:]
lora = dataflux.telemetry_common.telemetry_common.unpack_lora_header(
body[: dataflux.telemetry_common.telemetry_common.LORA_HEADER_SIZE]
)
payload = body[dataflux.telemetry_common.telemetry_common.LORA_HEADER_SIZE :]
if lora.size != len(payload):
print(f"Serial size mismatch header says {lora.size} actual payload is {len(payload)}")
print(
f"Serial size mismatch header says {lora.size} actual payload is {len(payload)}"
)
return None
calc_crc = dataflux.telemetry_common.telemetry_common.crc16_ccitt(payload)
if calc_crc != lora.crc16:
print("crc mismatch")
return None
@@ -129,13 +196,13 @@ def parse_uart_packet(body: bytes) -> dict | None:
"dest": lora.dest,
"version": lora.version,
}
if lora.version == 1:
pkt = dataflux.telemetry_common.telemetry_common.unpack_packet1(payload)
return {
**base,
"type": "packet1",
"ping": pkt.ping.decode("ascii", errors="replace")
"ping": pkt.ping.decode("ascii", errors="replace"),
}
if lora.version == 2:
@@ -153,5 +220,3 @@ def parse_uart_packet(body: bytes) -> dict | None:
print("Unknown payload")
return None