Pixels to Motors
DAY 190
mechArm 270 Pi
Log #8
動的追従の限界
DESCEND後に見えた次の壁 ── 把持姿勢への二段追従という問い
FROM / Log #7
動的追従ループは収束した
2D dist 2.9mm
DESCEND前 IndexError
把持シーケンス未到達
→
TO / Log #8
DESCEND完走、それでも掴めない
送/着ズレ実測
TIP_X 61.4mm シフト
運動学の壁
💡 Key Takeaway ── 本日の達成
01 / 構造的修正
IndexError を四段で構造的に潰した
daemon リトライ + Mac None ガード + 動的タイムアウト + ソケット再接続。観測パッチが本流を止めない設計に。
02 / 実測値の獲得
送/着ズレが初めて取れた
[0.02, 1.02, 0.17, -0.35, 0.88, -0.23]。命令通りに動いていることが実測で確認できた。
03 / 問題の再定位
掴めない理由は制御ではなく運動学
APPROACH→DESCEND の姿勢遷移で爪先が 61.4mm シフト。次のシリーズは二段追従と姿勢拘束。
01
起点 / Log#7 からの宿題
DESCEND 前後で `[]` が返り IndexError で停止
!
Log#7 持ち越し / 致命エラー
把持シーケンス本流まで到達できない
発生箇所
DESCEND 前後
症状
空リスト []
後段
IndexError 停止
→ 観測パッチが本流を止めない構造に変える
"
[OBS] DESCEND前 angle: []
[ERROR] list index out of range
Log#7 末尾
実機ログ
既知挙動の根本原因
pymycobot 由来の偶発的空リスト返却
原因 / pymycobot
get_angles() が稀に [] を返す
シリアル通信のバッファタイミングが噛み合わないと発生する既知挙動。
なぜ Log#6 まで顕在化しなかったか
旧 SSH 版は別プロセス起動
subprocess で毎回フレッシュな状態で呼べていて、偶然この問題を回避していた。
Log#7 で顕在化
デーモン化で永続 MyCobot
同一プロセスからの連続コールで、バッファ起因の空リスト返却が露出した。
なぜ今になって顕在化したか
通信構造の違い ── subprocess 版 vs daemon 版
旧 / SUBPROCESS 版
プロセスを毎回起動 → フレッシュな状態
Mac
SSH
新プロセス起動
get_angles()
毎回終了 ✓
新 / DAEMON 版 (Log#7 から)
同一プロセスから連続コール → バッファ起因の [] が顕在化
Mac
TCP
永続 daemon
mc.get_angles()
(永続インスタンス)
[ ] が稀に返る ⚠
02
構造的修正 / 4段
当初は三段、実際は四段 ── レイヤー別に潰す
修正レイヤー (各カードをクリックで展開)
観測パッチが本流を止めない設計に
01
daemon 側に get_angles リトライ (Pi)
+
DAEMON
100ms 待機
最大3回試行
失敗時は明示エラー
02
Mac 側 daemon_get_angles を None 返却に統一
+
MAC GUARD
RuntimeError を吸収
None / len!=6 を前提に
差分計算は両方有効時のみ
本流は止めない
03
動的タイムアウト ── wait + 3.0秒
+
DYNAMIC
cmd_obj.wait 参照
HOME/LIFT: 7.0s
APPROACH/DESCEND: 6.0s
デフォルト: 5.0s
04
タイムアウト時のソケット強制 close + 再接続
+
SOCKET
_daemon_sock = None
_daemon_buf クリア
バッファ汚染を防ぐ
次回呼び出しで再接続
"
観測失敗は本流を止めない、
という原則で書き直す。
修正2
設計原則
4段の修正がデータフローのどこに入るか
Mac ↔ daemon ↔ MyCobot の縦パイプ図
Mac (visual_servo_pick.py)
Pi / daemon (mecharm_daemon.py)
MyCobot
daemon_get_angles()
cmd=get_angles
get_angles handler
mc.get_angles()
[ ] (稀)
修正 1
100ms 待機 → 最大 3 回
a, len(a)==6 で break
{"ok":false}
修正 2
RuntimeError 吸収
→ None で返却
pre/post DESCEND
None なら計算スキップ
本流 DESCEND 継続 ★
修正 3 + 修正 4 (Mac の _daemon_request)
cmd_obj.wait + 3.0 sec で動的 timeout
timeout 時: sock.close() → 次回再接続
バッファ汚染を構造で防ぐ
= 修正ブロック
= 既存処理
= 強調・到達点
03
別の壁 / 初期HOMEで timeout
修正1・2投入後、最初の HOME で死んだ
事実確認 / 推測で動かない
daemon_send_angles の wait 値一覧
541
関数定義(デフォルト)
+
wait=0
マージン 5.0s 内
579
send_home() / HOME
+
wait=4
マージン 1.0s ⚠
超過しやすい
701
動的追従ループ内 send_angles
+
wait=2
マージン 3.0s
890
refine_mode 内 send_angles
+
wait=0
1027
APPROACH
+
wait=3
マージン 2.0s
1087
DESCEND
+
wait=3
マージン 2.0s
1127
LIFT
+
wait=4
マージン 1.0s ⚠
!
構造的バグ / ソケット汚染
_daemon_request に except ブロックが無かった
DAEMON_TIMEOUT
5.0 sec
HOME wait
4 sec
マージン
1.0 sec
→ シリアルジッタで容易に超過 / 遅延応答が次のコマンドの応答として読まれる
各コマンドの wait と DAEMON_TIMEOUT_SEC = 5.0s の関係
マージン可視化 ── HOME/LIFT は綱渡り、シリアルジッタで容易に超過
0s
1s
2s
3s
4s
5s
6s
DAEMON_TIMEOUT = 5.0s
L541 default
wait=0 / マージン 5.0s
L579 HOME
wait=4 / マージン 1.0s ⚠
L701 track
wait=2 / マージン 3.0s
L890 refine
wait=0 / マージン 5.0s
L1027 APPROACH
wait=3 / マージン 2.0s
L1087 DESCEND
wait=3 / マージン 2.0s
L1127 LIFT
wait=4 / マージン 1.0s ⚠
↑ シリアルジッタが乗ると
5.0s を超過
04
設計判断 / 対症 vs 構造
DAEMON_TIMEOUT 一律拡大か、動的タイムアウトか
案 A / 一律拡大
不採用
手段
DAEMON_TIMEOUT_SEC を 10秒など一律で大きくする
短所
対症療法。wait=10 のコマンドが追加されれば再発する
本質
マージン拡大であって、構造の修正ではない
案 B / 動的タイムアウト + ソケット再接続
採用 ★
手段
cmd_obj.wait を見て wait + 3.0秒を動的に設定
手段
タイムアウト時に sock.close() + _daemon_sock = None
利点
新規 wait 値にも自動追従 / バッファ汚染を構造で防ぐ
スコープ
socket.timeout のみ対象。他の例外はスコープ外
確定動作
cmd_obj.wait に応じた timeout 自動設定
⏱
wait=0 (デフォルト)
5.0 sec
⏱
wait=2 (追従)
5.0 sec
⏱
wait=3 (APPROACH)
6.0 sec
⏱
wait=4 (HOME/LIFT)
7.0 sec
05
追加修正 / set_gripper timeout
隠れていた事前 sleep 2秒
!
DAY175 由来の遺産
daemon 側の set_gripper に事前 sleep が隠れていた
事前 sleep
2.0 sec
事後 wait
2.0 sec
動的 timeout
5.0 sec ✕
→ 動的タイムアウトは cmd_obj.wait しか見ないので事前 sleep の存在を知らない
選択肢 1 / Pi 側を改修
見送り
手段
cmd_obj に「事前待機時間」を明示するフィールド追加
難点
daemon 側プロトコル変更が伴う
選択肢 2 / Mac 側で wait を増やす
採用 ★
手段
Mac 側 daemon_set_gripper の wait を引き上げ
利点
set_gripper は頻度が低い / 構造変更なし
結果
最後まで通った
タイムライン / なぜ動的タイムアウトでも超過したか
set_gripper ハンドラ内部の時間配分
0s
1s
2s
3s
4s
5s
動的 timeout = 5.0s
Pi 側 内部
事前 sleep 2.0s (DAY175 遺産)
_mc.set
事後 sleep 2.0s (cmd_obj.wait)
合計 4s + α (set_gripper_state 自体の処理)
Mac 側 認識
動的 timeout = wait(2) + 3.0 = 5.0s
超過
→ Mac は事前 sleep の存在を知らない。Mac 側の wait を増やす方が頻度が低い set_gripper には合理的だった。
06
到達点 / 送/着ズレ実測
DESCEND が完走し、命令と着地の差が初めて取れた
"
[OBS] 送/着ズレ (post - sent):
[0.02, 1.02, 0.17, -0.35, 0.88, -0.23]
DESCEND
実機ログ
j0
+0.02
deg
j1
+1.02
deg ★
j2
+0.17
deg
j3
-0.35
deg
j4
+0.88
deg ★
j5
-0.23
deg
この実測値の意味
物理的にほぼ命令通り動いた
事実 / j1 + j4
最大 1.02° の着き不足
それ以外の関節は 0.4° 以下。サーボ追従性能の問題ではない範囲。
Log#5 との対比
命令が滞留していた過去の症状
SSH 経由で Pi 側に溜まっていた状態は Log#7 デーモン化で解消済み。
今後の基準
姿勢別の mm/deg 換算が宿題
1°前後の着き不足が爪先の世界座標で何 mm になるか。次シリーズの実測課題。
6軸 / 送信値と着地値の差 (deg)
j1 と j4 が突出、それ以外は 0.4° 以下
+1.5°
+1.0°
+0.5°
0°
−0.5°
−1.0°
+0.02
j0
+1.02 ★
j1
+0.17
j2
−0.35
j3
+0.88 ★
j4
−0.23
j5
★ = サーボの追従性能限界に近い
07
壁 / それでも掴めない
DESCEND 後の最初のフレームで TIP_X が 61.4mm シフト
APPROACH 収束時
+213.5
mm (tip_x = red_x)
ALIGNED
DESCEND 後 最初の観測
+152.1
mm (tip_x)
SHIFTED
シフト量
−61.4
mm / 1 フレーム
DIFF_X
!
refine_mode の振る舞い
Z 制御が離れる方向に動いている
t=0.0s diff_z
+60.0mm
t=1.8s diff_z
+71.5mm
t=3.7s diff_z
+80.4mm
→ DESCEND 後の姿勢領域では j1+j2+j4 連動の符号が再び逆の可能性 / 仮説段階・実測必須
TOP-DOWN VIEW / マット平面での TIP_X シフト
APPROACH 収束時の爪先位置 → DESCEND 後の爪先位置
MAT (top-down)
→ +X
red block
x = +213.5
TIP @ APPROACH
tip_x = +213.5 (diff_x = ±0.0)
TIP @ DESCEND 後 →
tip_x = +152.1
diff_x = +61.4mm
命令通り動いた結果として、爪先の世界座標が幾何学的にシフトしている
refine_mode の最初の 4 フレーム / diff_z の推移
縮まるはずの Z が、逆に開いていく
100
80
60
40
20
0
diff_z (mm)
t=0.0s
t=1.8s
t=3.7s
t=5.4s
60.0
71.5
80.4 ↑
79.0
本来の収束方向 (Z 距離を縮める)
↑ 実際は離れる方向に動いている
08
構造 / 制御ではなく運動学
APPROACH と DESCEND は別の姿勢領域だった
APPROACH 姿勢
⌐ TIP
収束時の姿勢 (Unity 投影で TIP=ブロック)
j1
+76.10°
j2
−31.64°
TIP_X
+213.5mm
diff_x
±0.0mm ◎
→
DESCEND 後
⌐ TIP'
爪を下ろした後の別姿勢
j1
+87.74°
j2
−5.00°
TIP_X
+152.1mm
diff_x
+61.4mm ✕
MAT (z=0)
BASE
★
red block
x = +213.5
APPROACH
j1=+76° j2=−32°
TIP_X=+213.5
DESCEND
j1=+87° j2=−5°
TIP_X=+152.1
SHIFT 61.4mm →
SIDE VIEW (姿勢遷移の幾何)
同じ命令の関節角でも、姿勢が違えば爪先の世界座標は別になる
"
これは制御の問題ではない。
運動学側の構造的なギャップだ。
本文より
原因の再定位
09
次の設計 / 二段追従
姿勢拘束付きの第二追従レイヤーを設計する
"
アプローチPICK姿勢から真下に下がった後も、
TIP とブロックの位置調整が必要。
その時にグリッパーが上に向かない姿勢制御が必要。
現場の指摘
次シリーズの種
制御レイヤーの分離
2段階に分けた追従設計
段階 1 / 既存
APPROACH 段階の追従
上空でブロックの上に爪を持っていく。Log#7 で収束を達成済み。
段階 2 / 新規 ★
DESCEND 後の追従
降下後、グリッパー向きを下に保ったまま、もう一度 (x, z) を合わせる。自由度に拘束あり。
次シリーズの宿題
DESCEND 後姿勢領域での設計
◎
実測キャリブレーション
j1≈+87.74 / j2≈−5.0 での Z 符号 + mm/deg
⊥
姿勢拘束付き運動学
グリッパー下向きを保つ (x, z) 解
⟷
レイヤー分離
APPROACH 追従と DESCEND 後追従を別実装
制御フロー / 現状 (Log#8) vs 次シリーズ提案
PICK シーケンスへ第二追従レイヤーを挿入する
現状 (Log#8) ── 段階 1 のみ
DESCEND 直後の姿勢ギャップで爪先がシフト
PICK_INIT
APPROACH
段階 1 追従
DESCEND
GRIP ✕
LIFT
↑
TIP_X が 61.4mm シフトしている
次シリーズ ── 段階 2 を新規挿入
DESCEND 後にも (x, z) を合わせ直す + 姿勢拘束付き運動学
PICK_INIT
APPROACH
段階 1 追従
DESCEND
段階 2 追従 ★
(姿勢拘束付き)
GRIP ✓
↓
グリッパー下向きを保ったまま
(x, z) を合わせ直す
"
仮説を立てる前に、
まず測れる手段を持つこと (P008)。
設計原則
Log#5 からの教訓
本日の結論 ── 動きの質の壁から、形の質の壁へ
BEFORE / Log#7 まで
「動的追従が収束した日」
動きの質が変わった世界
2D 距離収束
◎
DESCEND 完走
✕
送/着ズレ実測
✕
掴むまで
✕
→
AFTER / Log#8 で
「形の質の壁」を初めて見た
運動学が問われる世界
2D 距離 2.8mm 到達
◎
DESCEND 完走
◎
送/着ズレ実測
◎
掴むまで
次シリーズ