diff --git a/Controll.py b/Controll.py index 325146f..4c67cac 100644 --- a/Controll.py +++ b/Controll.py @@ -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() \ No newline at end of file diff --git a/DeviceController.py b/DeviceController.py index c202965..6e3adec 100644 --- a/DeviceController.py +++ b/DeviceController.py @@ -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}") \ No newline at end of file + print(f"[{self.serial}] Writer beendet: {fn}") \ No newline at end of file diff --git a/SessionManager.py b/SessionManager.py new file mode 100644 index 0000000..835e394 --- /dev/null +++ b/SessionManager.py @@ -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 \ No newline at end of file