#!/usr/bin/env python3 """analyze_gamepad_runs.py — 2026-11-03 のゲームパッドの実機の記録 (log.jsonl) 5 本を読み, 送る間隔・長押しの速さ・止まった所を数える (LOG436) python3 analyze_gamepad_runs.py """ import json import os import statistics as st import sys RUNS = ["teleop_20261103_071424", "teleop_20261103_074936", "teleop_20261103_075657", "teleop_20261103_080648", "teleop_20261103_080930"] def load(root, run): rows = [json.loads(l) for l in open(os.path.join(root, run, "log.jsonl"), encoding="utf-8")] meta = json.load(open(os.path.join(root, run, "meta.json"), encoding="utf-8")) return rows, meta def fmt(a): return " ".join("{:+7.2f}".format(x) for x in a) def main(root): print("## 1. 記録ごとのまとめ") print("| 記録 | コミット | 1押しの刻み | 行 | 送った | 受付なし(busy) | 長押しの送る間隔 中央/90% (秒) | 届くまで1秒超 | 押してから送るまで 中央/最大 (秒) |") print("|---|---|---|---|---|---|---|---|---|") for run in RUNS: rows, meta = load(root, run) mv = [r for r in rows if r["kind"] == "move"] sent = [r for r in mv if r.get("sent") is True] busy = sum(1 for r in mv if r.get("reason") == "busy") by = {} for r in sent: if r.get("hold_id") is not None: by.setdefault(r["hold_id"], []).append(r) gaps = [] for rs in by.values(): ts = sorted(x["t_send"] for x in rs if x.get("t_send")) gaps += [b - a for a, b in zip(ts, ts[1:])] lat = [r["t_send"] - r["t_unix"] for r in sent if r.get("t_send")] long_ = sum(1 for r in sent if (r.get("elapsed_sec") or 0) > 1) g = "{:.3f} / {:.3f}".format(st.median(gaps), sorted(gaps)[int(len(gaps) * 0.9)]) if gaps else "-" print("| {} | {} {} | {}° | {} | {} | {} | {} | {} | {:.3f} / {:.2f} |".format( run, meta["git"]["commit"][:7], "" if meta["git"]["script_status"] == "clean" else "(変更あり)", meta["motion"]["step_fine_deg"], len(rows), len(sent), busy, g, long_, st.median(lat), max(lat))) print("\n## 2. 長押し (5 回以上送ったもの) の速さ = 動かした関節の進んだ角度 ÷ かかった時間") for run in RUNS[1:]: rows, _ = load(root, run) by = {} for r in rows: if (r["kind"] == "move" and r.get("sent") is True and r.get("hold_id") is not None and (r.get("hold_n") or 0) >= 1 and r.get("measured_before")): by.setdefault(r["hold_id"], []).append(r) sp = [] for rs in by.values(): if len(rs) < 5: continue j = rs[0]["joint"] d = abs(rs[-1]["measured_before"][j] - rs[0]["measured_before"][j]) t = rs[-1]["t_send"] - rs[0]["t_send"] if t > 0: sp.append(d / t) if sp: print("- {}: 長押し {} 回・中央 {:.1f}°/秒・最小 {:.1f}・最大 {:.1f}".format(run, len(sp), st.median(sp), min(sp), max(sp))) else: print("- {}: 5 回以上の長押しなし".format(run)) print("\n## 3. 届くまで 1 秒を超えた送信 (指令が無視されたもの)") for run in RUNS: rows, _ = load(root, run) t0 = rows[0]["t_unix"] for r in rows: if r["kind"] == "move" and r.get("sent") is True and (r.get("elapsed_sec") or 0) > 1: print("- {} {:7.2f}秒 j{} {:+.1f}° 長押し {}/{} 届くまで {} 秒 ずれ {}°".format( run, r["t_unix"] - t0, r["joint"], r["delta"], r.get("hold_id"), r.get("hold_n"), r["elapsed_sec"], r.get("reach_err"))) print(" 送る前 " + fmt(r["measured_before_send"])) print(" 指令 " + fmt(r["command"])) print(" 届いた " + fmt(r["reached_angles"])) print("\n## 4. 押してから送るまで 1 秒を超えたもの (角度の読み取りが続けて失敗した)") for run in RUNS: rows, _ = load(root, run) t0 = rows[0]["t_unix"] for r in rows: if r["kind"] == "move" and r.get("t_send") and r["t_send"] - r["t_unix"] > 1: print("- {} {:7.2f}秒に押した j{} → 送ったのは {:.2f}秒 ({:.2f} 秒あと)".format( run, r["t_unix"] - t0, r["joint"], r["t_send"] - t0, r["t_send"] - r["t_unix"])) print("\n## 5. 0.5° の押しで, 送る前と届いた角度が変わったか (1 押し 0.5° の 2 本)") for run in RUNS[:2]: rows, _ = load(root, run) moved = same = 0 for r in rows: if r["kind"] == "move" and r.get("sent") is True and abs(r["delta"]) == 0.5 and r.get("reached_angles"): j = r["joint"] if abs(r["reached_angles"][j] - r["measured_before_send"][j]) < 0.2: same += 1 else: moved += 1 print("- {}: 0.5° の送信 {} 回のうち, 読んだ角度が 0.2° 以上動いたのは {} 回".format(run, moved + same, moved)) if __name__ == "__main__": main(sys.argv[1])