diff --git a/bridges/s3-bridge/s3_bridge.py b/bridges/s3-bridge/s3_bridge.py index 4f1d46b..f3e1770 100644 --- a/bridges/s3-bridge/s3_bridge.py +++ b/bridges/s3-bridge/s3_bridge.py @@ -93,8 +93,11 @@ class SessionLogger: self._bin_fh = open(bin_path, "ab", buffering=0) self._lock = threading.Lock() # Optional pure-byte taps (no headers). BW=Blastware tx, S3=device tx. + # These can be opened/closed on demand via start_raw_capture/stop_raw_capture. self._raw_bw = open(raw_bw_path, "ab", buffering=0) if raw_bw_path else None self._raw_s3 = open(raw_s3_path, "ab", buffering=0) if raw_s3_path else None + self._cap_bw_path: Optional[str] = raw_bw_path + self._cap_s3_path: Optional[str] = raw_s3_path def log_line(self, line: str) -> None: with self._lock: @@ -124,6 +127,43 @@ class SessionLogger: self.log_line(f"[{ts}] [INFO] {msg}") self.bin_write_record(REC_INFO, msg.encode("utf-8", errors="replace")) + def start_raw_capture(self, label: str, logdir: str) -> tuple: + """Open new raw tap files for a named capture. Returns (bw_path, s3_path).""" + ts = _dt.datetime.now().strftime("%Y%m%d_%H%M%S") + safe = "".join(c if c.isalnum() or c in "-_" else "_" for c in label)[:40] if label else "" + suffix = f"_{safe}" if safe else "" + bw_path = os.path.join(logdir, f"raw_bw_{ts}{suffix}.bin") + s3_path = os.path.join(logdir, f"raw_s3_{ts}{suffix}.bin") + with self._lock: + # Close any previously open taps first + if self._raw_bw: + self._raw_bw.close() + if self._raw_s3: + self._raw_s3.close() + self._raw_bw = open(bw_path, "ab", buffering=0) + self._raw_s3 = open(s3_path, "ab", buffering=0) + self._cap_bw_path = bw_path + self._cap_s3_path = s3_path + self.log_info(f"raw capture started: label={label!r} bw={bw_path} s3={s3_path}") + return bw_path, s3_path + + def stop_raw_capture(self) -> tuple: + """Close raw tap files. Returns (bw_path, s3_path) for the capture just closed.""" + with self._lock: + bw = self._cap_bw_path + s3 = self._cap_s3_path + if self._raw_bw: + self._raw_bw.close() + self._raw_bw = None + if self._raw_s3: + self._raw_s3.close() + self._raw_s3 = None + self._cap_bw_path = None + self._cap_s3_path = None + if bw: + self.log_info(f"raw capture stopped: bw={bw} s3={s3}") + return bw, s3 + def close(self) -> None: with self._lock: try: @@ -291,8 +331,18 @@ def forward_loop( time.sleep(0.002) -def annotation_loop(logger: SessionLogger, stop: threading.Event) -> None: - print("[MARK] Type 'm' + Enter to annotate the capture. Ctrl+C to stop.") +def annotation_loop(logger: SessionLogger, logdir: str, stop: threading.Event) -> None: + """ + Reads stdin commands while the bridge runs. + + Commands: + m — prompt for a mark label (interactive) + CAP_START: