This commit is contained in:
Vincent Hanewinkel 2025-08-14 22:28:30 +02:00
parent 2cde913975
commit 8ea1532650
3 changed files with 106 additions and 43 deletions

View File

@ -1,21 +1,39 @@
import sys
import sys, pysmu
from functools import partial
from PyQt5.QtWidgets import QApplication
from GUI import MainWindow
from SessionManager import SessionManager
from DeviceController import DeviceController
from GUI import MainWindow
def main():
# 1) Session + Devices
sess = pysmu.Session()
sess.add_all()
sm = SessionManager(sess)
devices = list(sess.devices)
print("Gefundene Geräte:", [d.serial for d in devices])
controllers = [DeviceController(sm, dev) for dev in devices]
# Optional: Modi setzen (A/B)
for c in controllers:
c.set_mode("A", pysmu.Mode.SIMV)
c.set_mode("B", pysmu.Mode.HI_Z)
# 2) GUI
app = QApplication(sys.argv)
win = MainWindow()
ctrl = DeviceController(serial="TEST123")
widget = win.add_list_item("TEST123", 0)
# 'checked' wird von Qt geliefert; wir fangen es in der Lambda ab
widget.btn_start.clicked.connect(lambda checked=False: (ctrl.start(), widget.set_running(True)))
widget.btn_stop.clicked.connect (lambda checked=False: (ctrl.stop(), widget.set_running(False)))
# 3) Liste + Buttons
for i, ctrl in enumerate(controllers):
w = win.add_list_item(ctrl.serial, i)
# 'checked' wird in der Lambda abgefangen, wir übergeben nur (ctrl, w)
w.btn_start.clicked.connect(lambda checked=False, c=ctrl, ww=w: (c.start(), ww.set_running(True)))
w.btn_stop.clicked.connect (lambda checked=False, c=ctrl, ww=w: (c.stop(), ww.set_running(False)))
win.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
main()

View File

@ -6,45 +6,56 @@ DT = 1.0 / SAMPLE_RATE
CHUNK = 500 # 50 ms pro Chunk
OUTDIR = "./logs"
def _norm_channel(ch):
return {0:"A", 1:"B"}.get(ch, str(ch).upper())
class DeviceController:
def __init__(self, serial="DUMMY"):
self.serial = serial
def __init__(self, session_manager, dev):
self.sm = session_manager # <— SessionManager (teilt die Session)
self.dev = dev
self.serial = getattr(dev, "serial", "UNKNOWN")
self.cmdq = queue.Queue()
self.writer_q = queue.Queue(maxsize=100)
self.writer_q = queue.Queue(maxsize=200)
self.stop_evt = threading.Event()
self.running = False
self.reader_t = threading.Thread(target=self.reader_loop, daemon=True)
self.writer_t = threading.Thread(target=self.writer_loop, daemon=True)
# === öffentliche API (nicht blockierend) ===
# ==== Public API (nicht blockierend) ====
def start(self):
if not self.reader_t.is_alive(): self.reader_t.start()
if not self.writer_t.is_alive(): self.writer_t.start()
self.cmdq.put(("start", None))
self.cmdq.put(("start", None)) # nur anstoßen
def stop(self):
self.cmdq.put(("stop", None))
self.cmdq.put(("stop", None)) # nur anstoßen
def shutdown(self):
# Für Programmende
self.stop()
self.stop_evt.set()
# nicht joinen aus dem GUI-Thread; nur beim Programmende joinen!
# === Dummy-Reader: erzeugt Daten, statt pysmu zu lesen ===
def set_mode(self, ch, mode):
key = _norm_channel(ch)
self.dev.channels[key].mode = mode
# ==== Threads ====
def reader_loop(self):
phase = 0.0
step = 2*math.pi * 50.0 * DT # 50 Hz Sinus
print(f"[{self.serial}] Reader gestartet")
while not self.stop_evt.is_set():
# Kommandos abarbeiten
# Kommandos (Start/Stop) abarbeiten
try:
cmd, arg = self.cmdq.get_nowait()
if cmd == "start":
if cmd == "start" and not self.running:
n = self.sm.start() # evtl. startet hier die Session
self.running = True
print("[DUMMY] start")
elif cmd == "stop":
print(f"[{self.serial}] RUNNING (Session-Clients={n})")
elif cmd == "stop" and self.running:
n = self.sm.stop() # evtl. endet hier die Session
self.running = False
print("[DUMMY] stop")
print(f"[{self.serial}] STOPPED (Session-Clients={n})")
except queue.Empty:
pass
@ -52,32 +63,39 @@ class DeviceController:
time.sleep(0.02)
continue
# CHUNK Samples erzeugen
va, vb = [], []
for _ in range(CHUNK):
a = math.sin(phase) # Kanal A
b = 0.5*math.sin(phase*0.5) # Kanal B
va.append(a)
vb.append(b)
phase += step
# ——— Daten lesen (blockierend, aber im Worker-Thread) ———
try:
self.writer_q.put((time.time(), va, vb), timeout=0.5)
except queue.Full:
print("[WARN] writer_q voll Datenverlust")
# kurze Pause, um CPU zu schonen
time.sleep(CHUNK * DT)
# pysmu: read liefert Liste von [VA, IA, VB, IB]
samples = self.dev.read(CHUNK, -1) # je nach Version evtl. self.dev.read(CHUNK)
if not samples:
time.sleep(0.001)
continue
va = [row[0] for row in samples]
vb = [row[2] for row in samples]
# in Writer-Queue schieben
try:
self.writer_q.put((time.time(), va, vb), timeout=0.5)
except queue.Full:
print(f"[{self.serial}] WARN: writer_q voll dropping chunk")
except Exception as e:
# NICHT schlucken sichtbar loggen!
print(f"[{self.serial}] Read-Fehler: {e}")
time.sleep(0.01)
print(f"[{self.serial}] Reader beendet")
# === Writer: robust, Wide-Format ===
def writer_loop(self):
os.makedirs(OUTDIR, exist_ok=True)
fn = os.path.join(OUTDIR, f"{time.strftime('%Y%m%d_%H%M%S')}_{self.serial}.csv")
sample_idx = 0
written_rows = 0
print(f"[{self.serial}] Writer schreibt nach: {fn}")
try:
with open(fn, "w", newline="") as f:
w = csv.writer(f)
w.writerow(["t_rel_s", "A", "B"]) # Wide-Format
w.writerow(["t_rel_s", "A", "B"]) # Wide-Format
while not (self.stop_evt.is_set() and self.writer_q.empty()):
try:
ts, va, vb = self.writer_q.get(timeout=0.5)
@ -91,11 +109,10 @@ class DeviceController:
sample_idx += n
written_rows += n
# damit man live sieht, dass wirklich geschrieben wird
if written_rows and written_rows % (10*CHUNK) == 0:
f.flush()
print(f"[{self.serial}] geschrieben: {written_rows} Zeilen")
print(f"[{self.serial}] rows={written_rows}")
except Exception as e:
print(f"[{self.serial}] Writer-Fehler: {e}")
finally:
print(f"[{self.serial}] Datei geschlossen: {fn}")
print(f"[{self.serial}] Writer beendet: {fn}")

28
SessionManager.py Normal file
View File

@ -0,0 +1,28 @@
import threading
class SessionManager:
def __init__(self, sess):
self.sess = sess
self._lock = threading.Lock()
self._running_count = 0 # wie viele Devices wollen gerade streamen?
def start(self):
with self._lock:
if self._running_count == 0:
# Erster „Kunde“ → Session-Stream starten
self.sess.start(0) # 0 = endlos
self._running_count += 1
return self._running_count
def stop(self):
with self._lock:
if self._running_count > 0:
self._running_count -= 1
if self._running_count == 0:
# Letzter „Kunde“ ist weg → Session beenden
self.sess.end()
return self._running_count
def running_devices(self):
with self._lock:
return self._running_count