Unityでデジタルツインに「解析の眼」を授ける|mechArm 270データ可視化
LOG30R2R2R2026/1/8

Unityでデジタルツインに「解析の眼」を授ける|mechArm 270データ可視化

ロボットアームとの双方向通信に成功した喜びも束の間、新たな課題に直面しました。ターミナルを高速で流れる数値の滝を見ていても、ロボットが本当に正しく、効率的に動いているのかが直感的にわからなかったのです。

そこでリアルタイム・データモニターの自作に挑みました。

※本プロジェクトの目的:実機「mechArm 270 M5」の制御を通じて、AIの判断をいかに物理的な動きへ変換するか。そのロボット制御の仕組みを理解するプロセスを記録・検証するものです。

リアルタイム・グラフ化ツール

このスクリプトは、実験2で成功した /joint_states トピックを監視し、関節1(link1)の動きをリアルタイムで波形として描き出します。

保存方法: unity_ws/scripts フォルダの中に、標準テキスト形式で joint_monitor.py として保存してください。

Python

import rclpy
from rclpy.node import Node
from sensor_msgs.msg import JointState
import matplotlib.pyplot as plt
from collections import deque

class JointVisualizer(Node):
    def __init__(self):
        super().__init__('joint_visualizer')
        # 実験2で確認したトピックを購読
        self.subscription = self.create_subscription(
            JointState, '/joint_states', self.listener_callback, 10)
        
        # 過去100データ分のバッファを用意
        self.data_buffer = deque(maxlen=100)
        self.time_buffer = deque(maxlen=100)
        self.counter = 0

        # グラフの初期設定
        plt.ion() # インタラクティブモードON
        self.fig, self.ax = plt.subplots()
        self.line, = self.ax.plot([], [], 'r-')
        self.ax.set_ylim(-2.0, 2.0) # ラジアンの範囲
        self.ax.set_title('Real-time Joint 1 Position')
        self.ax.set_xlabel('Time Steps')
        self.ax.set_ylabel('Angle (rad)')

    def listener_callback(self, msg):
        # link1 の角度(index 0)を取得
        joint_pos = msg.position[0]
        self.data_buffer.append(joint_pos)
        self.counter += 1
        self.time_buffer.append(self.counter)

        # グラフの更新
        self.line.set_data(list(self.time_buffer), list(self.data_buffer))
        self.ax.set_xlim(max(0, self.counter - 100), self.counter + 5)
        self.fig.canvas.draw()
        self.fig.canvas.flush_events()

def main():
    rclpy.init()
    node = JointVisualizer()
    try:
        print("グラフ表示中... (Ctrl+Cで停止)")
        rclpy.spin(node)
    except KeyboardInterrupt:
        pass
    rclpy.shutdown()

if __name__ == '__main__':
    main()

実行の準備:ライブラリのインストール

グラフを描画するために、matplotlib というライブラリが必要です。ターミナルで以下のコマンドを 1 回だけ実行してください。

conda activate ros_env
pip install matplotlib

実験の進め方(3枚のターミナルを使いこなす!)

デジタルツインらしくなってきました。以下の順に起動してください。

  • Unityを再生(▶︎)する: 青いHUDを確認。

  • ターミナル1(送信): python3 robot_dance.py でロボットを動かす。

  • ターミナル2(可視化): ここで今回のツールを起動!

cd ~/unity_ws/scripts
conda activate ros_env
source ~/unity_ws/install/setup.zsh
python3 joint_monitor.py

この実験で「見える」こと

リアルタイム・グラフ
  • キレの視覚化: 実験1で設定した1.0秒ごとのポーズ遷移が、グラフ上でカクカクとした波形として現れます。

  • 物理挙動の確認: ロボットが目標角度に到達した際、少しだけ揺れているような物理エンジンの細かい挙動も、グラフなら捉えることができます。

  • システム遅延の把握: プログラムで送った瞬間と、グラフの山が動く瞬間のズレを見ることで、通信環境の健康診断ができます。

意外な「落とし穴」たち

ツール制作の序盤、いくつかの初歩的な、しかし厄介な壁にぶつかりました。

  • ファイル名の罠: プログラムを保存する際、無意識に .txt が末尾についてしまい、python3 コマンドでファイルが見つからないと怒られる事態に。

  • フォーマットの罠: Macのテキストエディットが標準で「リッチテキスト(.rtf)」になっていたため、コードの中に余計な装飾情報が混じり、実行エラーが発生。

  • ターミナル管理の混乱: 通信サーバー(Endpoint)、ダンス指令(Dance Script)、生データ表示(Echo)、そしてグラフツール。気づけば4枚ものターミナルが画面を埋め尽くし、どれがどれか分からなくなるマルチターミナル迷子になりました。

これらを一つずつ整理し、iCloud同期外のローカルフォルダ(unity_ws)に正しく配置することで、開発環境は劇的に安定しました。

疑問:「Joint 1」の正体を探る

最初に赤い線が一本、グラフ上に現れたとき、一つの疑問が湧きました。このJoint 1というのは、Unityのどの部分のことなのか?

データのインデックス(並び順)を紐解くと、以下の事実が判明しました。

  • ROS 2のメッセージには link1 から link6 が順番に格納されている。

  • プログラムで index[0] を指定して描画していたのは、紛れもなくロボットの土台となる link1 の回転角だったのです。

    この発見により、プログラム上の数値と3Dモデルの挙動が、頭の中で完全に一致しました。

全6軸の同時監視へ:データを丸裸にする

「他の関節も同時に見たい」という好奇心から、ツールをさらに改良しました。6本の異なる色の線が、ダンスのリズムに合わせて一斉に波打つ光景は圧巻です(サムネイルの画像がまさにソレです)

  • 2倍速の検証: 昨日作成したキレキレダンスを動かすと、1.0秒間隔で送られる指令に対してグラフが鋭い矩形波(くけいは)を描きました。

  • 物理挙動の視覚化: 数値だけでは見落としていた、微細な物理エンジンの揺れまでが波形として可視化されました。

解析フェーズ

状態

達成したこと

単軸モニタリング

成功

基本的な描画ロジックの確立

全6軸同時モニタリング

成功

腕全体の連動性の視覚化

マルチタスク運用

確立

4枚のターミナルによる監視体制

デジタルツインは「知能」への準備を終えた

デジタルツインは単に繋がっているだけの状態から、挙動を精密に監視・解析できる研究用プラットフォームへと進化しました。

失敗から学んだ環境構築の厳格さと、疑問から得たデータ構造の理解。これらがあるからこそ、次なるステップである実験3:テレオペレーション(自由操作)にも自信を持って挑めます。