# device_controller.py import threading, queue, time, math, os, csv 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): print(f"[{self.serial}] Reader start (decim={self.decim}, eff_rate={self.eff_rate:.0f} S/s)") while not self.stop_evt.is_set(): # Kommandos try: cmd, _ = self.cmdq.get_nowait() if cmd == "start" and not self.running: self.sm.start() # Session ggf. starten self.running = True elif cmd == "stop" and self.running: self.running = False self.sm.stop() # Session ggf. stoppen except queue.Empty: pass if not self.running: time.sleep(0.02) continue try: # pysmu: Liste von [VA, IA, VB, IB] samples = self.dev.read(CHUNK_RAW, -1) # je nach Version evtl. .read(CHUNK_RAW) if not samples: time.sleep(0.001) continue # Kanäle extrahieren va_raw = [row[0] for row in samples] vb_raw = [row[2] for row in samples] # -------- Decimation: jedes N-te Sample -------- if self.decim > 1: va = va_raw[::self.decim] vb = vb_raw[::self.decim] else: va, vb = va_raw, vb_raw # In Writer-Queue; bei voller Queue ältestes verwerfen (Ringpuffer) try: self.writer_q.put_nowait((va, vb)) except queue.Full: try: _ = self.writer_q.get_nowait() # drop oldest except queue.Empty: pass finally: # (erneut versuchen, blockiert jetzt nicht) try: self.writer_q.put_nowait((va, vb)) except queue.Full: # wenn immer noch voll, dann überspringen wir diesen Chunk pass except Exception as e: print(f"[{self.serial}] Read-Fehler: {e}") time.sleep(0.01) print(f"[{self.serial}] Reader stop") # -------- 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") sample_idx = 0 rows_since_flush = 0 print(f"[{self.serial}] Writer -> {fn} (dt={self.dt:.6e}s)") try: with open(fn, "w", newline="") as f: w = csv.writer(f) w.writerow(["t_rel_s", "A", "B"]) while not (self.stop_evt.is_set() and self.writer_q.empty()): try: va, vb = self.writer_q.get(timeout=0.5) except queue.Empty: continue n = min(len(va), len(vb)) # Zeitachse mit effektiver Rate for i in range(n): t_rel = (sample_idx + i) * self.dt w.writerow([t_rel, va[i], vb[i]]) sample_idx += n rows_since_flush += n if rows_since_flush >= 10_000: # alle ~10k Samples flushen f.flush() rows_since_flush = 0 except Exception as e: print(f"[{self.serial}] Writer-Fehler: {e}") finally: print(f"[{self.serial}] Writer beendet: {fn}")