test
This commit is contained in:
parent
559f512bee
commit
b2406ed037
39
Controll.py
39
Controll.py
@ -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()
|
|
||||||
@ -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}")
|
|
||||||
36
GUI.py
36
GUI.py
@ -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
|
|
||||||
@ -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
|
|
||||||
97
device_worker.py
Normal file
97
device_worker.py
Normal file
@ -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()
|
||||||
43
gui_main.py
Normal file
43
gui_main.py
Normal file
@ -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_())
|
||||||
17
main.py
Normal file
17
main.py
Normal file
@ -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()
|
||||||
39
multi_logger.py
Normal file
39
multi_logger.py
Normal file
@ -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()
|
||||||
20
session_manager.py
Normal file
20
session_manager.py
Normal file
@ -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()
|
||||||
Loading…
x
Reference in New Issue
Block a user