commit b3f290a88802443e38753323358a75ade1577e03 Author: Eiichi Shimazu Date: Sat Nov 14 19:27:42 2026 +0900 実機推論の 1 チャンクの中継点の間隔を lab_start.sh から変えられるようにする (依頼 182・Log35). lab_start.sh に --via-stride N (0〜10. 既定は今までの 3) を足し, run_policy_client.py の --via-stride にそのまま渡す (実機の 8 とドライランの 2 か所). 起動時に [VIA ] で送る step を出す (3 → step 2, 5, 8, 9 / 5 → step 4, 9 / 0 → 中継点なし. plan_vias と同じ並び). 先頭の 0 は 10 進で読む. --help は 2〜66 行. run_policy_client.py は変えない. テストを足す (lab_start 237 (境界 1・10・08・09. codex R1)・test_flow_send 18 (stride 5 で途中の点 1 つを待たずに送り最後だけ到着を待つ・max_move は計画と送る直前の両方で効く)) (依頼 182. codex 2 回) Co-Authored-By: Claude Opus 5.5 --- lab_start.sh | 20 +++++++++++++++++--- pi05/test_flow_send.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ test_lab_start.sh | 33 ++++++++++++++++++++++++++++++--- 3 files changed, 91 insertions(+), 6 deletions(-) diff --git a/lab_start.sh b/lab_start.sh index 18b1cb0..394710d 100755 --- a/lab_start.sh +++ b/lab_start.sh @@ -38,6 +38,8 @@ # run_policy_client.py の既定 20. 起動時に [GRIP ] で使う値を出す. 依頼 152) # --flow / --speed N run_policy_client.py にそのまま渡す (中継点を止まらずに流す / チャンクを送る速さ 1〜100. # 付けなければ今までどおり. 依頼 176. 起動時に [FLOW ] で出す) +# --via-stride N 1 チャンクの中継点の間隔 (0〜10. run_policy_client.py にそのまま渡す. 既定 3 = step 2, 5, 8, 9 の 4 点. +# 5 なら step 4, 9 の 2 点. 依頼 182. 起動時に [VIA ] で出す) # --step-confirm 実機推論でチャンクごとに確かめ (Enter) を待つ今までの形に戻す. 付けなければ (既定) run_policy_client.py に # --no-step-confirm を渡し, 始めの確かめのあとは止まらずに進み, スペース (Enter も可) を押したときだけ次の推論の # あとで入力欄を出す (依頼 178). 起動時に [CONFIRM] の行でどちらかを出す @@ -93,6 +95,7 @@ MODEL=trim TILT_FIX_ARGS=() # 依頼 144: 既定は傾きをソフトでそろえる. --no-tilt-fix で回さない GRIP_ARGS=() # 依頼 152: --grip-value N のときだけ run_policy_client.py に --grip-value N を渡す GRIP_VALUE_SHOWN="20 (既定. run_policy_client.py の GRIP_VALUE)" +VIA_STRIDE=3 # 依頼 182: --via-stride N で変える (既定は今までの 3) FLOW_ARGS=() # 依頼 176: --flow / --speed N のときだけ run_policy_client.py に渡す STEP_ARGS=(--no-step-confirm) # 依頼 178: 既定はチャンクごとに確かめない. --step-confirm で今までどおり FINISH_ARGS=(--auto-finish) # 依頼 181: 既定は置き終えたら自動で終わる. --no-auto-finish で今までどおり @@ -110,6 +113,10 @@ while [ $# -gt 0 ]; do [ ${#2} -le 3 ] && [ "$((10#$2))" -le 100 ] || { echo "[ERROR] --grip-value は 0〜100 の整数: $2" >&2; exit 2; } GRIP_ARGS=(--grip-value "$((10#$2))"); GRIP_VALUE_SHOWN="$((10#$2)) (--grip-value)"; shift 2 ;; --flow) FLOW_ARGS+=(--flow); shift ;; + --via-stride) [ $# -ge 2 ] || { echo "[ERROR] --via-stride には 0〜10 の整数を付ける" >&2; exit 2; } + case "$2" in ''|*[!0-9]*) echo "[ERROR] --via-stride は 0〜10 の整数: $2" >&2; exit 2 ;; esac + [ ${#2} -le 2 ] && [ "$((10#$2))" -le 10 ] || { echo "[ERROR] --via-stride は 0〜10 の整数: $2" >&2; exit 2; } + VIA_STRIDE="$((10#$2))"; shift 2 ;; --step-confirm) STEP_ARGS=(); shift ;; --no-auto-finish) FINISH_ARGS=(); shift ;; --speed) [ $# -ge 2 ] || { echo "[ERROR] --speed には 1〜100 の整数を付ける" >&2; exit 2; } @@ -118,7 +125,7 @@ while [ $# -gt 0 ]; do FLOW_ARGS+=(--speed "$((10#$2))"); shift 2 ;; --model) [ $# -ge 2 ] || { echo "[ERROR] --model には ${LAB_MODELS// / か } を付ける" >&2; exit 2; } # 値がないと shift 2 が失敗して回り続ける (codex R1) MODEL="$2"; shift 2 ;; - -h|--help) sed -n '2,64p' "$0"; exit 0 ;; + -h|--help) sed -n '2,66p' "$0"; exit 0 ;; *) echo "[ERROR] 知らない引数: $1 (./lab_start.sh --help)" >&2; exit 2 ;; esac done @@ -167,6 +174,13 @@ else echo "[TILT ] 上カメラの傾きのお手本 (各エピソードの 1 枚目の中央値) は $RAW_RANGE から (安全範囲と同じ. --no-tilt-fix なので回さない. 依頼 142)" fi echo "[GRIP ] 爪の閉じる値は $GRIP_VALUE_SHOWN. 締め直し r はこの値−3 とこの値を交互に送る (依頼 152)" +if [ "$VIA_STRIDE" = 0 ]; then + echo "[VIA ] --via-stride 0: 中継点なし (1 チャンクで選んだ 1 点だけ送る. 依頼 182)" +else + VIA_STEPS="" # run_policy_client.py の plan_vias と同じ: N-1, 2N-1, ... (9 より前) と最後の 9 (--steps-per-chunk 10) + for ((k = VIA_STRIDE - 1; k < 9; k += VIA_STRIDE)); do VIA_STEPS+="$k, "; done + echo "[VIA ] --via-stride $VIA_STRIDE: 1 チャンクを step ${VIA_STEPS}9 で送る (グリッパーが変わるチャンクはその手前まで. 依頼 182)" +fi [ ${#FLOW_ARGS[@]} = 0 ] || echo "[FLOW ] 推論の送り方: ${FLOW_ARGS[*]} (依頼 176)" # 付けなければ今までと同じ出力 if [ ${#STEP_ARGS[@]} = 0 ]; then echo "[CONFIRM] チャンクごとに確かめる (--step-confirm. 推論のたびに Enter を待つ)" @@ -549,14 +563,14 @@ while :; do [ $rc = 0 ] || [ $rc = 130 ] || fail "camera_align.py が失敗 (終了コード $rc)" stage_done stage "8. 実機推論 ($round 回目)" - python3 pi05/run_policy_client.py --raw-dir "$RAW_RANGE" --steps-per-chunk 10 --via-stride 3 \ + python3 pi05/run_policy_client.py --raw-dir "$RAW_RANGE" --steps-per-chunk 10 --via-stride "$VIA_STRIDE" \ --settle-retries 0 --state-from-target --execute --ignore-framing --model "$MODEL_TAG" --framing-raw-dir "$RAW" \ --tilt-raw-dir "$RAW_RANGE" ${TILT_FIX_ARGS[@]+"${TILT_FIX_ARGS[@]}"} ${GRIP_ARGS[@]+"${GRIP_ARGS[@]}"} ${FINISH_ARGS[@]+"${FINISH_ARGS[@]}"} ${STEP_ARGS[@]+"${STEP_ARGS[@]}"} ${FLOW_ARGS[@]+"${FLOW_ARGS[@]}"} \ ${CONFIRM_ARGS[@]+"${CONFIRM_ARGS[@]}"} rc=$? else stage "8. 推論のクライアント (ドライラン・--dry-keys. アームには触らない)" - python3 pi05/run_policy_client.py --raw-dir "$RAW_RANGE" --steps-per-chunk 10 --via-stride 3 \ + python3 pi05/run_policy_client.py --raw-dir "$RAW_RANGE" --steps-per-chunk 10 --via-stride "$VIA_STRIDE" \ --settle-retries 0 --state-from-target --dry-keys --ignore-framing --model "$MODEL_TAG" --framing-raw-dir "$RAW" \ --tilt-raw-dir "$RAW_RANGE" ${TILT_FIX_ARGS[@]+"${TILT_FIX_ARGS[@]}"} ${GRIP_ARGS[@]+"${GRIP_ARGS[@]}"} ${FINISH_ARGS[@]+"${FINISH_ARGS[@]}"} ${STEP_ARGS[@]+"${STEP_ARGS[@]}"} ${FLOW_ARGS[@]+"${FLOW_ARGS[@]}"} \ ${CONFIRM_ARGS[@]+"${CONFIRM_ARGS[@]}"} diff --git a/pi05/test_flow_send.py b/pi05/test_flow_send.py index e66cee3..2ca4382 100644 --- a/pi05/test_flow_send.py +++ b/pi05/test_flow_send.py @@ -338,6 +338,50 @@ class TestFlowClient(unittest.TestCase): run_flow(BASE + argv, self.td, MovingArm()) +class TestFlowStride5(unittest.TestCase): + """--via-stride 5 (step 4, 9 の 2 点) でも今の決まりのまま送る (依頼 182). 送り方は stride 3 と同じ道を通る.""" + def setUp(self): + self.td = Path(tempfile.mkdtemp()) + time.sleep(1.1) + + def tearDown(self): + rpc.CHUNK_SPEED = rpc.SPEED + self.assertEqual(rpc.j0_sweep_scan.SPEED, 20) + + def argv(self, *extra): + a = list(BASE) + a[a.index("--via-stride") + 1] = "5" + return a + list(extra) + + def test_two_points_flow(self): + """途中の点 step 4 は待たずに 1 回送り, 最後の step 9 だけ到着を待つ.""" + arm = MovingArm(rate=5.0) + code, lines, out = run_flow(self.argv("--flow", "--flow-tol", "0.5", "--max-chunks", "1"), self.td, arm) + self.assertEqual(code, 0) + self.assertEqual([s[1] for s in arm.fast_sends], [[5.0, 2.5, 0.0, 0.0, 0.0, -30.0]]) + self.assertEqual([s[1] for s in arm.wait_sends], ["pi05_chunk000_via09"]) + np.testing.assert_allclose(arm.wait_sends[0][2], [10.0, 5.0, 0, 0, 0, -30]) + ch = [x for x in lines if x.get("type") is None and "chunk" in x][0] + self.assertEqual([v["step"] for v in ch["vias"]], [4, 9]) + self.assertEqual(ch["vias"][0]["flow"]["advance"], "time") + self.assertGreaterEqual(ch["vias"][0]["flow"]["send_gap_s"], rpc.FLOW_MIN_INTERVAL_SEC - 1e-3) + self.assertEqual(ch["vias"][1]["flow"]["advance"], "arrived") + self.assertTrue(ch["arrived"] and ch["executed"]) + self.assertIn("中継点 2 点: step 4, 9", out) + + def test_max_move_still_applies(self): + """1 点の移動が大きくなっても max_move で切る (計画のときも, 送る直前に実測からかけ直すときも).""" + arm = MovingArm(rate=200.0) + code, lines, out = run_flow(self.argv("--flow", "--max-move-deg", "3", "--max-chunks", "1"), self.td, arm) + self.assertEqual(code, 0) + self.assertEqual(arm.fast_sends[0][1][0], 3.0) # step 4 の j0 +5° を +3° に + ch = [x for x in lines if x.get("type") is None and "chunk" in x][0] + self.assertTrue(any("±3°" in n for n in ch["vias"][0]["notes"])) + qa_last = np.asarray(ch["vias"][0]["actual_after"]) + self.assertLessEqual(float(np.max(np.abs(np.asarray(arm.wait_sends[0][2]) - qa_last))), 3.0 + 0.5) + self.assertTrue(any("±3°" in n for n in ch["vias"][1]["notes"])) + + class TestSettleSpeed(unittest.TestCase): def tearDown(self): rpc.CHUNK_SPEED = rpc.SPEED diff --git a/test_lab_start.sh b/test_lab_start.sh index c7cd4e6..0f32674 100755 --- a/test_lab_start.sh +++ b/test_lab_start.sh @@ -600,7 +600,7 @@ check "--serve-only でも空打ちまで行う (依頼 176)" 'has out.txt "=== check "Pod は消さない (DELETE を呼ばない) で, かかった時間と片付け方を出す" '[ $(n_call DELETE) = 0 ] && has out.txt "=== かかった時間" && has out.txt "Pod は残しています (id $ID_NEW). 片付けは ./lab_stop.sh --log LogT"' check "記録 lab_start_*.txt に残る・始めの行に --serve-only と出る" 'grep -qF "サーバーとトンネルだけ (--serve-only" "$T/data/lab/LogT"/lab_start_*.txt' rm -rf "$T" -check "--help に --serve-only・--grip-value・--flow / --speed・5b の空打ちの説明が入る (2〜64 行)" 'sed -n "2,64p" "$HERE/lab_start.sh" | grep -qF -- "--serve-only Pod を作って" && sed -n "2,64p" "$HERE/lab_start.sh" | grep -qF -- "--no-tilt-fix 傾きを" && sed -n "2,64p" "$HERE/lab_start.sh" | grep -qF -- "--grip-value N 爪の閉じる値" && sed -n "2,64p" "$HERE/lab_start.sh" | grep -qF -- "--flow / --speed N run_policy_client.py に" && sed -n "2,64p" "$HERE/lab_start.sh" | grep -qF -- "# 5b. 空打ち" && grep -qF "sed -n '"'"'2,64p'"'"'" "$HERE/lab_start.sh"' +check "--help に --serve-only・--grip-value・--flow / --speed・5b の空打ちの説明が入る (2〜66 行)" 'sed -n "2,66p" "$HERE/lab_start.sh" | grep -qF -- "--serve-only Pod を作って" && sed -n "2,66p" "$HERE/lab_start.sh" | grep -qF -- "--no-tilt-fix 傾きを" && sed -n "2,66p" "$HERE/lab_start.sh" | grep -qF -- "--grip-value N 爪の閉じる値" && sed -n "2,66p" "$HERE/lab_start.sh" | grep -qF -- "--flow / --speed N run_policy_client.py に" && sed -n "2,66p" "$HERE/lab_start.sh" | grep -qF -- "# 5b. 空打ち" && grep -qF "sed -n '"'"'2,66p'"'"'" "$HERE/lab_start.sh"' echo "== 空打ち (依頼 176): トンネルのあと・アームの前に 1 回. 失敗しても止めずに続ける" setup; state $ID_OLD; pod 200 "$RUNNING"; probe 1 1 1 1 1; run @@ -660,7 +660,7 @@ setup; state $ID_OLD; pod 200 "$RUNNING"; probe 1 1 1 1 1; run --step-confirm -- check "--step-confirm なら --no-step-confirm を渡さず (ほかの引数は同じ), [CONFIRM] で出す" '[ $RC = 0 ] && has py.log "pi05/run_policy_client.py" && ! has py.log "--no-step-confirm" && grep -q -- "pi05/run_policy_client.py .* --flow$" "$T/py.log" && has out.txt "[CONFIRM] チャンクごとに確かめる (--step-confirm"' rm -rf "$T" check "実機の 8 にも既定で --no-step-confirm を渡す (8 の 2 か所)" '[ "$(grep -c '"'"'${STEP_ARGS\[@\]+"${STEP_ARGS\[@\]}"}'"'"' "$HERE/lab_start.sh")" = 2 ] && grep -qF "STEP_ARGS=(--no-step-confirm)" "$HERE/lab_start.sh" && grep -qF -- "--step-confirm) STEP_ARGS=(); shift ;;" "$HERE/lab_start.sh"' -check "--help に --step-confirm の説明が入る" 'sed -n "2,64p" "$HERE/lab_start.sh" | grep -qF -- "--step-confirm 実機推論で"' +check "--help に --step-confirm の説明が入る" 'sed -n "2,66p" "$HERE/lab_start.sh" | grep -qF -- "--step-confirm 実機推論で"' echo "== 置き終えたら自動で終わる (依頼 181): 既定は --auto-finish を渡す. --no-auto-finish で今までどおり" setup; state $ID_OLD; pod 200 "$RUNNING"; probe 1 1 1 1 1; run @@ -670,7 +670,34 @@ setup; state $ID_OLD; pod 200 "$RUNNING"; probe 1 1 1 1 1; run --no-auto-finish check "--no-auto-finish なら --auto-finish を渡さず (ほかの引数は同じ), [FINISH] で出す" '[ $RC = 0 ] && has py.log "pi05/run_policy_client.py" && ! has py.log "--auto-finish" && grep -q -- "pi05/run_policy_client.py .* --no-step-confirm --flow$" "$T/py.log" && has out.txt "[FINISH] 置き終えても自動では終わらない (--no-auto-finish"' rm -rf "$T" check "実機の 8 にも既定で --auto-finish を渡す (8 の 2 か所)" '[ "$(grep -c '"'"'${FINISH_ARGS\[@\]+"${FINISH_ARGS\[@\]}"}'"'"' "$HERE/lab_start.sh")" = 2 ] && grep -qF "FINISH_ARGS=(--auto-finish)" "$HERE/lab_start.sh" && grep -qF -- "--no-auto-finish) FINISH_ARGS=(); shift ;;" "$HERE/lab_start.sh"' -check "--help に --no-auto-finish の説明が入る" 'sed -n "2,64p" "$HERE/lab_start.sh" | grep -qF -- "--no-auto-finish 箱に置き終えたら" && ./lab_start.sh --help | grep -qF -- "--no-auto-finish"' +check "--help に --no-auto-finish の説明が入る" 'sed -n "2,66p" "$HERE/lab_start.sh" | grep -qF -- "--no-auto-finish 箱に置き終えたら" && ./lab_start.sh --help | grep -qF -- "--no-auto-finish"' + +echo "== 中継点の間隔 (依頼 182): --via-stride N をそのまま渡す. 既定は今までの 3" +setup; state $ID_OLD; pod 200 "$RUNNING"; probe 1 1 1 1 1; run +check "既定ではクライアントに --via-stride 3 を渡し, [VIA ] で step 2, 5, 8, 9 と出す" '[ $RC = 0 ] && [ "$(grep -c -- "pi05/run_policy_client.py .* --steps-per-chunk 10 --via-stride 3 " "$T/py.log")" = 1 ] && has out.txt "[VIA ] --via-stride 3: 1 チャンクを step 2, 5, 8, 9 で送る"' +rm -rf "$T" +setup; state $ID_OLD; pod 200 "$RUNNING"; probe 1 1 1 1 1; run --via-stride 05 --flow +check "--via-stride 5 なら --via-stride 5 を渡し (ほかの引数は同じ), [VIA ] で step 4, 9 と出す" '[ $RC = 0 ] && [ "$(grep -c -- "pi05/run_policy_client.py .* --steps-per-chunk 10 --via-stride 5 " "$T/py.log")" = 1 ] && ! grep -q -- "--via-stride 3" "$T/py.log" && grep -q -- "pi05/run_policy_client.py .* --no-step-confirm --flow$" "$T/py.log" && has out.txt "[VIA ] --via-stride 5: 1 チャンクを step 4, 9 で送る"' +rm -rf "$T" +setup; state $ID_OLD; pod 200 "$RUNNING"; probe 1 1 1 1 1; run --via-stride 0 +check "--via-stride 0 は中継点なしと出して 0 を渡す" '[ $RC = 0 ] && grep -q -- "pi05/run_policy_client.py .* --via-stride 0 " "$T/py.log" && has out.txt "[VIA ] --via-stride 0: 中継点なし"' +rm -rf "$T" +for pair in "1|1|0, 1, 2, 3, 4, 5, 6, 7, 8, 9" "10|10|9" "08|8|7, 9" "09|9|8, 9"; do # 境界と先頭の 0 (8 進で読まない. codex R1) + IFS='|' read -r arg want steps <<< "$pair" + setup; state $ID_OLD; pod 200 "$RUNNING"; probe 1 1 1 1 1; run --via-stride "$arg" + check "--via-stride $arg なら $want を渡し, step $steps と出す" '[ $RC = 0 ] && grep -q -- "pi05/run_policy_client.py .* --via-stride $want " "$T/py.log" && has out.txt "[VIA ] --via-stride $want: 1 チャンクを step $steps で送る"' + rm -rf "$T" +done +for bad in 11 -1 abc 100 ""; do + setup; run --via-stride "$bad" + check "--via-stride '$bad' は受けない (終了コード 2・API を呼ばない)" '[ $RC = 2 ] && [ ! -s "$T/calls.log" ] && has out.txt "--via-stride"' + rm -rf "$T" +done +setup; run --via-stride +check "--via-stride に値がなければ受けない (終了コード 2)" '[ $RC = 2 ] && [ ! -s "$T/calls.log" ] && has out.txt "--via-stride には 0〜10"' +rm -rf "$T" +check "実機の 8 にも同じ値を渡す (8 の 2 か所・3 を書き込んだ所は残らない)" '[ "$(grep -cF -- "--steps-per-chunk 10 --via-stride \"\$VIA_STRIDE\"" "$HERE/lab_start.sh")" = 2 ] && ! grep -q -- "--via-stride 3 " "$HERE/lab_start.sh" && grep -qF "VIA_STRIDE=3 " "$HERE/lab_start.sh"' +check "--help に --via-stride の説明が入る" 'sed -n "2,66p" "$HERE/lab_start.sh" | grep -qF -- "--via-stride N 1 チャンクの中継点の間隔" && ./lab_start.sh --help | grep -qF -- "# 5b. 空打ち"' echo echo "OK $OK / NG $NG"