diff --git a/Controll.py b/Controll.py deleted file mode 100644 index 4c67cac..0000000 --- a/Controll.py +++ /dev/null @@ -1,39 +0,0 @@ -import sys, pysmu -from functools import partial -from PyQt5.QtWidgets import QApplication -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() - - # 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() \ No newline at end of file diff --git a/DeviceController.py b/DeviceController.py deleted file mode 100644 index b0f9d37..0000000 --- a/DeviceController.py +++ /dev/null @@ -1,106 +0,0 @@ -# device_controller.py -import threading, queue, time, math, os, csv, statistics - -RAW_SAMPLE_RATE = 100_000.0 # M1K nominal -CHUNK_RAW = 2000 # ~20 ms pro Chunk pro Gerät -OUTDIR = "./logs" - -class DeviceController: - def __init__(self, session_manager, dev, effective_rate_hz=10_000): - self.sm = session_manager - self.dev = dev - self.serial = getattr(dev, "serial", "UNKNOWN") - - # ---- Decimation berechnen ---- - # z.B. effective_rate_hz=10_000 -> DECIM=10 -> effektiv 10 kS/s - self.decim = max(1, int(round(RAW_SAMPLE_RATE / float(effective_rate_hz)))) - self.eff_rate = RAW_SAMPLE_RATE / self.decim - self.dt = 1.0 / self.eff_rate - - self.cmdq = queue.Queue() - # größere Queue; wenn voll, verwerfen wir älteste Chunks ("Ringpuffer") - self.writer_q = queue.Queue(maxsize=400) - 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) - - 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)) - - def stop(self): - self.cmdq.put(("stop", None)) - - def set_mode(self, ch, mode): - key = {0:"A",1:"B"}.get(ch, str(ch).upper()) - self.dev.channels[key].mode = mode - - # -------- Reader: liest roh und decimiert -------- - def reader_loop(self): - filter_window_size = 10 # wie in deinem Logger - interval = 0.1 # 10 Hz Mess-Update - last_log_time = 0 - - while not self.stop_evt.is_set(): - # Start/Stop Kommandos - try: - cmd, _ = self.cmdq.get_nowait() - if cmd == "start" and not self.running: - self.sm.start() - self.running = True - elif cmd == "stop" and self.running: - self.sm.stop() - self.running = False - except queue.Empty: - pass - - if not self.running: - time.sleep(0.05) - continue - - try: - # Rohdaten holen (filter_window_size Samples) - samples = self.dev.read(filter_window_size, -1) - if not samples: - time.sleep(interval) - continue - - # Kanäle extrahieren & mitteln - va = statistics.mean(row[0] for row in samples) - vb = statistics.mean(row[2] for row in samples) - - now = time.time() - if now - last_log_time >= 1.0: # nur jede Sekunde loggen - self.writer_q.put((now, va, vb)) - last_log_time = now - - except Exception as e: - print(f"[{self.serial}] Read-Fehler: {e}") - time.sleep(0.01) - - # Loop-Rate steuern - time.sleep(max(0.05, interval)) - - # -------- Writer: Wide-Format, effektive Zeitachse -------- - 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") - print(f"[{self.serial}] Writer -> {fn}") - try: - with open(fn, "w", newline="") as f: - w = csv.writer(f) - w.writerow(["timestamp", "A", "B"]) - while not (self.stop_evt.is_set() and self.writer_q.empty()): - try: - ts, va, vb = self.writer_q.get(timeout=0.5) - except queue.Empty: - continue - w.writerow([ts, va, vb]) - f.flush() - except Exception as e: - print(f"[{self.serial}] Writer-Fehler: {e}") - finally: - print(f"[{self.serial}] Writer beendet: {fn}") \ No newline at end of file diff --git a/GUI.py b/GUI.py deleted file mode 100644 index a5fb940..0000000 --- a/GUI.py +++ /dev/null @@ -1,36 +0,0 @@ -# gui.py -from PyQt5.QtWidgets import QWidget, QListWidget, QListWidgetItem, QPushButton, QLabel, QHBoxLayout, QVBoxLayout - -class ListItemWidget(QWidget): - def __init__(self, text, index): - super().__init__() - self.index = index - lay = QHBoxLayout(self) - lay.addWidget(QLabel(text)) - lay.addStretch() - self.btn_start = QPushButton("Start") - self.btn_stop = QPushButton("Stop") - self.btn_stop.setEnabled(False) - lay.addWidget(self.btn_start) - lay.addWidget(self.btn_stop) - lay.setContentsMargins(5,2,5,2) - - def set_running(self, running: bool): - self.btn_start.setEnabled(not running) - self.btn_stop.setEnabled(running) - -class MainWindow(QWidget): - def __init__(self): - super().__init__() - self.setWindowTitle("Logger") - lay = QVBoxLayout(self) - self.list_widget = QListWidget() - lay.addWidget(self.list_widget) - - def add_list_item(self, text, index): - item = QListWidgetItem() - w = ListItemWidget(text, index) - item.setSizeHint(w.sizeHint()) - self.list_widget.addItem(item) - self.list_widget.setItemWidget(item, w) - return w diff --git a/SessionManager.py b/SessionManager.py deleted file mode 100644 index 835e394..0000000 --- a/SessionManager.py +++ /dev/null @@ -1,28 +0,0 @@ -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 diff --git a/device_worker.py b/device_worker.py new file mode 100644 index 0000000..8ba674b --- /dev/null +++ b/device_worker.py @@ -0,0 +1,97 @@ +import threading, queue, time, csv, os, statistics + +class DeviceWorker: + def __init__(self, sm, dev, outdir, + filter_window_size=10, interval=0.1): + self.sm = sm + self.dev = dev + self.serial = getattr(dev, "serial", "UNKNOWN") + self.filter_window_size = filter_window_size + self.interval = interval + self.outdir = outdir + + self.cmdq = queue.Queue() + 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) + self.writer_q = queue.Queue(maxsize=50) # 1 Wert/Sek → reicht dicke + + 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)) + + def stop(self): + self.cmdq.put(("stop", None)) + + def set_mode(self, ch, mode): + key = {0:"A",1:"B"}.get(ch, str(ch).upper()) + self.dev.channels[key].mode = mode + + def reader_loop(self): + last_log = 0.0 + while not self.stop_evt.is_set(): + # Start/Stop Kommandos + try: + cmd, _ = self.cmdq.get_nowait() + if cmd == "start" and not self.running: + self.sm.start() + self.running = True + elif cmd == "stop" and self.running: + self.running = False + self.sm.stop() + except queue.Empty: + pass + + if not self.running: + time.sleep(0.05) + continue + + try: + # exakt wie dein Single-Loop: + samples = self.dev.read(self.filter_window_size, -1) # ggf. read(n) + if not samples: + time.sleep(self.interval) + continue + + vA = statistics.mean(row[0] for row in samples) + vB = statistics.mean(row[2] for row in samples) + + now = time.time() + if now - last_log >= 1.0: # 1 Hz logging + try: + self.writer_q.put((now, vA, vB), timeout=0.2) + last_log = now + except queue.Full: + pass # selten, bei 1Hz praktisch nie + except Exception as e: + print(f"[{self.serial}] Read-Fehler: {e}") + time.sleep(0.02) + + # Loop-Rate wie bei dir: + time.sleep(max(0.05, self.interval)) + + def writer_loop(self): + os.makedirs(self.outdir, exist_ok=True) + fn = os.path.join(self.outdir, f"{time.strftime('%Y%m%d_%H%M%S')}_{self.serial}.csv") + try: + with open(fn, "w", newline="") as f: + w = csv.writer(f) + w.writerow(["timestamp","A","B"]) + while not (self.stop_evt.is_set() and self.writer_q.empty()): + try: + ts, vA, vB = self.writer_q.get(timeout=0.5) + except queue.Empty: + continue + w.writerow([ts, vA, vB]) + f.flush() # 1 Zeile/Sek → ok + except Exception as e: + print(f"[{self.serial}] Writer-Fehler: {e}") + finally: + print(f"[{self.serial}] Datei geschlossen: {fn}") + + def shutdown(self): + self.stop() + self.stop_evt.set() \ No newline at end of file diff --git a/gui_main.py b/gui_main.py new file mode 100644 index 0000000..746f33b --- /dev/null +++ b/gui_main.py @@ -0,0 +1,43 @@ +import sys +from PyQt5.QtWidgets import QApplication, QWidget, QListWidget, QListWidgetItem, QVBoxLayout, QPushButton, QLabel, QHBoxLayout +from multi_logger import MultiLogger + +class ListItemWidget(QWidget): + def __init__(self, text, worker): + super().__init__() + self.worker = worker + layout = QHBoxLayout(self) + layout.addWidget(QLabel(text)) + layout.addStretch() + btn_start = QPushButton("Start") + btn_stop = QPushButton("Stop") + btn_start.clicked.connect(lambda: self.worker.start()) + btn_stop.clicked.connect(lambda: self.worker.stop()) + layout.addWidget(btn_start) + layout.addWidget(btn_stop) + layout.setContentsMargins(5, 2, 5, 2) + +class MainWindow(QWidget): + def __init__(self, logger): + super().__init__() + self.logger = logger + self.setWindowTitle("Multi-Geräte-Logger") + self.resize(400, 300) + main_layout = QVBoxLayout(self) + self.list_widget = QListWidget() + main_layout.addWidget(self.list_widget) + for worker in self.logger.workers: + item = QListWidgetItem() + widget = ListItemWidget(worker.serial, worker) + item.setSizeHint(widget.sizeHint()) + self.list_widget.addItem(item) + self.list_widget.setItemWidget(item, widget) + +if __name__ == "__main__": + ml = MultiLogger(outdir="./logs", filter_window_size=10, interval=0.1) + ml.set_default_modes() + + app = QApplication(sys.argv) + window = MainWindow(ml) + window.show() + sys.exit(app.exec_()) \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..0b901b4 --- /dev/null +++ b/main.py @@ -0,0 +1,17 @@ +from multi_logger import MultiLogger + +if __name__ == "__main__": + ml = MultiLogger(outdir="./logs", filter_window_size=10, interval=0.1) + print("Geräte:", [w.serial for w in ml.workers]) + ml.set_default_modes() + + # Start/Stop wie gewohnt (z. B. via GUI-Buttons) + ml.start_all() + + # ... hier deine GUI laufen lassen, oder kurz testen: + import time + time.sleep(5) # 5 Sekunden messen + ml.stop_all() + + # sauber beenden + ml.shutdown() \ No newline at end of file diff --git a/multi_logger.py b/multi_logger.py new file mode 100644 index 0000000..613999f --- /dev/null +++ b/multi_logger.py @@ -0,0 +1,39 @@ +import pysmu, os +from session_manager import SessionManager +from device_worker import DeviceWorker + +class MultiLogger: + def __init__(self, outdir="./logs", + filter_window_size=10, interval=0.1): + self.outdir = outdir + self.filter_window_size = filter_window_size + self.interval = interval + + self.sess = pysmu.Session() + self.sess.add_all() + self.sm = SessionManager(self.sess) + + self.devices = list(self.sess.devices) + self.workers = [DeviceWorker(self.sm, dev, outdir, + filter_window_size, interval) for dev in self.devices] + + def set_default_modes(self): + # wie gehabt: A/B-Modi setzen + for w in self.workers: + try: + w.set_mode("A", pysmu.Mode.SIMV) + w.set_mode("B", pysmu.Mode.HI_Z) + except Exception as e: + print(f"[{w.serial}] Mode-Set-Fehler: {e}") + + def start_all(self): + for w in self.workers: + w.start() + + def stop_all(self): + for w in self.workers: + w.stop() + + def shutdown(self): + for w in self.workers: + w.shutdown() \ No newline at end of file diff --git a/session_manager.py b/session_manager.py new file mode 100644 index 0000000..945d754 --- /dev/null +++ b/session_manager.py @@ -0,0 +1,20 @@ +import threading + +class SessionManager: + def __init__(self, sess): + self.sess = sess + self._lock = threading.Lock() + self._count = 0 + + def start(self): + with self._lock: + if self._count == 0: + self.sess.start(0) # kontinuierlich + self._count += 1 + + def stop(self): + with self._lock: + if self._count > 0: + self._count -= 1 + if self._count == 0: + self.sess.end() \ No newline at end of file