test
This commit is contained in:
parent
7d9be883a5
commit
a317fcb5d1
52
Controll.py
52
Controll.py
@ -1,57 +1,19 @@
|
|||||||
import sys
|
import sys
|
||||||
import pysmu
|
|
||||||
from PyQt5.QtWidgets import QApplication
|
from PyQt5.QtWidgets import QApplication
|
||||||
from PyQt5.QtCore import pyqtSignal, QObject
|
from gui import MainWindow
|
||||||
from GUI import MainWindow
|
from device_controller import DeviceController
|
||||||
from DeviceController import DeviceController
|
|
||||||
from functools import partial
|
|
||||||
|
|
||||||
class ControllerSignals(QObject):
|
|
||||||
started = pyqtSignal(str) # serial
|
|
||||||
stopped = pyqtSignal(str)
|
|
||||||
error = pyqtSignal(str, str) # serial, msg
|
|
||||||
|
|
||||||
signals = ControllerSignals()
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# 1) Geräte einsammeln (pysmu nur hier benutzen)
|
|
||||||
sess = pysmu.Session()
|
|
||||||
sess.add_all()
|
|
||||||
|
|
||||||
controllers = {dev.serial: DeviceController(sess, dev) for dev in sess.devices}
|
|
||||||
serials = list(controllers.keys())
|
|
||||||
print("Gefundene Geräte:", serials)
|
|
||||||
|
|
||||||
# Optional: Modi setzen
|
|
||||||
for serial in serials:
|
|
||||||
c = controllers[serial]
|
|
||||||
c.set_mode("A", pysmu.Mode.SIMV) # CH A z. B.
|
|
||||||
c.set_mode("B", pysmu.Mode.HI_Z) # CH B z. B.
|
|
||||||
|
|
||||||
# 2) GUI erstellen
|
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
win = MainWindow()
|
win = MainWindow()
|
||||||
|
|
||||||
# Kleine Helfer, damit die ListItem-Buttons nur Controller.start/stop aufrufen
|
ctrl = DeviceController(serial="TEST123")
|
||||||
def on_start(ctrl, widget):
|
widget = win.add_list_item("TEST123", 0)
|
||||||
ctrl.start()
|
|
||||||
widget.set_running(True)
|
|
||||||
|
|
||||||
def on_stop(ctrl, widget):
|
# 'checked' wird von Qt geliefert; wir fangen es in der Lambda ab
|
||||||
ctrl.stop()
|
widget.btn_start.clicked.connect(lambda checked=False: (ctrl.start(), widget.set_running(True)))
|
||||||
widget.set_running(False)
|
widget.btn_stop.clicked.connect (lambda checked=False: (ctrl.stop(), widget.set_running(False)))
|
||||||
|
|
||||||
# 3) Liste befüllen (auf der INSTANZ, nicht auf der Klasse!)
|
|
||||||
for i, serial in enumerate(serials):
|
|
||||||
ctrl = controllers[serial]
|
|
||||||
w = win.add_list_item(serial, i)
|
|
||||||
|
|
||||||
# WICHTIG: die Lambda fängt 'checked' ab (erster Parameter), wir ignorieren ihn.
|
|
||||||
w.btn_start.clicked.connect(lambda checked=False, c=ctrl, ww=w: on_start(c, ww))
|
|
||||||
w.btn_stop.clicked.connect (lambda checked=False, c=ctrl, ww=w: on_stop(c, ww))
|
|
||||||
|
|
||||||
|
|
||||||
# 4) Start
|
|
||||||
win.show()
|
win.show()
|
||||||
sys.exit(app.exec_())
|
sys.exit(app.exec_())
|
||||||
|
|
||||||
|
|||||||
@ -1,23 +1,23 @@
|
|||||||
import threading, queue, time, csv, os
|
# device_controller.py
|
||||||
|
import threading, queue, time, math, os, csv
|
||||||
|
|
||||||
CHUNK = 2000
|
SAMPLE_RATE = 10_000.0 # 10 kS/s (Dummy)
|
||||||
SAMPLE_RATE = 100_000.0 # 100 kS/s
|
|
||||||
DT = 1.0 / SAMPLE_RATE
|
DT = 1.0 / SAMPLE_RATE
|
||||||
OUTDIR = "logs"
|
CHUNK = 500 # 50 ms pro Chunk
|
||||||
FLUSH_EVERY = 50
|
OUTDIR = "./logs"
|
||||||
os.makedirs(OUTDIR, exist_ok=True)
|
|
||||||
|
|
||||||
class DeviceController:
|
class DeviceController:
|
||||||
def __init__(self, sess, dev):
|
def __init__(self, serial="DUMMY"):
|
||||||
self.sess = sess # <— Session referenzieren
|
self.serial = serial
|
||||||
self.dev = dev
|
|
||||||
self.cmdq = queue.Queue()
|
self.cmdq = queue.Queue()
|
||||||
|
self.writer_q = queue.Queue(maxsize=100)
|
||||||
self.stop_evt = threading.Event()
|
self.stop_evt = threading.Event()
|
||||||
self.running = False
|
self.running = False
|
||||||
self.writer_q = queue.Queue(maxsize=50)
|
|
||||||
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) ===
|
||||||
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()
|
||||||
@ -26,81 +26,76 @@ class DeviceController:
|
|||||||
def stop(self):
|
def stop(self):
|
||||||
self.cmdq.put(("stop", None))
|
self.cmdq.put(("stop", None))
|
||||||
|
|
||||||
def set_mode(self, ch, mode):
|
def shutdown(self):
|
||||||
# BUGFIX: nicht rekursiv sich selbst aufrufen!
|
self.stop()
|
||||||
self.dev.channels[ch].mode = mode
|
self.stop_evt.set()
|
||||||
|
# nicht joinen aus dem GUI-Thread; nur beim Programmende joinen!
|
||||||
|
|
||||||
|
# === Dummy-Reader: erzeugt Daten, statt pysmu zu lesen ===
|
||||||
def reader_loop(self):
|
def reader_loop(self):
|
||||||
|
phase = 0.0
|
||||||
|
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
|
||||||
try:
|
try:
|
||||||
cmd, arg = self.cmdq.get_nowait()
|
cmd, arg = self.cmdq.get_nowait()
|
||||||
if cmd == "start" and not self.running:
|
if cmd == "start":
|
||||||
# Continuous Session-Stream starten (für alle Devices)
|
|
||||||
self.sess.start(0) # <— statt dev.run(0)
|
|
||||||
self.running = True
|
self.running = True
|
||||||
elif cmd == "stop" and self.running:
|
print("[DUMMY] start")
|
||||||
self.sess.end() # <— Session sauber beenden
|
elif cmd == "stop":
|
||||||
# optional: Puffer leeren
|
|
||||||
# self.sess.flush(-1, True) # falls verfügbar in deiner Version
|
|
||||||
self.running = False
|
self.running = False
|
||||||
elif cmd == "mode":
|
print("[DUMMY] stop")
|
||||||
ch, mode = arg
|
|
||||||
self.dev.channels[ch].mode = mode
|
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if self.running:
|
if not self.running:
|
||||||
try:
|
time.sleep(0.02)
|
||||||
# Lies jeweils CHUNK Samples (blockierend bis voll)
|
continue
|
||||||
data = self.dev.read(CHUNK, -1) # -> [[VA, IA, VB, IB], ...]
|
|
||||||
vsA = [row[0] for row in data]
|
|
||||||
vsB = [row[2] for row in data]
|
|
||||||
self.writer_q.put((time.time(), vsA, vsB))
|
|
||||||
except Exception:
|
|
||||||
time.sleep(0.001)
|
|
||||||
else:
|
|
||||||
time.sleep(0.01)
|
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# === Writer: robust, Wide-Format ===
|
||||||
def writer_loop(self):
|
def writer_loop(self):
|
||||||
os.makedirs(OUTDIR, exist_ok=True)
|
os.makedirs(OUTDIR, exist_ok=True)
|
||||||
ts0 = None
|
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.dev.serial}.csv")
|
|
||||||
chunks_written = 0
|
|
||||||
sample_idx = 0
|
sample_idx = 0
|
||||||
|
written_rows = 0
|
||||||
try:
|
try:
|
||||||
with open(fn, "w", newline="") as f:
|
with open(fn, "w", newline="") as f:
|
||||||
w = csv.writer(f)
|
w = csv.writer(f)
|
||||||
# Long-Format: eine Zeile pro Kanalwert
|
w.writerow(["t_rel_s", "A", "B"]) # Wide-Format
|
||||||
w.writerow(["t_rel_s", "ch", "value"])
|
|
||||||
|
|
||||||
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.2)
|
ts, va, vb = self.writer_q.get(timeout=0.5)
|
||||||
except Exception:
|
except queue.Empty:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if ts0 is None:
|
n = min(len(va), len(vb))
|
||||||
ts0 = ts
|
for i in range(n):
|
||||||
|
|
||||||
# Sicherstellen, dass A und B gleich lang sind
|
|
||||||
if len(va) != len(vb):
|
|
||||||
n = min(len(va), len(vb))
|
|
||||||
va, vb = va[:n], vb[:n]
|
|
||||||
|
|
||||||
# Zeitindex aus Sample-Index ableiten (konstante Rate)
|
|
||||||
for i, (a, b) in enumerate(zip(va, vb)):
|
|
||||||
t_rel = (sample_idx + i) * DT
|
t_rel = (sample_idx + i) * DT
|
||||||
w.writerow([t_rel, "A", a])
|
w.writerow([t_rel, va[i], vb[i]])
|
||||||
w.writerow([t_rel, "B", b])
|
sample_idx += n
|
||||||
|
written_rows += n
|
||||||
sample_idx += len(va)
|
|
||||||
chunks_written += 1
|
|
||||||
|
|
||||||
if chunks_written % FLUSH_EVERY == 0:
|
|
||||||
f.flush() # optional: os.fsync(f.fileno())
|
|
||||||
|
|
||||||
|
# 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")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[{self.dev.serial}] Writer-Fehler: {e}")
|
print(f"[{self.serial}] Writer-Fehler: {e}")
|
||||||
finally:
|
finally:
|
||||||
print(f"[{self.dev.serial}] Datei geschlossen: {fn}")
|
print(f"[{self.serial}] Datei geschlossen: {fn}")
|
||||||
62
GUI.py
62
GUI.py
@ -1,30 +1,19 @@
|
|||||||
import sys
|
# gui.py
|
||||||
from functools import partial
|
from PyQt5.QtWidgets import QWidget, QListWidget, QListWidgetItem, QPushButton, QLabel, QHBoxLayout, QVBoxLayout
|
||||||
from PyQt5.QtWidgets import (
|
|
||||||
QApplication, QWidget, QListWidget, QListWidgetItem,
|
|
||||||
QPushButton, QLabel, QHBoxLayout, QVBoxLayout
|
|
||||||
)
|
|
||||||
|
|
||||||
class ListItemWidget(QWidget):
|
class ListItemWidget(QWidget):
|
||||||
def __init__(self, text, index):
|
def __init__(self, text, index):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.text = text
|
|
||||||
self.index = index
|
self.index = index
|
||||||
|
lay = QHBoxLayout(self)
|
||||||
layout = QHBoxLayout(self)
|
lay.addWidget(QLabel(text))
|
||||||
|
lay.addStretch()
|
||||||
label = QLabel(text)
|
|
||||||
layout.addWidget(label)
|
|
||||||
|
|
||||||
layout.addStretch() # schiebt die Buttons nach rechts
|
|
||||||
|
|
||||||
self.btn_start = QPushButton("Start")
|
self.btn_start = QPushButton("Start")
|
||||||
self.btn_stop = QPushButton("Stop")
|
self.btn_stop = QPushButton("Stop")
|
||||||
self.btn_stop.setEnabled(False)
|
self.btn_stop.setEnabled(False)
|
||||||
|
lay.addWidget(self.btn_start)
|
||||||
layout.addWidget(self.btn_start)
|
lay.addWidget(self.btn_stop)
|
||||||
layout.addWidget(self.btn_stop)
|
lay.setContentsMargins(5,2,5,2)
|
||||||
layout.setContentsMargins(5, 2, 5, 2)
|
|
||||||
|
|
||||||
def set_running(self, running: bool):
|
def set_running(self, running: bool):
|
||||||
self.btn_start.setEnabled(not running)
|
self.btn_start.setEnabled(not running)
|
||||||
@ -33,36 +22,15 @@ class ListItemWidget(QWidget):
|
|||||||
class MainWindow(QWidget):
|
class MainWindow(QWidget):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.setWindowTitle("Geräteliste")
|
self.setWindowTitle("Logger")
|
||||||
self.resize(420, 320)
|
lay = QVBoxLayout(self)
|
||||||
|
|
||||||
main_layout = QVBoxLayout(self)
|
|
||||||
|
|
||||||
self.list_widget = QListWidget()
|
self.list_widget = QListWidget()
|
||||||
main_layout.addWidget(self.list_widget)
|
lay.addWidget(self.list_widget)
|
||||||
|
|
||||||
# Beispiel-Daten
|
|
||||||
# for i in range(5):
|
|
||||||
# self.add_list_item(f"Eintrag {i+1}", i+1, self.handle_start, self.handle_stop)
|
|
||||||
|
|
||||||
def add_list_item(self, text, index):
|
def add_list_item(self, text, index):
|
||||||
item = QListWidgetItem()
|
item = QListWidgetItem()
|
||||||
widget = ListItemWidget(text, index)
|
w = ListItemWidget(text, index)
|
||||||
item.setSizeHint(widget.sizeHint())
|
item.setSizeHint(w.sizeHint())
|
||||||
self.list_widget.addItem(item)
|
self.list_widget.addItem(item)
|
||||||
self.list_widget.setItemWidget(item, widget)
|
self.list_widget.setItemWidget(item, w)
|
||||||
return widget
|
return w
|
||||||
|
|
||||||
# Die Aktionen, die beim Klick ausgeführt werden sollen
|
|
||||||
def handle_start(self, index):
|
|
||||||
print(f"Start gedrückt bei Eintrag {index}")
|
|
||||||
|
|
||||||
def handle_stop(self, index):
|
|
||||||
print(f"Stop gedrückt bei Eintrag {index}")
|
|
||||||
|
|
||||||
|
|
||||||
def startGUI():
|
|
||||||
app = QApplication(sys.argv)
|
|
||||||
w = MainWindow()
|
|
||||||
w.show()
|
|
||||||
sys.exit(app.exec_())
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user