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 PyQt5.QtWidgets import QApplication
from GUI import MainWindow from SessionManager import SessionManager
from DeviceController import DeviceController from DeviceController import DeviceController
from GUI import MainWindow
def main(): 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) app = QApplication(sys.argv)
win = MainWindow() win = MainWindow()
ctrl = DeviceController(serial="TEST123") # 3) Liste + Buttons
widget = win.add_list_item("TEST123", 0) for i, ctrl in enumerate(controllers):
w = win.add_list_item(ctrl.serial, i)
# 'checked' wird von Qt geliefert; wir fangen es in der Lambda ab # 'checked' wird in der Lambda abgefangen, wir übergeben nur (ctrl, w)
widget.btn_start.clicked.connect(lambda checked=False: (ctrl.start(), widget.set_running(True))) w.btn_start.clicked.connect(lambda checked=False, c=ctrl, ww=w: (c.start(), ww.set_running(True)))
widget.btn_stop.clicked.connect (lambda checked=False: (ctrl.stop(), widget.set_running(False))) w.btn_stop.clicked.connect (lambda checked=False, c=ctrl, ww=w: (c.stop(), ww.set_running(False)))
win.show() win.show()
sys.exit(app.exec_()) sys.exit(app.exec_())
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

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