fix
This commit is contained in:
parent
2cde913975
commit
8ea1532650
34
Controll.py
34
Controll.py
@ -1,18 +1,36 @@
|
|||||||
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_())
|
||||||
|
|||||||
@ -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,28 +63,35 @@ class DeviceController:
|
|||||||
time.sleep(0.02)
|
time.sleep(0.02)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# CHUNK Samples erzeugen
|
# ——— Daten lesen (blockierend, aber im Worker-Thread) ———
|
||||||
va, vb = [], []
|
try:
|
||||||
for _ in range(CHUNK):
|
# pysmu: read liefert Liste von [VA, IA, VB, IB]
|
||||||
a = math.sin(phase) # Kanal A
|
samples = self.dev.read(CHUNK, -1) # je nach Version evtl. self.dev.read(CHUNK)
|
||||||
b = 0.5*math.sin(phase*0.5) # Kanal B
|
if not samples:
|
||||||
va.append(a)
|
time.sleep(0.001)
|
||||||
vb.append(b)
|
continue
|
||||||
phase += step
|
|
||||||
|
|
||||||
|
va = [row[0] for row in samples]
|
||||||
|
vb = [row[2] for row in samples]
|
||||||
|
|
||||||
|
# in Writer-Queue schieben
|
||||||
try:
|
try:
|
||||||
self.writer_q.put((time.time(), va, vb), timeout=0.5)
|
self.writer_q.put((time.time(), va, vb), timeout=0.5)
|
||||||
except queue.Full:
|
except queue.Full:
|
||||||
print("[WARN] writer_q voll – Datenverlust")
|
print(f"[{self.serial}] WARN: writer_q voll – dropping chunk")
|
||||||
# kurze Pause, um CPU zu schonen
|
except Exception as e:
|
||||||
time.sleep(CHUNK * DT)
|
# 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)
|
||||||
@ -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
28
SessionManager.py
Normal 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
|
||||||
Loading…
x
Reference in New Issue
Block a user