| |
| """Socket-level probe — debug 'no data' before touching the GUI. |
| |
| Binds the local receive port (2244), sends the init sequence (b -> rate -> *) to the |
| board's command endpoint (192.168.4.1:8086), and prints exactly what comes back and |
| from where. Shows your local IPs so you can confirm the Mac holds 192.168.4.2. |
| |
| python src/acquisition/probe.py |
| python src/acquisition/probe.py --start B # try UPPERCASE start byte |
| """ |
| from __future__ import annotations |
|
|
| import argparse |
| import socket |
| import sys |
| import time |
| from pathlib import Path |
|
|
| sys.path.insert(0, str(Path(__file__).resolve().parents[1])) |
| from common.montage import ADC_MICROVOLTS_PER_COUNT |
|
|
| RATE = {250: b"1", 500: b"2", 1000: b"3"} |
|
|
|
|
| def local_ips(): |
| ips = set() |
| try: |
| ips.update(socket.gethostbyname_ex(socket.gethostname())[2]) |
| except OSError: |
| pass |
| |
| try: |
| s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
| s.connect(("192.168.4.1", 8086)); ips.add(s.getsockname()[0]); s.close() |
| except OSError: |
| pass |
| return sorted(ips) |
|
|
|
|
| def main() -> None: |
| ap = argparse.ArgumentParser(description=__doc__) |
| ap.add_argument("--host", default="192.168.4.1") |
| ap.add_argument("--cmd-port", type=int, default=8086) |
| ap.add_argument("--local-port", type=int, default=2244) |
| ap.add_argument("--sfreq", type=int, default=250) |
| ap.add_argument("--seconds", type=float, default=3.0) |
| ap.add_argument("--start", default="b", help="start byte (default lowercase 'b'; try 'B')") |
| args = ap.parse_args() |
|
|
| ips = local_ips() |
| print(f"local IPs: {ips}") |
| if "192.168.4.2" not in ips: |
| print(" ⚠ none of your IPs is 192.168.4.2 — the board sends data to .2, so it may") |
| print(" not reach you. Join WiFi 'ESPBCI' and check `ifconfig | grep 192.168.4`.") |
|
|
| s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
| s.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1 << 20) |
| try: |
| s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) |
| except OSError: |
| pass |
| s.bind(("0.0.0.0", args.local_port)) |
| print(f"bound local :{args.local_port}, commands -> {args.host}:{args.cmd_port}") |
|
|
| for cmd in (args.start.encode(), RATE.get(args.sfreq, b"1"), b"*"): |
| try: |
| s.sendto(cmd, (args.host, args.cmd_port)) |
| print(f" sent {cmd!r}") |
| except OSError as e: |
| print(f" ⚠ send {cmd!r} failed: {e} (route to {args.host}? on the WiFi?)") |
| time.sleep(0.2) |
|
|
| s.settimeout(2.0) |
| stream = bytearray() |
| ndg, sizes, srcs = 0, set(), set() |
| t0 = time.time() |
| try: |
| while time.time() - t0 < args.seconds: |
| pkt, addr = s.recvfrom(65536) |
| ndg += 1; sizes.add(len(pkt)); srcs.add(addr); stream.extend(pkt) |
| except socket.timeout: |
| pass |
|
|
| print(f"\nreceived {ndg} datagrams ({len(stream)} bytes) in {args.seconds:.0f}s | " |
| f"sizes={sorted(sizes)} | sources={srcs}") |
| if ndg == 0: |
| print("NO DATA. Check: (1) Mac joined WiFi 'ESPBCI' (pw 12345678)? " |
| "(2) a local IP == 192.168.4.2? (3) `ping 192.168.4.1` works? " |
| "(4) board accepts the start byte — retry with `--start B`.") |
| return |
|
|
| |
| FRAME = 105 |
| frames, i = [], 0 |
| while i <= len(stream) - FRAME: |
| if stream[i] == 0xA0 and stream[i + FRAME - 1] == 0xC0: |
| frames.append(bytes(stream[i:i + FRAME])); i += FRAME |
| else: |
| i += 1 |
| print(f"reframed {len(frames)} valid 105-B frames (0xA0…0xC0) from the stream") |
| if not frames: |
| print("⚠ 收到数据但解不出 105B 帧 —— 通道数可能不是 32,或帧格式不同。") |
| return |
|
|
| import numpy as np |
| from common.montage import CAP32_CHANNELS as CH |
|
|
| def decode(fr): |
| v = np.empty(32) |
| for c in range(32): |
| o = 2 + c * 3 |
| raw = int.from_bytes(fr[o:o + 3], "big") |
| if raw & 0x800000: |
| raw -= 16777216 |
| v[c] = raw * ADC_MICROVOLTS_PER_COUNT |
| return v |
|
|
| M = np.array([decode(fr) for fr in frames]) |
| print(f" seqs (should ++): {[fr[1] for fr in frames[:16]]}") |
| print(f" trigger(frame0): {int.from_bytes(frames[0][100:104], 'big')}") |
| raw_mean, raw_std = M.mean(0), M.std(0) |
| car = M - np.median(M, axis=1, keepdims=True) |
| car_std = car.std(0) |
|
|
| print("\n ch raw_mean(µV) raw_std(µV) | CAR_std(µV)") |
| for c in range(32): |
| print(f" {CH[c]:<4} {raw_mean[c]:11.1f} {raw_std[c]:11.1f} | {car_std[c]:9.1f}") |
| print(f"\n common-mode test: median raw_std={np.median(raw_std):.0f}µV " |
| f"-> after CAR={np.median(car_std):.1f}µV") |
| if np.median(car_std) < max(1.0, np.median(raw_std) / 3): |
| print(" ✅ 大部分是共模(参考漂移);CAR 后大幅下降 —— GUI 已加 CAR,重开应正常。") |
| else: |
| print(" ⚠ CAR 后仍大 —— 可能不只是共模;看是否某些电极浮空/接触差,或通道顺序/对齐问题。") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|