🤖 ros2-lab ROS2 教育ラボ — topic / service / action / 物理 / AI

未接続

人形ロボット 3D(物理エンジン駆動)

3D は物理シミュの /joint_states(関節) と /model_pose(ベース) で動きます。重力で落下・接地し、姿勢を崩すと転倒します。

📋 この操作をコードで (topic 購読)
# コンテナ内で実行: docker exec -it ros2-lab bash
source /opt/ros/jazzy/setup.bash
ros2 topic echo /joint_states --once     # 関節角を 1 回受信
ros2 topic hz /joint_states              # 配信レート(≈30Hz)

# rclpy (Python)
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import JointState

rclpy.init()
node = Node("listener")
node.create_subscription(JointState, "/joint_states",
    lambda m: print(dict(zip(m.name, m.position))), 10)
rclpy.spin(node)

目標姿勢(位置制御)

URDF 読込中…
📋 この操作をコードで (topic 発行)
# 両手を上げる (discovery のため数回 publish: -r 5 -t 5)
ros2 topic pub -r 5 -t 5 /target_joint_positions sensor_msgs/msg/JointState \
  "{name: [l_shoulder, r_shoulder], position: [3.0, 3.0]}"

# rclpy (Python)
pub = node.create_publisher(JointState, "/target_joint_positions", 10)
msg = JointState(name=["l_shoulder", "r_shoulder"], position=[3.0, 3.0])
pub.publish(msg)   # 実運用は timer で数回 publish すると確実

物理シミュ状態 (PyBullet)

目標角 → /target_joint_positions → PyBullet(重力 -9.81, 接地) → /joint_states + /model_pose → 3D

/joint_states 受信0
ベース高さ z (m)-
傾き (度)-
IMU 角速度 ωy (rad/s)-
バランス制御-
状態-
バランス制御(足首戦略 PD)で直立を維持。「押す(外乱)」で前方に押すと、
ON なら踏ん張って復帰、OFF なら倒れます。⤓ で起き上がり。

⚠️ 歩行・横方向バランスは未実装です。足首戦略だけでは重心が足裏を越える外乱を戻せず、踏み出し(capture point)や全身 MPC という別クラスの制御が必要なため — これ自体がバランス制御の重要な教材です。📖 詳しく

📋 この操作をコードで (service 呼び出し)
ros2 service call /set_balance std_srvs/srv/SetBool "{data: false}"  # バランス OFF
ros2 service call /push        std_srvs/srv/Trigger                  # 前方 140N の外乱
ros2 service call /reset_sim   std_srvs/srv/Trigger                  # 起き上がり/リセット

⚖ 接地力 / ZMP バランスの実測 (Phase 14)

足裏の接地力(法線力)ZMP(圧力中心)。ZMP が足裏(支持多角形)の中にある間は立っていられ、端に達すると転倒します。押す(外乱)と赤点が前に動くのを観察してください。

左足 接地力- N
右足 接地力- N
↑ 前 (上面図, base 相対)
ZMP(圧力中心) CoM 投影 ・ 枠 = 足裏。3D 表示の床にも同じマーカーが出ます。
📋 この操作をコードで (力センサ/ZMP topic)
ros2 topic echo /zmp --once                # ZMP(圧力中心, 世界座標)
ros2 topic echo /foot_force_left --once    # 左足の法線力 (geometry_msgs/WrenchStamped)
ros2 topic echo /com --once                # 全身重心の床投影
ros2 topic hz /zmp                         # ≈30Hz

ロボット視点カメラ 首スライダで左右パン

頭部カメラの一人称視点(JPEG 10Hz)。1 回のレンダから RGB / 深度 / セグメンテーションの 3 ストリームを配信 — 実機の RGB-D カメラと同じ構成です。色柱(赤=正面/緑=左前/青=右前/黄=背面)。

robot camera
📋 この操作をコードで (画像 topic)
ros2 topic hz /camera/image_raw/compressed              # ≈10Hz
ros2 topic echo /camera/image_raw/compressed --once --no-arr   # ヘッダのみ表示

# 首を回してカメラをパン (neck はヨー軸)
ros2 topic pub -r 5 -t 5 /target_joint_positions sensor_msgs/msg/JointState \
  "{name: [neck], position: [1.2]}"

# 深度 / セグメンテーション
ros2 topic hz /camera/depth/compressed
ros2 topic hz /camera/segmentation/compressed

🌀 LiDAR (2D スキャン) /scan (sensor_msgs/LaserScan, 5Hz)

base 高さ 0.35m の水平 360° レーザースキャン(180 本、機体固定)。周囲の色柱が点列として見えます。上=前方、中心=ロボット。自律移動の目になるセンサです。

📋 この操作をコードで (LaserScan)
ros2 topic hz /scan                      # ≈5Hz
ros2 topic echo /scan --once --no-arr    # メタ情報(角度範囲・分解能)
# rclpy: msg.ranges[i] が角度 angle_min + i*angle_increment の距離[m]

🌳 tf ツリー /tf をライブ購読 (v1.1)

全フレームの親子関係と変換をライブ表示。robot_state_publisher が URDF+関節角から、physics_sim が world→base_link を配信しています。関節を動かすと値が変わります。

(受信待ち…)
変換:
フレームを選択…
📋 この操作をコードで (tf)
ros2 run tf2_ros tf2_echo base_link head   # 2 フレーム間の変換をライブ表示
ros2 run tf2_tools view_frames             # ツリーを PDF に出力
ros2 topic echo /tf --once                 # 生の TransformStamped 配列

📶 QoS 実験 durability / history depth (v1.1)

QoS は DDS 通信の性質を決める設定。決定的に再現する実験で体感できます(実験はノード内で完結し、結果を service で受け取ります)。

boot 時に 1 回だけ送られたメッセージを「後から購読」して届くか試します。
📋 この操作をコードで (QoS)
# CLI で latch を体感(後から購読しても届く)
ros2 topic echo /qos/latched --qos-durability transient_local --once
ros2 topic echo /qos/volatile --once      # ← こちらは何も来ない

# rclpy で QoS 指定
from rclpy.qos import QoSProfile, DurabilityPolicy
qos = QoSProfile(depth=1, durability=DurabilityPolicy.TRANSIENT_LOCAL)
node.create_publisher(String, "/qos/latched", qos)

♻ lifecycle ノード managed node の状態機械 (v1.1)

lifecycle ノードは状態機械で管理される: /lifecycle/heartbeatactive のときだけ流れます(inactive では publish がドロップ)。遷移させて確かめてください。

unconfigured ──configure──▶ inactive ──activate──▶ active
     ▲              cleanup ──┘   ▲── deactivate ──┘
現在の状態-
heartbeat 受信0
📋 この操作をコードで (lifecycle)
ros2 lifecycle get /lifecycle_demo             # 現在の状態
ros2 lifecycle set /lifecycle_demo configure   # unconfigured → inactive
ros2 lifecycle set /lifecycle_demo activate    # inactive → active
ros2 topic echo /lifecycle/heartbeat           # active のときだけ流れる

🦊 Foxglove Studio 連携 業界標準ツール (v1.1)

このラボの全 topic / tf / カメラ画像は、ロボティクス業界標準の可視化ツール Foxglove Studio からも見られます(foxglove_bridge が ws :16552 で待機)。自作 UI(rosbridge) と業界標準の両対応です。

1. Foxglove Studio (デスクトップ版) を起動
2. 「Open connection」→ Foxglove WebSocket
3. URL: ws://192.168.11.90:16552   (LAN 内)
4. 3D パネルに /tf・Image パネルに /camera/image_raw/compressed 等を追加

※ Web 版 (app.foxglove.dev) は https のため ws:// に接続できません — デスクトップ版を使うか、wss 中継が必要です。

🎯 ROS2 Action デモ /perform_motion (control_msgs/FollowJointTrajectory)

ROS2 の第3の通信「アクション」= 長時間ゴール + 進捗フィードバック + キャンセル可。動作を選ぶと軌道ゴールを送り、physics_sim がバランス下で実行します(topic/service との違いを体感)。

進捗 0% ・ 状態 待機

      
📋 この操作をコードで (action)
ros2 action list                                  # /perform_motion が見える
ros2 action send_goal --feedback /perform_motion \
  control_msgs/action/FollowJointTrajectory \
  "{trajectory: {joint_names: [l_shoulder, r_shoulder],
     points: [{positions: [3.0, 3.0], time_from_start: {sec: 2}}]}}"

# rclpy (Python)
from rclpy.action import ActionClient
from control_msgs.action import FollowJointTrajectory

ac = ActionClient(node, FollowJointTrajectory, "/perform_motion")
ac.wait_for_server()
goal = FollowJointTrajectory.Goal()
goal.trajectory.joint_names = ["l_shoulder", "r_shoulder"]
# goal.trajectory.points に JointTrajectoryPoint(positions, time_from_start) を追加
future = ac.send_goal_async(goal)   # 中断は goal_handle.cancel_goal_async()

🦾 腕リーチ (IK) 2 リンク逆運動学 → action 実行 (v1.1)

肩フレームの目標 (前後 x, 上下 z) を決めると、バックエンドが閉形式 2 リンク IK で肩・肘の角度を解き、/perform_motion action で腕を伸ばします。3D の橙のボールが目標 — 届くとになります(tf で手先位置を検証)。腕は肩の縦平面しか動けない(肩・肘とも同軸ピッチ)ことも体感できます。

IK 解 (shoulder / elbow)-
手先と目標の距離-

      
📋 この操作をコードで (2リンク IK)
# IK を解く(バックエンド API)
curl "https://ros2-lab.akylabo.com/api/arm-ik?x=0.35&z=0.0"
# → {"shoulder": 0.79, "elbow": -1.62, ...}

# 閉形式 2 リンク IK (L1=0.26, L2=0.24):
#   cos(q2)   = (x²+z² − L1² − L2²) / (2·L1·L2)      # 肘の曲げ量
#   shoulder  = atan2(x, −z) − atan2(L2·sin(q2), L1 + L2·cos(q2))
#   elbow     = −q2                                   # URDF は負方向が曲げ

# 解いた角度を action で実行
ros2 action send_goal /perform_motion control_msgs/action/FollowJointTrajectory \
  "{trajectory: {joint_names: [r_shoulder, r_elbow],
     points: [{positions: [0.79, -1.62], time_from_start: {sec: 2}}]}}"

🛩 フライトレコーダ / ブラックボックス

テレメトリ(高さ z / 傾き / バランス)と事象(押す・転倒・復帰・バランス切替・リセット)を SQLite に記録(/api/recorder/*)。エピソード(⤓リセット間)を選ぶと、そこで何が起きたかを「ブラックボックス」として振り返れます。

    エピソードを選択してください
    高さ z(0〜0.9m)   傾き(0〜90°)  |  縦線=事象: 押す / 転倒 / 復帰 / バランス / リセット

    サービス呼び出し — /add_two_ints

    a b
    
          
    📋 この操作をコードで (service)
    ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 2, b: 3}"
    
    # rclpy (Python)
    from example_interfaces.srv import AddTwoInts
    
    cli = node.create_client(AddTwoInts, "/add_two_ints")
    cli.wait_for_service()
    fut = cli.call_async(AddTwoInts.Request(a=2, b=3))
    rclpy.spin_until_future_complete(node, fut)
    print(fut.result().sum)   # -> 5

    ノードグラフ