Files
NmeaClient/backend/file_backend.py
T
jensandClaude Sonnet 4.6 ff686b64f7 gui: add File source type to connection tab
- ConnectionConfig: new file_path and delay fields
- ConnectionRow: File page with path input, Browse button, delay
  spinner (0–10 s), and auto-fill of receiver-ID from filename
- ReceiverManager.connect_receiver: FileBackend wired for 'file' type;
  UBX poll timer skipped for file sources
- FileBackend.register_xcvr: fallback to first unoccupied entry when
  name does not match filename pattern

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 12:47:28 +02:00

82 lines
2.4 KiB
Python

import re
import time
from threading import Thread
from backend.a_backend import ABackend
from msg.container import MsgContainer
_FILENAME_RE = re.compile(r'\d{4}_\d{2}_\d{2}_\d{2}_\d{2}_\d{2}_(.+)\.log$')
_CHUNK = 64
def source_id_from_path(path: str) -> str | None:
m = _FILENAME_RE.search(path)
return m.group(1) if m else None
class FileBackend(ABackend):
def __init__(self, files: list[str], delay: float = 1.0):
self._delay = max(0.0, min(10.0, delay))
self._entries = [{'path': p, 'source_id': source_id_from_path(p), 'xcvr': None, 'file': None, 'thread': None}
for p in files]
self._running = False
@property
def delay(self) -> float:
return self._delay
@delay.setter
def delay(self, value: float):
self._delay = max(0.0, min(10.0, value))
def register_xcvr(self, xcvr):
for e in self._entries:
if e['source_id'] == xcvr.name.strip():
e['xcvr'] = xcvr
xcvr.on_register(self)
return
for e in self._entries:
if e['xcvr'] is None:
e['xcvr'] = xcvr
xcvr.on_register(self)
return
raise Exception(f"No available log file entry for \"{xcvr.name}\"")
def connect(self):
for e in self._entries:
e['file'] = open(e['path'], 'rb')
def start(self):
self._running = True
for e in self._entries:
e['thread'] = Thread(target=self._run, args=(e,), daemon=True)
e['thread'].start()
def stop(self):
self._running = False
for e in self._entries:
if e['thread'] and e['thread'].is_alive():
e['thread'].join(timeout=self._delay + 1.0)
def join(self):
for e in self._entries:
if e['thread']:
e['thread'].join()
def disconnect(self):
for e in self._entries:
if e['file']:
e['file'].close()
e['file'] = None
def _run(self, entry: dict):
xcvr = entry['xcvr']
f = entry['file']
while self._running:
data = f.read(_CHUNK)
if not data:
break
if xcvr:
xcvr.on_recv(MsgContainer(data, xcvr))
if self._delay > 0:
time.sleep(self._delay)