Skip to content

feat: improve drive measurement fidelity for lifted-wheel tests - #172

Merged
asa-naki merged 26 commits into
mainfrom
feat/drive-measurement-fidelity
Oct 10, 2026
Merged

asa-naki merged 26 commits into
mainfrom
feat/drive-measurement-fidelity

Conversation

@Yuichiroh-Kobayashi

@Yuichiroh-Kobayashi Yuichiroh-Kobayashi commented Oct 3, 2026 •

Copy link
Copy Markdown
Collaborator

車輪を浮かせた動作テストの前準備です。足回りの測定忠実度を上げ(LAB の生値表示、制御 tick ごとの診断トピック、応答タイムアウトのパラメータ化)、velocity / current モードの制御実装を見直し、振動の切り分け用の解析スクリプトと共振モデルのテストを追加します。既定の走行挙動は変えていません(根拠は下記)。

実機の結果(2026-10-05)と、それを受けた修正(e67d59f)

  • 検証用ブランチ verify/main-172-176 @ 371c110(main 2deeb87 + この PR 8cf0bf0 + fix: start shot servos without blocking emergency stop handling #176)、機体 ID13、車輪を浮かせた試験。GPIO の非常停止経路を有効にして実施。独立レビュー(セカンドオピニオン)で、bag の統計・CSV・繰り返し性の表が再計算で一致することを確認済み。
  • 結果の要点(50 Hz、57600 baud、2 回の掃引 20〜150 rpm):
    • /drive_control_sample は 50.00 Hz、seq の欠落 0、response_timeout 0、fault_code 0。
    • feedback_new=false(8.8 %)はすべて指令 0 の周期(停止フレームの再送の間引き 300 ms。停止中の更新は中央値 約 320 ms)。回転中の欠落ではない。
    • 1 周期の所要時間:中央値 8.6 ms、p99 10.06 ms。往復時間の中央値は左 4.5 ms、右 4.0 ms。57600 baud のままでは 100 Hz の実施条件を満たさない。
    • 主な成分は回転の 20 次。80 rpm を超えると 20 次が 25 Hz(50 Hz 記録の上限)を超えて折り返し、23.5 / 16.9 / 10.3 / 0.3〜0.5 Hz に見える。
    • position_raw は 1 LSB 刻み。モータの電源を入れ直しても値は同じ(±1 LSB)。モータで回すと 1 回転で 1 周し、目視の回転数と合う。ただし手で約 90° 回したときの変化は約 9〜17° で、理由は未解明。
    • velocity_rpm_raw の平均は、position から求めた回転より約 0.45 rpm 小さく(モータの符号で)、さらに約 0.7〜0.9 % の比例のずれがある(HYP:切り捨ての量子化と速さの目盛りの差)。オドメトリへの影響はこの PR の範囲外として別途確認する。
  • e67d59f の修正(ロボットの動作は変わらない。msg はコメントだけで型と型ハッシュは同じ):
    • ripple_analysis.py:
      • 指定した次数の振幅を、回転角の位相への当てはめで直接求める(--track-orders、既定 20)。
      • 標本化の上限を超える成分に「折り返し」と観測周波数を示し、卓越ピークが追跡する次数(またはその折り返し先)と一致するときに印を付ける。
      • 「判別不能」の理由を「定速区間が足りない」と「判別できるピークが足りない」に分ける(実データで後者なのに前者と表示していた)。
      • 「非同期」を「1〜8 次を除いた残差」と呼ぶ(9 次以上の同期成分は残差に残る)。
      • エンコーダ誤差 1 % が校正していない仮定であることを明記。
      • テストを 4 件追加(20 次の直接計算、折り返しの印、折り返し周波数、判別不能の理由)。CI で pytest scripts/identify 19 passed。
    • position_raw の説明(msg・questix_msgs/README.md・scripts/identify/README.md):「8 LSB 刻み」を削除し、上の観察結果と「校正済みの絶対角としては扱わない」を記載。識別の README の手順に「非常停止では DDT の電源が切れない(ID13)」「値は解除後に新しいフレームだけを読む」を追加。電源の記述は誤りで、732a846 で訂正した(下記 2026-10-06 の結果)。
    • command_sent:停止フレームの再送を省いた周期でも true になる(実際に送った証拠ではない。送受信は feedback_new で見る)ことを msg と README に明記。
    • 停止時の INFO「シリアル往復レイテンシ統計」は lifecycle の deactivate のときだけ出る(サービスの停止では出ない)ことを README と YAML のコメントに記載し、bag の roundtrip_ms / response_timeout を使うよう変更。

追加の実機試験に向けた記録スクリプトの拡張(51dbf6d、a7b54e7)

2026-10-06 の追加試験(エンコーダの手回し、停止フレームの再送の間引きの比較、微速・信地旋回)に使うため、scripts/identify/record.sh と step_sequence.py を拡張しました。ノードのコードと設定値は変えていません。

  • 51dbf6d(機能の追加):
    • --schedule 3:60,5:60,...:レベルごとに保持時間を指定できる。
    • --lead-in-rpm / --lead-in-sec:止まった状態から動き出せないレベルの前に、助走を入れる。助走の後は 0 を通らずにそのレベルへ下げる。
    • --pattern straight|spin|pivot-left|pivot-right:信地旋回では、片輪を止めて反対の輪だけを回す。
    • --set-param NAME=VALUE:記録の間だけパラメータを変え、終了時に元へ戻す。変えられるのは min_command_rpm と stop_resend_interval_ms だけで、値は整数。
    • 停止判定の事前検査:動き出しに max(1, min_command_rpm) + 2 rpm 以上が要るので、回らないレベルがあれば質問の前に開始を断る。--set-param の値も検査に含める。
    • wheel_radius と wheel_separation はノードから読む。meta.yaml にパターン・スケジュール・助走・車輪の寸法・変えたパラメータと戻した値を残す。
  • a7b54e7(独立レビューの指摘を受けた修正):
    • step_sequence.py が /emergency_stop を購読する(reliable + transient_local、depth 1)。
      • 解除(active=false)を受信していなければ開始しない。
      • 実行中に押下されるか受信が途絶えたら(--estop-timeout)、0 を送って中断する。終了コードは 4、meta.yaml は step_sequence: "aborted_emergency_stop"。
      • 押下は一度受けたら取り消さない(解除しても再開しない)。
      • 修正前は押下中も指令を送り続けていたため、解除した瞬間に残りのステップで回り出す(コードを読んでの判断。実機では起きていない)。
    • record.sh の --set-param:
      • 設定の後に値を読み返し、違えば失敗にする。
      • 元の値に戻せなかったときは、終了コード 5 で終わり、meta.yaml に param_restore: failed を残す。修正前は警告だけで、成功扱いで終わっていた。
    • 単体の診断用に --no-estop-watch を追加した(既定は監視あり)。
  • 利用者への影響:record.sh / step_sequence.py は、非常停止を解除してから実行する必要がある(README に記載)。監視には questix_msgs が要るため、questix のワークスペースを source しておく(無ければ開始時にその旨を表示して終わる)。

実機の結果(2026-10-06)と、それを受けた修正(732a846、dc9c423、5cb6fe1)

  • 対象:検証用ブランチ verify/main-172-176-20261006 @ e2e9680(main 2deeb87 + この PR a7b54e7 + fix: start shot servos without blocking emergency stop handling #176)、機体 ID13。
    • 試験の状態:車輪を浮かせた試験と、カーペットの上の信地旋回。
    • 独立レビューを 2 本行った(別セッションの Claude と Codex)。どちらも、CSV が bag と全行一致すること、解析表が再現することを確認した。
  • 結果の要点:
    • 非常停止での中断(a7b54e7):実行中の押下から +3 ms で 0 の指令を送り、終了コード 4、meta.yaml は aborted_emergency_stop。解除しても回り出さないことは作業者の観察のみ(bag は解除の前に終わっていた)。
    • --set-param:10 回すべてで、設定・読み返し・復元が一致した。終了コード 5 は 0 回。
    • 微速(--schedule / --lead-in-rpm、浮かせた状態):1〜10 rpm の正転・逆転で、位置から求めた平均速度は指令の 0.975〜1.081 倍。0.2 s の窓での止まりかけ・逆回りは 0。
    • 起動の拒否:13 回中 3 回(押下中に始めた 1 回を除く)、step_sequence.py が 2 s の待ちで /emergency_stop を 1 件も受け取れず、開始しなかった。
      • 安全側の動作だが、押下と同じ終了コード 4 で記録されていた。
      • 同じ夜の記録側(ros2 bag record)でも、/emergency_stop の最初の受信が最大 3.0 s 遅れた。
    • position_raw の手回し:非常停止の押下中に手で 30° ずつ回した(押下で DDT の駆動電源が無電圧になることは、作業者が確認)。
      • 値は回した角度に追従しないが、同じ角度では同じ値に戻る。「36° で一周する値」で当てはまる(HYP)。
      • 解除の後、DDT は 1.28〜1.61 s 応答しない。
    • 周期の超過:解除の直後の待機の tick が 21〜24 ms かかった(68 tick)。Control tick overrun の WARN は出ていない(待機の経路では判定していない)。
    • 停止フレームの再送の間引き(300 / 100 / 0 ms、浮かせた状態):位置から求めた量(前進の最大、戻り、停止までの角度)は、どの条件でも重なった。止まった時刻は受信の間隔と静止の閾値に依存し、条件の差は確定できない。浮かせた状態では、停止はほとんど最初の再送の前に終わる。
    • 信地旋回の角度:計算では約 560°、目視では約 270°。原因は wheel_radius が実寸の 2 倍だったこと → fix: wheel_radius が実寸の 2 倍(0.1 m、実寸 0.05 m)。物理単位を正し、走り方は変えない #179。
    • 負荷時のぎくしゃく:カーペットの信地旋回 5 rpm で、速度の主な成分は約 1.65 Hz(回転の 20 次と同じ周波数)、片振幅 3.6〜5.9 rpm で、浮かせた無負荷(0.2〜0.35 rpm)の約 15〜20 倍だった。
      • 2026-10-07 の追加検証(機体 ID15・ID14、同じ SD、カーペット):直進 10 rpm で 1.58〜1.72 Hz・片振幅 6〜11 rpm、信地旋回 10 rpm で 1.47〜1.66 Hz。2 台とも再現した。
      • 回転数を 5 → 10 rpm に倍にしても周波数は倍にならないので、10/6 の「20 次」は、約 1.5〜1.7 Hz の付近の共振と 20 次が重なったものと考えられる(原因は未確定。独立レビュー〔Codex〕で数値を再現済み)。
      • 設計資料の「ファームの速度ループは約 1.8 Hz で振動する」という断定の修正は、この PR の範囲外として別の PR で行う。この PR の test_control_core の共振モデル(1.8 Hz、ζ 0.15)は、仮定値のまま。
  • 732a846 の修正(記録スクリプトと文書。ロボットの動作は変わらない。msg はコメントだけ):
    • step_sequence.py:
      • 最初の /emergency_stop を待つ時間を --listen-before とは別に設けた(--estop-wait、既定 10 s)。待つ間も、他の送り手の検出は続ける。
      • 開始の前に、最初の受信までの時間・受信件数・publisher の数・RMW_IMPLEMENTATION を表示する。
      • 押下を受信した拒否は終了コード 4。受信できなかった(または開始の前に途絶えた)拒否は終了コード 6 で、record.sh は meta.yaml に refused_estop_not_received と残す。
    • 文書(scripts/identify/README.md、questix_msgs/README.md、DriveControlWheelSample.msg のコメント):
      • 「DDT の電源は切れない」を「切れるかは未確認」に直した。10/5 と 10/6 の観察が食い違うことも書いた。→ 作業者の確認を受けて、5cb6fe1 で「ID13 では非常停止の押下で DDT の駆動電源が切れる」に更新した(解除のたびに DDT が起動し直し、最初の応答まで 1.3〜1.6 s)。
      • 信号ケーブル(RS485)や変換器の端子を抜いても、電源は切れない。
      • 電源断をまたいだ position_raw は、角度としても回転数としても使わない。
      • 10/6 の手回しの結果を足した。
  • 5cb6fe1:上の電源の記述を、作業者の確認(押下で駆動電源が無電圧。RS485 の信号線は生きている)に合わせて更新した(文書と msg のコメントだけ)。
  • dc9c423:motor_control_app/README.md と drive_component.yaml のコメントに、周期超過の WARN は駆動指令を送る tick だけで判定し、待機中の超過は bag の tick_duration_sec で見ることを書いた(コードの変更なし)。

外部レビューを受けた修正(cbc20a3)

  • 同定のステップが drive_component の max_motor_rpm(と仕様上限 475 rpm、kSpecVelocityMaxRpm)を超えると、指令が切り詰められ、同定の入力が指定と変わる。これまでは黙って切り詰められていた。
    • record.sh が max_motor_rpm を読み、step_sequence.py --max-command-rpm で、超えるステップ(助走を含む)があれば記録の前に拒否する。単体の step_sequence.py も --max-command-rpm を渡せば同じ判定をする。
    • 既定のレベルを 300 rpm までにした(record.sh 20〜300、step_sequence.py 50〜300)。M6 規格書 V1.0 の速度 ±330 rpm(下の「データシートとの食い違い」)に収めるため。搭載モータの仕様が確定するまでの暫定。
    • テスト:test_step_sequence.py に 1 件、test_evidence.sh に 3 件(max_motor_rpm=330 で 400 rpm を拒否し、出力ディレクトリを作らない)。
  • データシート(115200 baud / ±330 rpm / Kt など)と、リポジトリ(57600 baud / 475 rpm)の食い違いは未解決のまま。ID13 では 57600 baud・50 Hz の通信が成立している。どちらかに合わせる変更はしていない。

base と履歴(#170 → #169 → この PR)

レビュー後の修正(db70920〜c43b69a)

# 指摘 修正
S1 LAB の注記・コメントが「20 Hz」。ブリッジの間引き(新しい方を、前回から 1/max_hz 以上空いたときだけ送る)は 50 Hz の /drive_status を 3 回に 1 回(0.06 s、約 17 Hz)にする。ブリッジの記録も同じ 画面の注記、WHEEL_GAP_SECONDS / recording-core のコメント、lab_bridge.yaml のコメント、ブリッジ README を実際の値に修正(6a3d3c7、挙動の変更なし)
S2 輪ごとの LQR 適用判定が run_exit だけで、抜け閾値付近の目標で適用/解除と状態の破棄が tick ごとに入れ替わり、指令が最大 ±max_correction_rpm 跳ねる 輪ごとにも入り/抜けのヒステリシス(run_enter で入り、run_exit 未満で抜ける。向きが変わったら入り直す)。29/31 rpm の往復で入れ替わらないテストを追加(修正前のコードでは失敗することを確認)(db70920、LQR は既定 OFF)
S3 scripts/identify のテスト(fit_models / step_sequence / ripple_analysis)が CI のどこでも走っていなかった Robot Manager Checks の python-tests に NumPy(ロボットの apt 版 1.26.4 に固定)と pytest scripts/identify を追加(c43b69a)
S4 届いたが有効でないフレーム(CRC 不一致・別 ID)を、診断トピックは「有効な応答なし」、停止時のレイテンシ統計は「往復あり」と数えていた どちらも「有効な応答なし」(往復時間は記録せず、タイムアウトを 1 つ数える)に統一。CRC を壊して返す偽モータのテストを追加(b216708)
S5 feedback_count は tick の外の送受信(非常停止コールバックの即時停止、configure/activate)でも増える メッセージ定義・契約(questix_msgs/README.md)・ripple_analysis に明記。ripple_analysis の表示は「載らなかったフレーム」(サンプルの欠落とは別)(220dbe8)

変更点

1. LAB に平滑化前の生値を描く(d49cd24、注記は S1 で修正)

  • 「実機の状態」の左右車輪グラフを 3 本にした: 生値(太い実線、主)、平滑化後(細く薄い線。太さ・不透明度・直接ラベルで区別し、色だけに頼らない)、送った指令(破線。/drive_status の target_rpm)。
  • 車輪カードの大きな数字を生値にし、平滑化後を小さく併記。旧ブリッジ(生値なし)では従来どおり。
  • ストリップのスパークラインは、生値の左右 rpm と wheel_radius から求めた車体速度。生値が無ければ従来の speed に戻す。
  • 欠測区間を線でつながない: 系列に非有限値を残し、0.5 s(WHEEL_GAP_SECONDS)を超える間隔の前に切れ目を入れる。html-chart.js は非有限点で線を分け、ラベルは最後の有限点に置く。欠測の無い既存の図(motor / slam)の見た目は変わらない(テストで確認)。
  • 「表示は間引いた値(約 17 Hz)、解析は rosbag」の注記。右輪の符号反転は従来どおり画面のみ。ストリップの数字とメモの行は平滑化後のまま(既存テストの契約)。

2. 診断トピック /drive_control_sample(3380781)

  • 新メッセージ questix_msgs/DriveControlSample と DriveControlWheelSample。MotorFeedback / DriveStatus は変更していない(型ハッシュと既存 bag の互換のため)。
  • drive_component が制御 tick ごとに 1 回 publish(control_rate、reliable + volatile + keep_last(100)、ACTIVE 時のみ、既定で有効。publish_control_sample / control_sample_topic、実行時変更不可)。tick 本体を runControlTick() に移し、途中で return する tick も含めて毎 tick ちょうど 1 回出す。tick_action(IDLE / DRIVE / TIMEOUT_STOP / FAULT_STOP)付き。
  • DdtMotorLib がモータごとの送受信の記録(受信フレーム数・送受信回数・有効な応答なしの数・直近の往復時間・最後に送った電流 raw)を state_mutex_ の下で持つ。
  • 契約は questix_msgs/README.md(診断専用、時刻の意味、seq による欠落の検出、feedback_new / feedback_count による重複の検出、既存トピックを変えない理由)。既存の誤記も修正: /drive_status の既定は 10 Hz → 50 Hz。position_raw の説明は e67d59f で実測に合わせて修正。CHANGELOG、record.sh の OPTIONAL_TOPICS、AGENTS.md。

3. 応答タイムアウトのパラメータ化(ad43268)

  • readFeedbackFrame(..., 10) の固定値を DdtMotorLib の設定値にした(既定 10、[2, 50] ms にクランプ)。drive_component に serial_response_timeout_ms(既定 10。範囲外はクランプ + WARN、実行時変更不可)。single_ddt_motor / joy_axis_drive はライブラリ既定のまま。

4. velocity / current 実装のレビュー(6559eaf 修正、43b4ae1 警告と文書、db70920 S2)

論点 判断 既定挙動への影響
L1 目標 0 の輪に ±max_correction_rpm が出得る 修正: 目標 0 の輪は補正せず 0、その輪の状態を捨てる なし(LQR は既定 OFF)
L2 輪ごとの適用範囲の判定がない 修正: 輪ごとに入り/抜けのヒステリシス付きで判定、範囲外は FF のみ。符号反転でも状態を捨てる なし(同上)
V-ticks tick 単位のパラメータ LQR 有効かつ control_rate ≠ 50 のとき起動時 WARN。YAML を周期によらない表現に なし
V3 停止の最後はファーム任せ 文書化のみ なし
V4 高頻度の送信でファームが減速を完了できない 文書化し、送信頻度を変える試験の手順を追加 なし
V5 LQR は振動対策ではない 共振モデルのテストで固定: 状態 FB では揺れが減らず(≈10 → 12 rpm)、外乱補償は共振を補正上限まで励起する(≈10 → 84 rpm、仮定値のモデル) なし
C1 current の既定ゲイン(純 P) current モード起動時 WARN、README に評価前提 なし
C2 current のランプ二重 実害なし(ライブラリ側のランプは既定 0 で設定箇所なし) なし
C3 current_invert_measured: true が未確認 起動時 WARN、符号の確認手順 なし
C4/C5 浮かせた状態での安定性 README に「浮かせた状態は低ゲインから」 なし
C6 指令途絶時のドライバの挙動が不明 起動時 WARN、安全注意 なし
周期・観測(実測 dt と固定 dt、overrun、/drive_status と tick、アイドル時の再取得、非常停止の待ち) 文書化(README「周期と時間の扱い」) なし
データシートとの食い違い(115200 baud / ±330 rpm) コードは変更せず README に記録。同定の既定のレベルは 300 rpm まで、max_motor_rpm を超えるステップは拒否(cbc20a3) なし

5. 解析スクリプトと共振モデル(e868a76、e67d59f)

  • scripts/identify/ripple_analysis.py: rosbag2(/drive_control_sample 優先、旧 bag は /drive_status)または CSV。重複・欠落の処理、position_raw のつなぎ直し、定速区間の抽出、回転角での同期平均、次数スペクトル、時間スペクトル、キャンベル線図相当、左右の和・差、比例か一定かの判定、エンコーダ誤差未満を「判別不能」、追跡する次数(既定 20)の直接計算と折り返しの表示。
  • test_control_core に 2 次系のファームループモデル(仮定値: 1.8 Hz、ζ 0.15)。
  • README / handout に浮かせた試験の手順。

6. 記録スクリプト(51dbf6d、a7b54e7、732a846、cbc20a3)

  • 上の「追加の実機試験に向けた記録スクリプトの拡張」「実機の結果(2026-10-06)」「外部レビューを受けた修正」を参照。

既定の走行挙動を変えていない根拠

  • L1/L2(S2 を含む)は velocity_run_lqr_enabled: false(既定)の機能の中だけ。既定設定で出力が従来と一致することは既存の DefaultConfigIsIdenticalWithAndWithoutFeedback / DefaultConfigMatchesPreFsmPipeline が引き続き確認する。
  • serial_response_timeout_ms の既定 10 は従来の固定値。
  • S4 は統計の数え方だけで、送受信と制御の流れは同じ。
  • 診断サンプルは publish を足すだけ。警告は起動時のログだけ。e67d59f はコメント・文書・オフラインの解析スクリプトだけ。
  • 51dbf6d / a7b54e7 は、作業者が手で実行する記録スクリプトだけ。--set-param は実行時変更可能な 2 つのパラメータを記録の間だけ変え、終了時に戻す(戻せなければ終了コード 5)。ノードのコード・YAML・launch は変えていない。
  • 732a846 / dc9c423 / 5cb6fe1 / cbc20a3 は、記録スクリプト・文書・コメントだけ。

検証

最初の版(16f6cb1、作成者の PC: WSL2 Ubuntu 24.04 / ROS 2 Jazzy)

  • colcon build / colcon test → 1081 tests, 0 failures。pytest scripts/identify → 15 passed。LAB 583 tests / 0 fail、Prettier OK。旧 base での CI は全 16 チェック success。

スタックとレビュー修正(c43b69a)

  • ローカル(ROS の無いクラウド環境): git diff --check クリーン。clang-format 18.1.3 --dry-run --Werror を全 C++ ファイルに → 差分なし。test_control_core.cpp を最小の gtest 互換シムでコンパイル・実行 → 42 tests, 0 failed。LAB node --test → 578 pass / 0 fail、Prettier OK。flake8 --select=F(scripts/identify)0 件。
  • CI: 全チェック success(PR の run 37193447124 ほか、workflow_dispatch の run 37193308833)。

#169 追従(6809491)、main 追従(8cf0bf0)

  • 6809491 ローカル: PYTHONPATH=scripts pytest -q scripts/robot_manager → 470 passed, 7 skipped。CI: 全 19 check run が success。

実機の結果を受けた修正(e67d59f)

  • ローカル: py_compile、flake8 --select=F(scripts/identify)0 件、git diff --check クリーン(手元に NumPy が無いため pytest は CI で実行)。
  • CI(run 37325415861 ほか): 全 18 チェック success。pytest -q scripts/identify → 19 passed。

記録スクリプトの拡張(51dbf6d)、非常停止での中断とパラメータ復元の失敗(a7b54e7)

  • ローカル(ROS の無いクラウド環境):
    • bash scripts/identify/test_evidence.sh(ros2 をスタブに差し替え)→ 82 passed, 0 failed。開始を断る停止判定の検査、--set-param の許可リスト、復元(成功・中断)、読み返しの不一致、復元の失敗による終了コード 5、非常停止による終了コード 4 を含む。このテストは CI では実行していない。
    • test_step_sequence.py(スケジュール、助走、開始の検査、信地旋回・超信地旋回、EstopGuard)→ 13 passed。
    • flake8 --select=F(scripts/identify)0 件。git diff --check はクリーン。
  • CI(a7b54e7。run 37386310682 / 37386310485 ほか):全 17 チェック success。pytest -q scripts/identify → 27 passed。

最初の E-stop の待ちと終了コード 6(732a846)、周期超過の文書(dc9c423)

  • ローカル(ROS の無いクラウド環境):test_evidence.sh → 84 passed, 0 failed(終了コード 6 → refused_estop_not_received を追加)。test_step_sequence.py → 14 passed(押下と受信なしの区別を追加)。CI と同じ設定の flake8 / pydocstyle、git diff --check はクリーン。
  • CI(dc9c423):全 18 チェック success。

上限の検査(cbc20a3)

  • ローカル(ROS の無いクラウド環境):pytest scripts/identify 29 passed、test_evidence.sh 87 passed、CI と同じ設定の flake8(--select=F)・pydocstyle 0 件、bash -n OK、git diff --check クリーン。
  • CI(cbc20a3):全チェック success。

実機(Raspberry Pi 5)で確認すること

  • fix: require GPIO5 E-stop ROS monitoring in all production launches #169 の Gate A–E(6aafa7d で確認、fix: require GPIO5 E-stop ROS monitoring in all production launches #169 は main にマージ済み)
  • ros2 topic hz /drive_control_sample が control_rate(50 Hz)で出る(2026-10-05、50.00 Hz。INACTIVE で止まることは未確認)
  • bag で seq の飛び、feedback_new の割合、roundtrip_ms の分布(2026-10-05、浮かせた試験。床上の走行中の bag は 2026-10-07 に ID15・ID14 で取得し、seq の欠落 0)
  • CPU 負荷(2026-10-05、drive_component と ros2 bag record が各約 4.6 %。新トピック追加前との比較は未実施)
  • serial_response_timeout_ms が既定 10 で従来と同じ挙動(response_timeout 0 件、周期の超過は 26,717 周期中 1 回。停止時のレイテンシ統計は deactivate のときだけ出ると判明し、文書を修正)
  • 起動ログに publish_control_sample: true と serial_response_timeout_ms: 10。velocity モードの既定では新しい WARN が出ない
  • LAB「実機の状態」: 生値が太線、平滑化が細い線、送った指令が破線、注記「約 17 回」(カードの数字が生値かは作業者の「恐らく」。通信が途切れたときに線が切れるかは未確認)
  • 浮かせた試験の手順(scripts/identify/README.md「振動の切り分け」)を通しで実行できる(GPIO の非常停止経路を有効にして実施)
  • position_raw の電源の入れ直し(同じ値に戻る)を確かめ、questix_msgs/README.md に反映(e67d59f)
  • position_raw の手回しの追加検証(2026-10-06、型紙で 30° ずつ 17 点):手で回した角度には追従せず、同じ角度では同じ値に戻る。電源断をまたいだ値は角度として使えない、と文書に反映(732a846)。モータで回したときの四分点の通過は未実施
  • e67d59f の解析スクリプトで、2026-10-05 の bag を解析し直す(2026-10-06:20次 … rpm 64 件、折り返しの表示 32 件、「判別できるピークが N 個」の理由。run1・run2 とも)
  • step_sequence.py が実行中の非常停止の押下で中断する(2026-10-06:終了コード 4、aborted_emergency_stop、押下の +3 ms で 0 の指令)。解除しても回り出さないことは作業者の観察のみ(解除をまたいで記録した bag は無い)
  • --set-param の設定・読み返し・復元の表示と、記録の前後の ros2 param get の値が一致する(2026-10-06:10 回、終了コード 5 なし。2026-10-07:30 回、すべて開始時の値に戻った)
  • --schedule / --lead-in-rpm で、1・2 rpm のレベルを助走のあと 0 を通らずに保持できる(2026-10-06:2 回、平均速度は指令の 0.975〜1.081 倍)
  • 732a846:/emergency_stop の最初の受信までの時間が表示され、2 s を超えても拒否されずに始まる(拒否されたときは終了コード 6 と refused_estop_not_received)。2026-10-07 の試験は e2e9680(この修正の前)で行ったため未確認(起動拒否は ID14 で 2 回)
  • 解除をまたいで切れ目なく記録し、解除しても残りのステップが再開しないことを bag で確かめる
  • 非常停止の押下で DDT の駆動電源が切れる(ID13、作業者が無電圧を確認)。文書に反映(5cb6fe1)。2026-10-07 に ID15・ID14 でも無電圧を確認
  • cbc20a3:record.sh が max_motor_rpm を表示し、上限以下のレベルでは従来どおり記録できる

関連

🤖 Generated with Claude Code

https://claude.ai/code/session_01UtCWWiR4ExGRx34k3Y78Xp

Yuichiroh-Kobayashi and others added 15 commits October 4, 2026 00:09
The 実機の状態 wheel charts now draw the raw feedback (rpm_raw) as the main
solid line, the node's low-pass filtered value as a thin faint helper and
the sent command (DriveStatus target_rpm) dashed. The wheel card's big
number is the raw value with the filtered one beside it (older bridges
unchanged). The strip's sparkline is the chassis speed computed from the raw
wheels and wheel_radius, falling back to the node speed.

Missing samples are no longer joined: wheel series keep non-finite values
and get a break after a pause longer than WHEEL_GAP_SECONDS (0.5 s), and
html-chart splits a line at non-finite points (one polyline per run, the
label on the last finite point). Gap-free data draws exactly as before.

The panel notes that it shows /drive_status thinned to 20 Hz and that the
rosbag is for analysis.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New questix_msgs/DriveControlSample + DriveControlWheelSample, published by
drive_component once per control tick on /drive_control_sample (rate =
control_rate, reliable + volatile + keep_last(100), ACTIVE only, on by
default; publish_control_sample / control_sample_topic, not runtime
changeable). Each sample carries a tick seq (loss detection), the tick's
action, shaped chassis command, per-wheel pre-correction target, sent
command (and the current raw in current mode), whether this tick received
a new feedback frame plus a cumulative frame count (duplicate detection),
the round trip, timeout flag, receipt stamp and the raw wire fields.

DdtMotorLib keeps a per-motor transaction record (frames parsed,
exchanges, timeouts, last round trip, last current raw sent) behind
state_mutex_. The tick body moved into runControlTick() so that every tick,
early returns included, publishes exactly one sample. /drive_status,
MotorFeedback and DriveStatus are unchanged (type hash, recorded bags).

Docs: the topic contract in questix_msgs/README.md (diagnostic only), the
/drive_status default rate corrected to 50 Hz, the 4096-step encoder behind
position_raw, CHANGELOG, the parameter tables, AGENTS.md; record.sh records
the topic when present. Tests: the pure message conversion and the library
counters over a pseudo terminal that answers with real frames.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The feedback wait after each command was a fixed 10 ms in
sendFrameWithFeedback. DdtMotorLib now keeps it as a setting (default 10,
setResponseTimeoutMs clamps to [2, 50] ms and logs the applied value) and
drive_component exposes serial_response_timeout_ms (default 10, clamped
with a WARN when out of range, not runtime changeable). The default keeps
the behaviour unchanged; single_ddt_motor and joy_axis_drive keep the
library default.

The YAML comment gives the lower bound (response frame 1.74 ms at 57600
baud + measured firmware time + margin) and where to read the measured
round trip (the latency INFO on deactivate). Tests: the clamp, the library
default, the parameter default and its rejection at runtime.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ControlCore decides RUN from the larger |wheel target| and used to apply
the velocity RUN LQR+FF to both wheels. The sign guard in runWheel() only
covered ref > 0 / ref < 0, so a wheel whose target was 0 (turning about it)
could be sent up to +/-max_correction_rpm, and a slow inner wheel below the
RUN range was corrected with a model that does not hold there.

Now each wheel is corrected only when its own target is nonzero and
|target| >= the normalized run_exit threshold (wheelInRunRange); any other
wheel gets the feedforward (its target as is) and its observer/LQR state is
dropped. A wheel whose target changes sign drops its state too, so a
forward observer and disturbance estimate never carry into reverse.
Output gains left_lqr_active / right_lqr_active; lqr_active is their OR.

The feature is off by default (velocity_run_lqr_enabled: false), so the
default driving is unchanged. Tests: a zero-target wheel, a wheel below
run_exit, two wheels at different speeds, a reversal, lost feedback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… assumptions

No default behaviour changes. drive_component now warns at configure when
the velocity RUN LQR is effective with control_rate != 50 Hz (its
tick-based parameters assume 50 Hz), and in current mode about the pure-P
default gains, the unchecked sign of current_invert_measured and the
undocumented driver behaviour when commands stop. The LQR warning is also
raised when the LQR or the RUN thresholds change at runtime.

YAML comments no longer assume a 50 Hz tick, state that the LQR is not an
oscillation fix (first-order model), and describe how the last part of a
stop is left to the firmware. The motor_control_app README records the
review (fixed / documented / why not), the timing of a tick (fixed vs
measured dt, overrun, round trips, idle refresh rate, E-stop latency,
/drive_status vs the tick), the current-mode evaluation premises with a
sign-check procedure and safety notes, and the unconfirmed differences from
the M6 datasheet (115200 baud, +/-330 rpm), which the code keeps as is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scripts/identify/ripple_analysis.py separates the wheel-speed ripple into a
rotation-synchronous part and a fixed-frequency part. It reads a rosbag2
(/drive_control_sample first: seq for losses, feedback_new / feedback_count
for duplicates; /drive_status of older bags deduplicated by each wheel's
receipt stamp) or a CSV (its own --export-csv, or a QUESTiX LAB
-messages.csv). It unwraps position_raw against the speed, extracts the
constant-command segments, fits a synchronous average in rotation angle
(orders), takes the time spectrum of the raw speed and of the residual,
tabulates rpm vs peak (Campbell), analyses the left/right sum and
difference, and classifies peaks as proportional to rotation or constant;
components below the encoder error (1 % x order by default) are reported as
not resolvable. rosbag2_py and matplotlib are imported only when used.
Tests use synthetic data (order-1 ripple + 1.8 Hz sine, integer rpm,
duplicate and lost samples) for both CSV paths.

test_control_core gains a second-order firmware loop model (assumed
1.8 Hz, damping 0.15, dead time, integer rpm) with a once-per-revolution
disturbance: the swing peaks at 95-110 rpm, the state feedback of the
first-order LQR does not damp it, and its disturbance compensation excites
it up to the correction limit (about 10 -> 84 rpm in this model). The YAML
and README now warn against enabling the disturbance compensation before
the resonance is identified.

The identify README and handout describe the lifted-wheel test (record.sh
--levels 20,...,150 --hold 30), counting detents by hand, comparing
position_raw across a power cycle, the command-rate test and floor runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Whitespace and line wrapping only, from .clang-format (checked with
clang-format 14, which reports no difference on every other C++ file of
the repository).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After #170 a practice launch with ENABLE_GPIO_REF=false still moves:
operation_manager runs with operation_manager.no_gpio.yaml and publishes
/emergency_stop released, but GPIO5 is not read, so a press of the physical
E-stop never reaches ROS (no software stop edge, no diagnostics; only RLY1's
power cut remains). The production practice launcher followed launch.env,
and Robot Manager's 管理設定 could persist ENABLE_GPIO_REF=false (issue #168).

Production policy only; #170's architecture (questix_safety,
EmergencyStopMonitor, always-on operation_manager, the no_gpio profile,
fail-close, teacher permission) is unchanged.

- questix_robot_launcher.sh (systemd and Ansible copy): practice passes
  enable_gpio_ref:=true / enable_autoreferee:=false like competition passes
  true / true; ENABLE_GPIO_REF is never read, so a legacy false is ignored.
- PUT /api/launch-config refuses ENABLE_GPIO_REF other than "true" (422),
  migrates a legacy false to true on save, and logs each changed key as
  "launch-config updated: KEY old -> new".
- 管理設定 no longer has the switch; it shows GPIO5 as always on.
- enable_gpio_ref:=false stays a questix_core capability for explicit manual
  diagnostic launches; docs, YAML/launch comments and test comments say so.
- Tests: practice launcher contract inverted (false / true / empty / an
  unexpected value all launch with enable_gpio_ref:=true), launcher static
  test pins the fixed practice args, new launch-config API/UI tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GjS1yNf6dAUWTwi1RTpWeP
…ent default

questix_core took enable_gpio_ref's default from $(env ENABLE_GPIO_REF true),
so an exported ENABLE_GPIO_REF=false, or a shell that sourced a legacy
launch.env, put a plain `ros2 launch questix_core.launch.xml` into the
no-GPIO mode without anyone asking for it on the command line (issue #168).

- questix_core.launch.xml: enable_gpio_ref defaults to a literal true. Only
  an explicit enable_gpio_ref:=false on that launch selects no-GPIO; the
  override lives in that process only and nothing persists it.
- Tests: static test pins the literal default and that it never reads
  ENABLE_GPIO_REF / $(env ...); runtime test exports ENABLE_GPIO_REF=false,
  leaves the argument out and expects gpio_reader, gpio_safety_enabled=true
  and the GPIO E-stop (not "released (no GPIO safety path)"). The explicit
  false runtime cases from #170 stay as they are.
- Docs, launch.env comments and AGENTS.md: ENABLE_GPIO_REF is a legacy
  field nothing reads; no-GPIO needs the launch argument and does not
  survive process exit or a power cycle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GjS1yNf6dAUWTwi1RTpWeP
Stack this branch on #169 (which is based on #170 in main). Conflicts with
#170's E-stop / teacher-permission refactor are resolved as:
- drive_component.cpp: kRequiresReconfigure takes #170's teacher_permission_*
  names and keeps serial_response_timeout_ms, publish_control_sample and
  control_sample_topic.
- drive_component.hpp: drop emergency_stop_topic_ (now owned by
  EmergencyStopMonitor), keep the control-sample members.
- motor_control_app/README.md: keep both the review section and the
  build-time section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtCWWiR4ExGRx34k3Y78Xp
The per-wheel range check used run_exit only, so a wheel target hovering
around run_exit switched the correction on and off every tick, dropping
the wheel's observer/LQR state each time and letting the command jump by
up to max_correction_rpm. A wheel now enters at run_enter and stays while
|target| >= run_exit (the same hysteresis as the chassis CREEP<->RUN);
a sign change re-enters. LQR stays off by default, so the default drive is
unchanged.

Test: a wheel crossing run_exit back and forth (29/31 rpm) stays out until
it reaches run_enter again; fails on the previous rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtCWWiR4ExGRx34k3Y78Xp
The bridge's latest-wins limiter sends a message only when at least
1/drive_max_hz has passed, so drive_component's 50 Hz /drive_status at
drive_max_hz 20 reaches the pages every third message (0.06 s, about
17 Hz), not 20 Hz. The same limiter thins the bridge's records. Correct the
panel's note, the WHEEL_GAP_SECONDS and recording-core comments, the
lab_bridge.yaml comment and the bridge README. No behaviour change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtCWWiR4ExGRx34k3Y78Xp
…istics

A 10-byte frame that arrived but was not valid feedback (CRC mismatch,
another ID) counted as "no valid response" in the per-motor record behind
/drive_control_sample, but as a measured round trip (and not a timeout) in
the latency statistics logged at deactivate. Both now count it as no valid
response: no round trip recorded, one timeout. Test with a fake motor that
answers with a corrupted CRC.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtCWWiR4ExGRx34k3Y78Xp
The E-stop callback's immediate stop and configure/activate exchange frames
outside the tick, so feedback_count can step by 2 or more between two
samples with a contiguous seq. Say so in the message, the topic contract and
ripple_analysis (whose "frames_missing" is reported as frames not carried
by any sample, separate from lost samples).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtCWWiR4ExGRx34k3Y78Xp
pytest scripts/identify (fit_models, step_sequence, ripple_analysis) ran
nowhere in CI: robot_manager's job only collects scripts/robot_manager and
the identification tests need NumPy. Install NumPy pinned to the robot's apt
version (1.26.4) and run them in the existing python-tests job. They use
synthetic data only; rosbag2_py and matplotlib are imported only when used.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtCWWiR4ExGRx34k3Y78Xp
@Yuichiroh-Kobayashi
Yuichiroh-Kobayashi changed the base branch from main to claude/eloquent-carson-jlyi52 October 4, 2026 09:47
@Yuichiroh-Kobayashi
Yuichiroh-Kobayashi added this pull request to stack #174 October 4, 2026 09:50
claude added 2 commits October 4, 2026 12:29
The 管理設定 card showed 「安全入力(GPIO5、非常停止ボタン)」 as a read-only item. It is not a
setting, has no state of its own and looked like a disabled switch; the header's 非常停止ボタン
already shows the live E-stop state. The test now asserts neither a switch nor the item exists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtCWWiR4ExGRx34k3Y78Xp
…ent-fidelity

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtCWWiR4ExGRx34k3Y78Xp
Base automatically changed from claude/eloquent-carson-jlyi52 to main October 5, 2026 00:22
#169 was squash-merged as 2deeb87; its tree equals the stacked base, so this merge only moves
the merge base for the PR diff.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtCWWiR4ExGRx34k3Y78Xp
claude added 2 commits October 5, 2026 14:31
…_raw docs

Hardware results from 2026-10-05 (ID13, lifted, 50 Hz) and an independent
review showed several gaps in the measurement tooling and its contract:

- ripple_analysis.py: track fixed orders (--track-orders, default 20) by
  fitting the rotation phase directly instead of picking the largest peak;
  mark components above the Nyquist frequency as aliased (with the observed
  frequency) and tag dominant/residual peaks that match a tracked order;
  say whether "insufficient" means too few segments or too few resolvable
  peaks; call the residual "orders 1..N removed" instead of non-synchronous;
  state that the 1 % encoder error is an uncalibrated assumption.
- position_raw: the wire value moves in 1 LSB steps (all residues mod 8
  occur), not 8. Record the power-cycle result and the unexplained
  hand-turn behaviour; do not treat it as a calibrated absolute angle.
- command_sent: true also when DdtMotorLib skips re-sending a stop frame
  within stop_resend_interval_ms; it is not proof that a frame went out.
- The latency INFO appears only on lifecycle deactivate; point to the
  bag's roundtrip_ms / response_timeout instead.
- identify README: the E-stop does not cut DDT power on ID13; read
  position only after release from new frames (~0.32 s while stopped).

Message comments only; field types and type hashes are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtCWWiR4ExGRx34k3Y78Xp
…arams in record.sh

For the next lifted/low-speed tests on the Pi:

- step_sequence.py: --schedule rpm:sec,... (per-level hold), --lead-in-rpm /
  --lead-in-sec (reach a slow level from a faster one without passing zero),
  --pattern straight|spin|pivot-left|pivot-right (pivot = one wheel held at
  0, the other at the level), and --min-command-rpm to refuse a schedule
  the drive_component stop gate would not follow (below min_command_rpm, or
  a start from rest below min_command_rpm + 2).
- record.sh: pass those through with --sign; read wheel_radius /
  wheel_separation from drive_component; check the stop gate before the
  prompts; --set-param NAME=VALUE (min_command_rpm, stop_resend_interval_ms
  only) changes a parameter for the recording and restores and verifies it
  on exit, including Ctrl-C and failures, recording both in meta.yaml.
- record.sh preflight: give the /target_twist echo its type and stop on a
  CLI failure instead of treating it as "no traffic".
- README: stop-resend comparison, low-speed and pivot procedures.

Tests: test_step_sequence.py 11 pass; test_evidence.sh 74 pass (adds the
stop-gate refusal, --set-param allow list, restore after success and after
an interrupted run).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtCWWiR4ExGRx34k3Y78Xp
… parameter restore

From an independent review of 51dbf6d:

- step_sequence.py now subscribes to /emergency_stop (questix_msgs/EmergencyStop,
  reliable + transient_local) and aborts with exit code 4, publishing zeros,
  when a press arrives or the topic goes silent for --estop-timeout (1 s). A
  later release does not resume the remaining steps: drive_component blocks
  commands while pressed, but a sequence that kept publishing would move the
  wheels again the moment the E-stop was released. It does not start without
  a release; --no-estop-watch is only for diagnostics without the topic.
- record.sh reads each --set-param value back right after setting it and
  refuses to record on a mismatch; a failed restore now ends the run with exit
  code 5 (also from the EXIT trap) and is recorded in meta.yaml, instead of
  printing an error and exiting 0. Exit code 4 is recorded as
  step_sequence: "aborted_emergency_stop".

Tests: test_step_sequence.py 13 pass (EstopGuard); test_evidence.sh 82 pass
(read-back mismatch, restore failure -> 5, E-stop abort -> 4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtCWWiR4ExGRx34k3Y78Xp
Yuichiroh-Kobayashi pushed a commit that referenced this pull request Oct 5, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtCWWiR4ExGRx34k3Y78Xp
…he DDT power notes

step_sequence.py refused to start three times on 2026-10-06 although
/emergency_stop was flowing: it gave up when nothing arrived within the
2 s --listen-before window, and recorded the refusal as an E-stop abort.

- Wait up to --estop-wait (default 10 s) for the first /emergency_stop
  message, separately from --listen-before; foreign-publisher detection
  keeps running while waiting.
- Log the time to the first message, the message count, the publisher
  count and RMW_IMPLEMENTATION before starting.
- Exit 4 only when a press was received; exit 6 when no message (or a
  stale one) was received before the start. record.sh records exit 6 as
  step_sequence: "refused_estop_not_received".
- README / msg / questix_msgs README: whether the E-stop cuts the DDT
  power is not verified (the 2026-10-05 note said it does not; the
  2026-10-06 observations disagree); unplugging the RS485 side does not
  cut the power; position_raw across a power loss is not an angle or a
  turn count. Add the 2026-10-06 hand-turn results.

Comments only in the msg file (type hash unchanged).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtCWWiR4ExGRx34k3Y78Xp

Copy link
Copy Markdown
Collaborator Author

732a846 の CI で build-and-test (jazzy, amd64) が失敗しました。失敗したのは launcher/test/test_gpio_safety_runtime.py::test_gpio_ref_environment_does_not_select_the_no_gpio_diagnostic の 1 件だけです。questix_core の起動後 15 秒以内に、ros2 node list --no-daemon で /gpio_reader_node と /operation_manager_node が見つかりませんでした(見つかったノードは空)。

このコミットの変更ではないと判断した理由:

  • a7b54e7..732a846 で変えたのは scripts/identify/(step_sequence.py、record.sh、テスト、README)と、questix_msgs の README と msg のコメントだけです。launcher・gpio_reader・operation_manager には触れていません。
  • 同じコミットの build-and-test (jazzy, arm64) は成功しています。直前の a7b54e7 では、amd64 でもこのテストは通っていました。

いま用意できる修正はありません(テストの起動待ちのしかたは、この PR の範囲外です)。失敗したジョブを 1 回だけ再実行します。再実行でも同じ失敗が出たら、原因を調べ直します。


Generated by Claude Code


Generated by Claude Code

The operator confirmed that the DDT drive power drops to zero volts
while the E-stop is pressed (RS485 lines stay powered). Replace the
'not verified' notes, explain the 1.3-1.6 s of no response after each
release as the DDT booting, and mark the 2026-10-05 'not cut' note as
wrong. Comments only in the msg file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtCWWiR4ExGRx34k3Y78Xp
A level above drive_component's max_motor_rpm (or the 475 rpm spec clamp)
is silently clipped, so the identification input differs from the request.
record.sh now reads max_motor_rpm and step_sequence.py refuses such a
schedule before recording (--max-command-rpm). The default levels stop at
300 rpm, inside the M6 datasheet's +/-330 rpm, until the motor variant is
confirmed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtCWWiR4ExGRx34k3Y78Xp
Yuichiroh-Kobayashi pushed a commit that referenced this pull request Oct 6, 2026
…ntify

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtCWWiR4ExGRx34k3Y78Xp
Yuichiroh-Kobayashi pushed a commit that referenced this pull request Oct 7, 2026
@Yuichiroh-Kobayashi
Yuichiroh-Kobayashi marked this pull request as ready for review October 7, 2026 09:30
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtCWWiR4ExGRx34k3Y78Xp
Yuichiroh-Kobayashi pushed a commit that referenced this pull request Oct 7, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtCWWiR4ExGRx34k3Y78Xp

Copy link
Copy Markdown
Collaborator Author

robot_manager pytest + controller UI tests が 2f9befb(main a2a49e8 を取り込んだマージ)で 1 件失敗しました。この PR の変更によるものではありません。

  • 失敗:test_launcher_script.py::test_unusable_requests_never_launch_and_are_removed[request_kwargs0-600 s old]
  • 原因:テストは requested_at = int(time.time()) - 600 で起動要求を書き、ランチャーが別のプロセスで現在時刻との差を計算します。その間に秒が変わると、メッセージが 601 s old になり、'600 s old' in stdout が成り立ちません(ログ:the start request is 601 s old (limit 120 s))。
  • このテストとランチャーは main のもので(81e836f2、feat: split robot modes into lesson, practice and competition #171)、この PR は test_launcher_script.py もランチャーも変えていません。
  • 修正はまだありません。案:reason を "s old (limit 120 s)" にする、または re.search(r"\b60[01] s old", ...) で判定する(テストだけの変更、main に対して別の PR)。
  • 失敗したジョブを 1 回だけ再実行しました。

Generated by Claude Code


Generated by Claude Code

@asa-naki
asa-naki merged commit 89b641c into main Oct 10, 2026
20 of 21 checks passed
@asa-naki
asa-naki deleted the feat/drive-measurement-fidelity branch October 10, 2026 09:43
asa-naki added a commit that referenced this pull request Oct 10, 2026
Resolve the drive_component.cpp and shot_component.cpp conflicts by
carrying main's changes (#172, #176, #180) into the split translation
units. warnIfLqrTicksAssumeAnotherRate and warnCurrentModeAssumptions
go to drive_component_params.cpp with kLqrTickRateHz, and the unused
<thread> include is dropped from the shot split files as main dropped
it from shot_component.cpp. Function bodies match main's single files.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants