産業用6軸ロボット(DENSO)を中心に、電動ハンド・産業用カメラ・Webカメラ・ゲームパッドを統合し、 Webブラウザから遠隔操作できるリアルタイム制御システムをゼロから構築した。 バックエンドはPython/Flask、フロントエンドはサイバーパンク風カスタムUI。
Browser (192.168.1.76:8080)
│ REST API / MJPEG stream
▼
Flask App (app.py)
├── DensoRobot ─── b-CAP TCP ──▶ DENSO RC8 コントローラー
│ ├── Normal mode: robot_move() (PCボタン操作)
│ └── Slave mode: slvMove 125Hz (Gamepad)
│ └── Velocity Buffer (gamepadスレッド分離)
├── IAIHand ────── Modbus RTU ─▶ IAI 電動シリンダー
│ └── Hand Control Thread (Modbusブロッキング分離)
├── BaslerCamera ─ pypylon GigE ▶ Basler 4K カメラ
├── USBCamera ──── OpenCV ──────▶ USB Webカメラ
└── GamepadDriver ─ pygame HID ─▶ PS5 DualSense
└── 125Hz ポーリング → Velocity Buffer 書き込み
◈ 前半フェーズ(16:00〜)は Claude Desktop で実施。 要件定義書の読み込み・初期コード生成・Basler カメラ IP 設定などを完了後、 Claude Code CLI (Opus 4.7) へ引き継ぎ。
app.py / config.py / requirements.txt / hardware/*.py / templates/index.html
計 9 ファイルをゼロから一括生成。初期の Flask バックエンドと
ダークテーマ Web UI のベースを完成させた。
192.168.127.100 に更新済み。
lsof で前回の残留プロセス(PID 87376)を特定・kill して解決。
| 問題 | エラーコード / 現象 | 難易度 | 根本原因 | 解決策 |
|---|---|---|---|---|
| 【前半】 Basler カメラの IP 未設定・発見不可 |
PING 不達 GigE Discovery 無応答 |
高 | カメラ出荷時 IP 未設定のためリンクローカル (169.254.x.x) で起動。 pypylon では変更 API なし | GVCP プロトコルで PersistentIP レジスタ (0x064C) を直接書き込み。 ForceIP コマンドで 192.168.127.100 に即時適用。 電源再投入で永続化 |
| Basler カメラが起動しない (引き継ぎ後) | 0xE1018006Controlled by another app |
中 | 前回セッションの残留プロセスが GigE バスを占有 | lsof / kill で残留プロセスを強制終了 |
| slvMove API 呼び出しエラー | AttributeError: int has no attribute slvMove |
中 | self._h_robot は int ハンドル。直接メソッド呼び出し不可 |
bcap_client.robot_execute(handle, "slvMove", pos) に修正 |
| 初期位置[0,0,0]への急動作 | ロボットが起動直後に 原点へ急移動(危険) |
高 | target_pos のデフォルト値が [0,0,0,0,0,0] 初期化時に CurPos を取得していなかった |
connect() で Motor ON 直後に CurPos を取得し target_pos の初期値として設定 |
| スレーブモード速度違反 (スティック即クラッシュ) |
0x835001210x84201482 |
最高 | デフォルト座標系(Tool0)で rx≈180, ry≈0 → 手首特異点近傍。 微小な TCP 移動でも関節速度が爆発 |
robot_change(HRobot, "Tool1") をTakeArm直後に追加。 Tool1 では ry≈−0.15, rz≈90° で特異点回避 |
| gamepad ガタガタ問題 | ロボットが断続的にガタつく (可聴レベルの振動) |
高 | デルタバッファ(積み上げ方式)はgampad/slaveの 周期ずれで二重加算・ゼロ加算が交互発生 |
速度バッファ(上書き方式)に変更。 gamepad は "今この速度" を毎フレーム書くだけ |
| IAI ハンドの応答遅延 | ボタンを離してから ハンドが止まるまでに遅延 |
中 | Modbus RTU write (~15ms) がgamepadスレッドを ブロックし、ボタン離し検出が遅延 |
hand-ctrl 専用スレッドを分離。 gamepad は velocity 変数を書くだけ。即停止 |
| L1/R1 ボタンが無反応 | RX 軸が全く動かない | 中 | macOS DualSense の実際のマッピングが文書と相違。 デバッグログで確認: L1=10, R1=9 |
全ボタン押下ログを仕込み実測でインデックス特定。 BTN_L1=10, BTN_R1=9 に修正 |
熟練エンジニアが同等のシステムを単独で構築する場合、 仕様書解読・ドライバ実装・デバッグを含めると 2〜4週間 を要すると想定される。 Claude Code との協業により 4時間30分(16:00〜22:30) での完成を実現。 ただし、実機フィードバックと参照コードの提供が解決の鍵であり、 「AIが知識を持ち、人間が現場情報を提供する」協業モデルが最も効率的だった。
積み上げ型デルタバッファはgamepadとslaveのサイクルずれで二重加算が起きる。 上書き型速度バッファに変えることで、どちらが速くても「今この速度」が常に正確に伝わる。
# NG: 積み上げ → 周期ずれで二重加算・ゼロ加算 delta_buf[i] += delta[i] # gamepadが書く pos[i] += delta_buf[i] # slaveが消費 # OK: 上書き → 常に最新の速度コマンドが適用 velocity[i] = delta[i] # gamepadが書く (+=ではない) pos[i] += velocity[i] # slaveが毎サイクル読む
DENSO RC8 でスレーブモードを使う際、robot_change(HRobot, "Tool1") を
TakeArm の直後に呼ばないと、デフォルト座標系(Tool0)で rx≈180°, ry≈0° の
手首特異点近傍に配置される。この状態では微小な TCP 移動で関節速度が発散し、
スレーブモードが即座にエラー終了する。
# この一行を入れないとスレーブモードが即クラッシュ m_bcapclient.robot_change(HRobot, "Tool1") # ← 必須 cur_pos = m_bcapclient.robot_execute(HRobot, "CurPos") # slvChangeMode(0x201) へ続く
Python の GIL 環境で複数の通信プロトコルが競合するとタイミングが不安定になる。 各ハードウェアを専用スレッドに隔離し、共有変数(速度バッファ)のみで通信する設計が 最もガタつきが少なく、応答性も高かった。
最大の教訓は、「AIと人間の役割分担の明確化」だ。 AI はプロトコル実装・コード生成・エラー分析・設計提案を高速に行う。 人間は実機の現場フィードバック・参照コードの提供・最終判断を担う。 この協業モデルにより、従来の開発期間を大幅に短縮できることが実証された。
一方で、ハードウェア固有の暗黙仕様(座標系・ボタンマッピング・通信タイミング)は 実機テストなしには解決できない壁であり、AI はあくまで「現場情報を持つ専門家と協働する 高速なコーディングパートナー」として機能することが本質だと理解された。