Dynamixel For Unity Document - HackMD

目次

1. 概要

「DynamixelforUnity(D4U)」は、Unity3D内で簡単にDynamixelサーボモーター制御を実装するためのアセットです。D4Uは、様々なDynamixelサーボモーターと効率的かつ容易に接続し、制御し、データを読み取るための多くのメソッドを提供します。

具体的には、D4Uは以下の機能セットに焦点を当てています:

  • サーボモーターの制御テーブルを自動的に読み込む
  • 複数のサーボモーターモデルを同時に制御できる
  • シンプルな宣言型のコードでDynamixelサーボモーターを制御できる
  • 低レベルのサーボモーターを抽象化し、サーボモーター制御を簡素化するための多くのクラスを提供する
  • 複数のモデルの一括制御や単位変換などの高度な機能を有効にする
  • Unity3D内での低レベルの通信とサーボモーター制御を提供する(必要な場合)

が可能になります。

そのほかにも複数モデルの一括制御、単位換算など便利な関数が含まれて、ユーザーはUnity3D内から完全にロボット制御コードの記述に集中でき、C#で簡単に統合できるシンプルなコードを使用して、さまざまなアプリケーション、ゲーム、シミュレーション、または他の経験やユースケースに統合できます。これらはUnity3Dを活用します。

対応OS

  • Windows

対応プロトコル

対応Dynamixelモデル

DYNAMIXEL Protocol 2.0に対応するモデル。

  • MX-28
  • MX-64
  • MX-106
  • X Series (2X Series included)
  • PRO Series
  • P Series

2. インストール

利用したいUnityプロジェクトが開かれている状態で、DynamixelForUnity.unitypackageをダブルクリック、もしくは利用したいUnityプロジェクトのProjectウィンドウにドラッグアンドドロップします。

すべて選択された状態でImportしてください。

3. ライセンス購入とアクティベーション

完全な体験を得るためには、HatsuMuv からライセンスを購入する必要があります。ライセンスなしでは、Unityの実行ごとに10回の操作しか行えません。

購入後、以下のフォームへのリンクが提供されます。フォームでは、デバイスIDなどの登録情報を提供する必要があります。デバイスIDは、ライセンスを使用するマシンにD4Uを関連付けるために使用される固有のID番号です。
License Registration Form

デバイスIDを生成し、上記のフォームに挿入するには、ツールバーでGet DeviceID for Registrationをクリックします。これにより、デバイスIDが自動的にクリップボードにコピーされ、コンソールウィンドウに表示されます。以下のスクリーンショットに示されている指示に従ってください:

Getting Device ID

D4Uを購入した後、SN(シリアルナンバー)がメールで送られてきます。D4U内でSNを使用するには、次のようにD4Uの検証メソッドにメールとSNを挿入するか、Unity3DのUI内の「Dynamixel For Unity Component」に挿入してください:

アクティベーションを確認するには、シーンを実行し、コンソールでアクティベーションが正しく行われたかどうかを確認できます。

4. クイックスタート

ここでは、2つのDynamixelサーボモーターの位置の取得および設定をするデモを紹介します。

新規シーンの作成

  1. 新しいシーンを作成し、HatsuMuv/DynamixelForUnity/Prefabs/DynamixelForUnityをヒエラルキーウィンドウにドラッグアンドドロップします。

  1. ヒエラルキーメニューでDynamixelForUnityゲームオブジェクトを選択します。

  1. インスペクターウィンドウで、DynamixelForUnityコンポーネントに、メールアドレスSNコードを、デバイス名フィールドには、Dynamixelサーボモーターに接続されたUSBシリアルポートを入力します(ポート名はデバイスマネージャーから取得できます 例:「COM3」)。

  2. (オプション)操作するサーボモーターのボーレートを知っている場合は、事前にDynamixelForUnityのインスペクターでボーレートを設定します。分からない場合は空白のままにします。(後に記述するC#スクリプトのDynamixelForUnityのSetUpメソッドにてスキャンされますが、これには時間がかかることがあります)。

  3. 「コンポーネントの追加」ボタンをクリックし、スクリプト名を任意のもの(今回は「Demo」)と入力します。それから「新しいスクリプト」をクリックします。

  1. グレーの「Demo」スクリプトをダブルクリックします。しばらくするとVisualStudioが起動されます。

C#スクリプトのコーディング

Visual Studioが起動しtら、以下のように書きます。このコードは、2つのサーボモーターの現在位置をDebug.Logに出力し、その後、それぞれのGoalPositionを1024に設定します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using HatsuMuv.DynamixelForUnity.x64;

public class Demo : MonoBehaviour
{
    DynamixelForUnity d4u;
    
    void Start()
    {
        d4u = GetComponent<DynamixelForUnity>();
        d4u.ConnectDynamixel();
        d4u.SetUp();
       
        // Set operating mode to Position Control Mode
        d4u.SyncSetDataGroupMultiModels("OperatingMode", new uint[] { 3, 3 });
        
        // Torque enable
        d4u.SyncSetDataGroupMultiModels("TorqueEnable", new uint[] { 1, 1 });
        
        // Get and print present position
        int[] presentPosition = d4u.SyncGetSignedDataGroupMultiModels("PresentPosition");
        Debug.Log("PresentPosition: " + string.Join(", ", presentPosition));
    
        // Set goal position
        d4u.SyncSetDataGroupMultiModels("GoalPosition", new uint[] { 1024, 1024 });
        
    }
    
    void OnApplicationQuit()
    {
        // Torque disable
        uint[] torqueEnableCommand = new uint[] { 0, 0 };
        d4u.SyncSetDataGroupMultiModels("TorqueEnable", torqueEnableCommand);    
    }
}

実行

Unity画面に戻り、再生ボタンをクリックしてください。

サーボモーターの位置はコンソールに表示されます。

5. Script Reference

名前空間は

// 対応するシステム(OS)から以下の名前空間の中から一つを選択してください
HatsuMuv.DynamixelForUnity.x64
HatsuMuv.DynamixelForUnity.x86

から、お客様ののシステムに対応環境から選んでください。

DynamixelForUnity

DynamixelForUnityは宣言的なコードでDynamixelモーターを制御できるよう提供されるクラスです。一つのポートにつき一つのコンポーネントで管理します。IControlTableUserを実装します。

使用の流れ

  1. ConnectDynamixelでポートを開く。
  2. (オプション)SetUpもしくはSetIDsForUseで使用するモーターIDとモデル番号を保持する。この手順を踏むことで、データフィールド名だけで命令を送信することや、すべてのモーターへの命令を送信する際に簡潔に書くことができます。
  3. SetDataなどのメソッドでモーターを制御。OperatingModeなどのEEPROM領域のデータはトルクがOFF状態でないと書き込めないことに注意してください。
  4. 終了時はOnApplicationQuitもしくはOnDisable内でトルク無効化などの終了処理を行って下さい。DynamixelForUnityはOnDestroyが呼ばれたタイミングで接続されていればDisconnectDynamixelします。

定数

定数 説明
PROTOCOL_VERSION 2 DynamixelForUnityはプロトコルバージョン2.0にのみ対応。

変数

変数名 説明
showMessage これをtrueにするとDynamixelForUnityのすべての実行ログが
Debug.Logに出力される。エラーや警告は影響されない。

プロパティ

プロパティ名 説明
ControlTables 複数のモデルのcontrolTableを格納したオブジェクト。データフィールド名から命令を送信する関数はこれを参照する。
BaudRate 現在のボーレートを返す。読み取り専用。
IDsForUse DynamixelForUnityに保持されているID配列。SetUpメソッドで設定される。読み取り専用。
ModelNumbers Dictionary<byte, int>型。保持されているIDのモーターのモデル番号を表す。SetIDsForUseメソッド、SetUpメソッドで設定される。読み取り専用。
ConnectStatus モーターとの接続の有無を表す真偽値。読み取り専用。
Varification ライセンスが認証されているかを表す真偽値。読み取り専用。

Verificate

  • 宣言
    public bool Verificate(HatsuVerif verif)

  • 引数

    HatsuVerif verif
    検証オブジェクト。ライセンスファイルのパスと検証情報から作成する。
  • 返り値
    ライセンスの有効性。

  • 説明

使用前にこのメソッドを実行し、ライセンスを検証してください。

ConnectDynamixel

  • 宣言
    public bool ConnectDynamixel()
    public bool ConnectDynamixel(string deviceName)
    public bool ConnectDynamixel(BaudRate baudRate)
    public bool ConnectDynamixel(string deviceName, BaudRate baudRate)

  • 引数

    string deviceName
    接続するポート名。ex.)"COM3"
    BaudRate baudRate
    接続するボーレート。
  • 返り値
    接続に成功すればtrueを、そうでなければfalseを返す。

  • 説明
    指定されたボーレートでポートを開き、Dynamixelモーターと接続する。ポートまたはボーレートの指定がなければ、インスペクタで指定された値を用いる。

ScanInBaudRate

  • 宣言
    public byte[] ScanInBaudRate(BaudRate baudRate, byte[] idsForScan = null)

  • 引数

    BaudRate baudRate
    スキャンに用いるボーレート。
    byte[] idsForScan
    スキャンを行う対象のID。特定のIDのモーターだけ探す際に利用する。nullが指定されるとすべてのIDをスキャンする。
  • 返り値
    利用可能なモーターIDのbyte配列。

  • 説明
    指定されたボーレートでPingを打ち、Dynamixelモーターをスキャンする。SetBaudRateメソッドを用いて指定されたボーレートに変更する。


  • 宣言
    public byte[] ScanInBaudRate(BaudRate baudRate, byte minIDsForScan, byte maxIDsForScan)

  • 引数

    byte minIDsForScan
    スキャンを行う対象のIDを範囲の下限。(範囲に含む)
    byte maxIDsForScan
    スキャンを行う対象のIDを範囲の上限。(範囲に含む)
  • 返り値
    利用可能なモーターIDのbyte配列。

  • 説明
    指定されたボーレートでPingを打ち、Dynamixelモーターをスキャンする。SetBaudRateメソッドを用いて指定されたボーレートに変更する。スキャン対象を範囲で指定することができる。

ScanInBaudRateAsync

  • 宣言
    public async Task<byte[]> ScanInBaudRateAsync(BaudRate baudRate, byte[] idsForScan = null, CancellationToken cancellationToken = default(CancellationToken))

  • 引数

    BaudRate baudRate
    スキャンに用いるボーレート。
    byte[] idsForScan
    スキャンを行う対象のID。特定のIDのモーターだけ探す際に利用する。nullが指定されるとすべてのIDをスキャンする。
    CancellationToken cancellationToken
    処理をキャンセルするために使用。
  • 返り値
    利用可能なモーターIDのbyte配列。

  • 説明
    非同期的に指定されたボーレートでPingを打ち、Dynamixelモーターをスキャンする。SetBaudRateメソッドを用いて指定されたボーレートに変更する。


  • 宣言
    public async Task<byte[]> ScanInBaudRateAsync(BaudRate baudRate, byte minIDsForScan, byte maxIDsForScan, CancellationToken cancellationToken = default(CancellationToken))

  • 引数

    byte minIDsForScan
    スキャンを行う対象のIDを範囲の下限。(範囲に含む)
    byte maxIDsForScan
    スキャンを行う対象のIDを範囲の上限。(範囲に含む)
    CancellationToken cancellationToken
    処理をキャンセルするために使用。
  • 返り値
    利用可能なモーターIDのbyte配列。

  • 説明
    非同期的に指定されたボーレートでPingを打ち、Dynamixelモーターをスキャンする。SetBaudRateメソッドを用いて指定されたボーレートに変更する。スキャン対象を範囲で指定することができる。

Ping

  • 宣言
    public bool Ping(byte ID, bool hideErrorMsg = true)

  • 引数

    byte ID
    Pingを打つ対象のID。
    bool hideErrorMsg
    接続失敗時のエラー出力を無効にする。
  • 返り値
    接続されているか。

  • 説明
    指定されたIDにPingを打ち、接続されているか確認する。

PingGetModelNumber

  • 宣言
    public int PingGetModelNumber(byte ID)

  • 引数

    byte ID
    Pingを打つ対象のID。
  • 返り値
    モデル番号。通信失敗時には-1を返す。

  • 説明
    指定されたIDにPingを打ち、モデル番号を取得する。

GetAllModelNumber

  • 宣言
    public int[] GetAllModelNumber(byte[] ids = null)

  • 引数

    byte[] ids
    モデル番号を得る対象のID。nullが指定されるとDynamixelForUnityに保持されているIDが対象になる。
  • 返り値
    モデル番号の配列。通信失敗した項目には-1が入れられる。

  • 説明
    指定された複数のIDにPingを打ち、モデル番号の配列を返す。

SetControlTable

  • 宣言
    public void SetControlTable(ControlTables controlTables)

  • 引数

    ControlTables controlTables
    モデル番号を得る対象のID。nullが指定されるとDynamixelForUnityに保持されているIDが対象になる。
  • 説明
    IControlTableUserの実装。コントロールテーブルをセットする。

SetUp

  • 宣言
    public byte[] SetUp(BaudRate? baudRate = null, byte[] idsForScan = null, bool scanAllBaudRateIfNotFound = true)

  • 引数

    BaudRate? baudRate
    最初にスキャンするボーレート。nullが指定されるとInspectorなどであらかじめ設定されたBaudRateが使用される。
    byte[] idsForScan
    セットアップ対象のID。nullが指定されるとすべてのIDを対象にスキャンを実行する。
    bool scanAllBaudRateIfNotFound
    最初にスキャンしたボーレートでDynamixelモーターが見つからなければすべてのボーレートでスキャンする。
  • 返り値
    利用可能なモーターIDのbyte配列。

  • 説明
    ScanInBaudRateメソッドでdynamixelモーターをスキャンし、SetIDsForUseメソッドでスキャンしたIDを使用するモーターとして保持する。

SetUpAsync

  • 宣言
    public async Task<byte[]> SetUpAsync(BaudRate? baudRate = null, byte[] idsForScan = null, bool scanAllBaudRateIfNotFound = true, CancellationToken cancellationToken = default(CancellationToken))

  • 引数

    BaudRate? baudRate
    最初にスキャンするボーレート。nullが指定されるとInspectorなどであらかじめ設定されたBaudRateが使用される。
    byte[] idsForScan
    セットアップ対象のID。nullが指定されるとすべてのIDを対象にスキャンを実行する。
    bool scanAllBaudRateIfNotFound
    最初にスキャンしたボーレートでDynamixelモーターが見つからなければすべてのボーレートでスキャンする。
    CancellationToken cancellationToken
    処理をキャンセルするために使用。
  • 返り値
    利用可能なモーターIDのbyte配列。

  • 説明
    非同期的にScanInBaudRateAsyncメソッドでdynamixelモーターをスキャンし、SetIDsForUseメソッドでスキャンしたIDを使用するモーターとして保持する。

SetIDsForUse

  • 宣言
    public void SetIDsForUse(byte[] idsForUse)

  • 引数

    byte[] idsForUse
    対象のID。
  • 説明
    IDを利用対象として保持し、モデル番号と関連付ける。

モーターIDとモデル番号をDynamixelForUnityに保持させることで、データフィールド名だけで命令を送信することや、すべてのモーターへの命令を送信する際に簡潔に書くことができます。

DisconnectDynamixel

  • 宣言
    public void DisconnectDynamixel()

  • 説明
    ポートを閉じる。

CloseAndReopenPort

  • 宣言
    public void CloseAndReopenPort(bool withSetBaudRate = true)

  • 引数

    bool withSetBaudRate
    直前のボーレートに再び設定する。
  • 説明
    ポートを閉じ、開き直す。

RebootDynamixel

  • 宣言
    public void RebootDynamixel(byte[] ids = null)

  • 引数

    byte[] idsForScan = null
    対象のID。nullが指定されるとDynamixelForUnityに保持されているIDが対象になる。
  • 説明
    DynamixelモーターをRebootする。

FactoryResetDynamixel

  • 宣言
    public void FactoryResetDynamixel(DynamixelFactoryResetMode mode, byte[] ids = null)

  • 引数

    DynamixelFactoryResetMode mode
    すべての値をリセットするか、ID 以外のすべての値をリセットするか、ID とボーレート以外のすべての値をリセットするかを選択する。
    byte[] idsForScan
    対象のID。nullが指定されるとDynamixelForUnityに保持されているIDが対象になる。
  • 説明
    Dynamixelモーターを工場出荷時の状態にリセットする。

SetBaudRate

  • 宣言
    public bool SetBaudRate(BaudRate baudRate)

  • 引数

    BaudRate baudRate
    対象のボーレート。
  • 返り値
    成功すればtrueを、そうでなければfalseを返す。

  • 説明
    ポートのボーレートを設定する。

SetData

  • 宣言
    public bool SetData(ushort ADDR, ushort LEN, uint data, byte id)

  • 引数

    ushort ADDR
    書き込むデータフィールドのアドレス。モデルのControl Tableを参照。
    ushort LEN
    書き込むデータフィールドのサイズ。モデルのControl Tableを参照。
    uint data
    書き込むデータ。
    byte id
    対象のID。
  • 返り値
    成功すればtrueを、そうでなければfalseを返す。

  • 説明
    指定されたIDのDynamixelモーターにデータを書き込む。

データフィールドのアドレスと長さはの各Dynamitelモーターのe-ManualからContorol Tableを確認してください。


  • 宣言
    public bool SetData(ControlTableAddressInfo addressInfo, uint data, byte id)

  • 引数

    ControlTableAddressInfo addressInfo
    Control Tableの各データフィールドのアドレス、長さ、アクセスを持つ構造体。
    uint data
    書き込むデータ。
    byte id
    対象のID。
  • 返り値
    成功すればtrueを、そうでなければfalseを返す。

  • 説明
    SetData(ushort ADDR, ushort LEN, uint data, byte id)を使用して、指定されたIDのDynamixelモーターにデータを書き込む。データフィールドが読み取り専用であれば実行されない。


  • 宣言
    public bool SetData(string dataName, uint data, byte id)

  • 引数

    string dataName
    Control Tableのデータフィールド名
    uint data
    書き込むデータ。
    byte id
    対象のID。
  • 返り値
    成功すればtrueを、そうでなければfalseを返す。

  • 説明
    SetData(ushort ADDR, ushort LEN, uint data, byte id)を使用して、指定されたIDのDynamixelモーターにデータを書き込む。データフィールドが読み取り専用であれば実行されない。

SetUpメソッドもしくはSetIDsForUseメソッドによってIDとモデル番号が保持されていなければ使用できません。

SetSignedData

  • 宣言
    public bool SetSignedData(ushort ADDR, ushort LEN, uint data, byte id)
    public bool SetSignedData(ControlTableAddressInfo addressInfo, int data, byte id)
    public bool SetSignedData(string dataName, int data, byte id)

  • 説明
    指定されたIDのDynamixelモーターに符号付きデータを書き込む。SetDataの説明を参照してください。

GetData

  • 宣言
    public uint GetData(ushort ADDR, ushort LEN, byte id)

  • 引数

    ushort ADDR
    書き換えるデータフィールドのアドレス。モデルのControl Tableを参照。
    ushort LEN
    書き換えるデータフィールドのサイズ。モデルのControl Tableを参照。
    byte id
    対象のID。
  • 返り値
    読み取ったデータ。エラーが発生した場合0を返す。

  • 説明
    指定されたIDのDynamixelモーターのデータを読み取る。

データフィールドのアドレスと長さはの各Dynamitelモーターのe-ManualからContorol Tableを確認してください。


  • 宣言
    public uint GetData(ControlTableAddressInfo addressInfo, byte id)

  • 引数

    ControlTableAddressInfo addressInfo
    Control Tableの各データフィールドのアドレス、長さ、アクセスを含む構造体。
    byte id
    対象のID。
  • 返り値
    読み取ったデータ。エラーが発生した場合0を返す。

  • 説明
    GetData(ushort ADDR, ushort LEN, byte id)を使用して、指定されたIDのDynamixelモーターのデータを読み取る。


  • 宣言
    public uint GetData(string dataName, byte id)

  • 引数

    string dataName
    Control Tableのデータフィールド名
    byte id
    対象のID。
  • 返り値
    読み取ったデータ。エラーが発生した場合0を返す。

  • 説明
    GetData(ushort ADDR, ushort LEN, byte id)を使用して、指定されたIDのDynamixelモーターのデータを読み取る。

SetUpメソッドもしくはSetIDsForUseメソッドによってIDとモデル番号が保持されていなければ使用できません。

GetSignedData

  • 宣言
    public int GetSignedData(ushort ADDR, ushort LEN, byte id)
    public int GetSignedData(ControlTableAddressInfo addressInfo, byte id)
    public int GetSignedData(string dataName, byte id)

  • 説明
    指定されたIDのDynamixelモーターの符号付きデータを読み取る。GetDataの説明を参照してください。

SyncSetDataGroup

  • 宣言
    public bool SyncSetDataGroup(ushort ADDR, ushort LEN, uint[] data, byte[] ids = null)
    public bool SyncSetDataGroup(ControlTableAddressInfo addressInfo, uint[] data, byte[] ids = null)

  • 引数

    ushort ADDR
    書き換えるデータフィールドのアドレス。モデルのControl Tableを参照。
    ushort LEN
    書き換えるデータフィールドのサイズ。モデルのControl Tableを参照。
    ControlTableAddressInfo addressInfo
    Control Tableの各データフィールドのアドレス、長さ、アクセスを含む構造体。
    uint[] data
    書き込むデータ配列。
    byte[] ids = null
    対象のID。nullが指定されるとDynamixelForUnityに保持されているIDが対象になる。
  • 返り値
    成功すればtrueを、そうでなければfalseを返す。

  • 説明
    GroupSyncWrite用いて複数モーターに同時にデータを書き込む。

SyncSetDataGroupMultiModels

  • 宣言
    public bool SyncSetDataGroupMultiModels(string dataName, uint[] data, byte[] ids = null)

  • 説明
    GroupSyncWrite用いて複数モーターに同時にデータを書き込む。データフィールド名が同一であれば、異なるアドレスでも送信する。その場合、ユニークなアドレス毎にSyncSetDataGroupを呼び出す。SyncSetDataGroupの説明を参照してください。

SetUpメソッドもしくはSetIDsForUseメソッドによってIDとモデル番号が保持されていなければ使用できません。

SyncSetSignedDataGroup

  • 宣言
    public bool SyncSetSignedDataGroup(ushort ADDR, ushort LEN, int[] data, byte[] ids = null)
    public bool SyncSetSignedDataGroup(ControlTableAddressInfo addressInfo, int[] data, byte[] ids = null)

  • 説明
    GroupSyncWrite用いて複数モーターに同時に符号付きデータを書き込む。SyncSetDataGroupの説明を参照してください。

SyncSetSignedDataGroupMultiModels

  • 宣言
    public bool SyncSetSignedDataGroupMultiModels(string dataName, int[] data, byte[] ids = null)

  • 説明
    GroupSyncWrite用いて複数モーターに同時に符号付きデータを書き込む。SyncSetDataGroupMultiModelsの説明を参照してください。

SetUpメソッドもしくはSetIDsForUseメソッドによってIDとモデル番号が保持されていなければ使用できません。

SyncGetDataGroup

  • 宣言
    public uint[] SyncGetDataGroup(ushort ADDR, ushort LEN, byte[] ids = null)
    public uint[] SyncGetDataGroup(ControlTableAddressInfo addressInfo, byte[] ids = null)

  • 引数

    ushort ADDR
    読み取るデータフィールドのアドレス。モデルのControl Tableを参照。
    ushort LEN
    読み取るデータフィールドのサイズ。モデルのControl Tableを参照。
    ControlTableAddressInfo addressInfo
    Control Tableの各データフィールドのアドレス、長さ、アクセスを含む構造体。
    byte[] ids = null
    対象のID。nullが指定されるとDynamixelForUnityに保持されているIDが対象になる。
  • 返り値
    読み取ったデータ配列。

  • 説明
    GroupSyncRead用いて複数モーターから同時にデータを読み取る。

SyncGetDataGroupMultiModels

  • 宣言
    public uint[] SyncGetDataGroupMultiModels(string dataName, byte[] ids = null)

  • 説明
    GroupSyncReadを用いて複数モーターに同時にデータを読み取る。データフィールド名が同一であれば、異なるアドレスでも送信する。その場合、ユニークなアドレス毎にSyncSetDataGroupを呼び出す。SyncGetDataGroupの説明を参照してください。

SetUpメソッドもしくはSetIDsForUseメソッドによってIDとモデル番号が保持されていなければ使用できません。

SyncGetSignedDataGroup

  • 宣言
    public int[] SyncGetSignedDataGroup(ushort ADDR, ushort LEN, byte[] ids = null)
    public int[] SyncGetSignedDataGroup(ControlTableAddressInfo addressInfo, byte[] ids = null)

  • 説明
    GroupSyncReadを用いて複数モーターに同時に符号付きデータを読み取る。SyncGetDataGroupの説明を参照してください。

SyncGetSignedDataGroupMultiModels

  • 宣言
    public int[] SyncGetSignedDataGroupMultiModels(string dataName, byte[] ids = null)

  • 説明
    GroupSyncReadを用いて複数モーターに同時に符号付きデータを読み取る。SyncGetDataGroupMultiModelsの説明を参照してください。

SetUpメソッドもしくはSetIDsForUseメソッドによってIDとモデル番号が保持されていなければ使用できません。

BulkSetDataGroup

  • 宣言
    public BulkWritePacket BulkSetDataGroup()

  • 返り値
    BulkWritePacketオブジェクト。

  • 説明
    GroupBulkWriteを用いて複数モーターに一括でデータを書き込むためのBulkWritePacketオブジェクトを返す。

GroupBulkWriteを行うにはBulkWritePacketを媒介するメソッドチェーンで記述します。

BulkGetDataGroup

  • 宣言
    public BulkReadPacket BulkGetDataGroup()

  • 返り値
    BulkReadPacketオブジェクト。

  • 説明
    GroupBulkReadを用いて複数モーターに一括でデータを読み取るためのBulkReadPacketオブジェクトを返す。

GroupBulkReadを行うにはBulkReadPacketを媒介するメソッドチェーンで記述します。

コントロールテーブルの使用方法

各Dynamixelサーボモーターのコントロールテーブルを表すテキストファイルを作成しました。コントロールテーブルは、各プロパティ名を対応するサーボモーターモデル内のレジスタ番号にマッピングしています。

これらのコントロールテーブルは、ROBOTIS e-Manualにあるサーボモーターのコントロールテーブルに対応しています。

以下の場所でコントロールテーブルのデータを確認できます: HatsuMuv/DynamixelForUnity/Resources/ControlTables/。

スクリーンショットのように、対応するサーボモーターを見つけてください:
ControlTable

これらのコントロールテーブルは、D4Uのさまざまなメソッドのパラメータとして使用できます。パラメータ名を文字列として渡すことで、さまざまなサーボモーターの制御やフィードバックを行うことができます。
例:

// 以下のメソッドでコントロールテーブルのパラメータを読み取るためにコントロールテーブルのパラメータを渡すことができます。ここで、コントロールテーブルのパラメータは設定/読み取りする必要のあるレジスタの文字列表現に対応します。

// この例では、SyncGetDataGroupMultiModelsメソッドを使用してデータを一括で設定しています。
SyncGetDataGroupMultiModels("GoalPosition");

BulkWritePacketの使用方法

Bulk Writeコマンドの実行には、次の手順が含まれます:

  1. Bulk Writeパラメータを保持するための構造体を初期化する。
  2. 構造体にパラメータを追加する。
  3. Bulk Writeコマンドを送信する。
  4. (オプション)コマンドの実行結果を読み取る。

上記の4つのステップを実行するために、BulkWritePacketオブジェクトが使用されます。以下で説明されているように、そのプロパティを設定し、メソッドを呼び出すことでこれらの手順を実行できます。

ステップ1. 構造体の初期化

書き込みパケットのパラメータを保持するための構造体を初期化するために、まず関連するBulkWritePacketオブジェクトを宣言して初期化する必要があります。以下のように行います:

// bulk write packet
BulkWritePacket writePacket = dynamixelForUnity.BulkSetDataGroup();

ステップ2. パラメータの追加

BulkWritePacketオブジェクトを作成すると、書き込むデータはパラメータとして作成したオブジェクトに追加する必要があります。以下のように行います:

writePacket.AddParam(addressInfo1, idArray1, data1);
// 必要なだけパラメータを追加できます
writePacket.AddParam(addressInfo2, idArray2, data2);
writePacket.AddParam(addressInfo3, idArray3, data3);

ステップ3. バルクライトコマンドの送信

パラメータを追加したら、BulkWritePacketオブジェクトのSend()メソッドを呼び出すことでコマンドを実行できます。以下のように行います:

writePacket.Send();

ステップ4. (オプション)BulkWriteコマンドの実行結果の確認

コマンドのステータスを確認することもできます。コマンドが正常に実行されたかどうかを確認するために、プロパティ「AllSuccess」をチェックできます。これはコマンドが成功した場合にtrueに設定されます。

Csharp!
if(writePacket.AllSuccess)
    Debug.Log("成功");

また、コマンドが成功したかどうかを確認するために、BulkPacketオブジェクト自体を’if’条件文内に直接配置することもできます。

if(writePacket)
    Debug.Log("成功");

全体的に、以下のようにして効率的に4つのステップを1行で実行できます:

if(dynamixelForUnity
    .BulkSetDataGroup()
    .AddParam(addressInfo1, idArray1, data1)
    .AddParam(addressInfo2, idArray2, data2)
    .AddParam(addressInfo3, idArray3, data3)
    .Send())
    Debug.Log("成功");

GroupBulkWriteを用いて複数モーターに一括でデータを書き込むためのオブジェクト。BulkWritePacketを媒介するメソッドチェーンで記述する。

dynamixelForUnity.BulkSetDataGroup()
    .AddParam(addressInfo1, idArray1, data1)
    .AddParam(addressInfo2, idArray2, data2)
    .Send();

プロパティ

プロパティ名 説明
IDParamPair Dictionary<byte, ParamInfo>型。各IDと書き込むデータフィールドの情報を保持。ParamInfoはアドレス、長さ、成功したか、送信するデータを保持する。読み取り専用。
AllSuccess 全てのパラメータの通信が成功したかを表す真偽値。読み取り専用。
HasSent コマンドが送信されたかを表す真偽値。読み取り専用。

AddParam

  • 宣言
    public BulkWritePacket AddParam(ushort ADDR, ushort LEN, uint[] data, byte[] ids = null)

  • 引数

    ushort ADDR
    書き込むデータフィールドのアドレス。モデルのControl Tableを参照。
    ushort LEN
    書き込むデータフィールドのサイズ。モデルのControl Tableを参照。
    uint[] data
    書き込むデータ。
    byte[] id
    対象のID。nullが指定されるとDynamixelForUnityに保持されているIDが対象になる。
  • 返り値
    自身。

  • 説明
    書き込むデータフィールドの情報とデータをIDParamPairに格納する。


  • 宣言
    public BulkWritePacket AddParam(ControlTableAddressInfo addressInfo, uint[] data, byte[] ids = null)

  • 引数

    ControlTableAddressInfo addressInfo
    Control Tableの各データフィールドのアドレス、長さ、アクセスを含む構造体。
    uint[] data
    書き込むデータ。
    byte[] id
    対象のID。nullが指定されるとDynamixelForUnityに保持されているIDが対象になる。
  • 返り値
    自身。

  • 説明
    AddParam(ushort ADDR, ushort LEN, uint[] data, byte[] ids = null)を使用して書き込むデータフィールドの情報とデータをIDParamPairに格納する。データフィールドが読み取り専用であれば実行されない。

AddParamSigned

  • 宣言
    public BulkWritePacket AddParamSigned(ushort ADDR, ushort LEN, int[] data, byte[] ids = null)
    public BulkWritePacket AddParamSigned(ControlTableAddressInfo addressInfo, int[] data, byte[] ids = null)

  • 説明
    書き込むデータフィールドの情報と符号付きデータをIDParamPairに格納する。AddParamの説明を参照してください。

Send

  • 宣言
    public BulkWritePacket Send(bool discardAllWhenErrorHappen = false)

  • 引数

    bool discardAllWhenErrorHappen = false
    tureにすると、AddParam操作の間にエラーが発生した場合にコマンドを送信せずに処理を中断する。
  • 返り値
    自身。

  • 説明
    IDParamPairに基づいてGroupBulkWriteコマンドをDynamxielモーターに送信する。

ResetStatus

  • 宣言
    public void ResetStatus()

  • 説明
    通信結果と送信フラグをリセットする。書き込むデータは削除されない。

BulkReadPacketの使用方法

bulk readの実行には以下のステップが含まれます:

  1. bulk readのパラメータを保持するための構造体を初期化します。
  2. 構造体にパラメータを追加します。
  3. bulk readコマンドを送信します。
  4. (オプション)コマンドの実行結果を読み取ります。
  5. サーボモーターからの返された値を読み取ります。

上記の4つのステップを実行するためには、BulkReadPacketオブジェクトを使用してそのプロパティを設定し、下記で説明するようにメソッドを呼び出す必要があります。

ステップ1. 構造体の初期化

読み取りパケットのパラメータを保持するための構造体を初期化するには、まず関連するBulkReadPacketオブジェクトを次のように宣言して初期化します:

// bulk read packet
BulkReadPacket readPacket = dynamixelForUnity.BulkGetDataGroup();

ステップ2. パラメータの追加

BulkReadPacketオブジェクトを作成したら、読み取るデータをオブジェクトにパラメータとして追加する必要があります。以下のように行えます:

readPacket.AddParam(addressInfo1, idArray1);
// 必要なだけパラメータを追加できます
readPacket.AddParam(addressInfo2, idArray2);
readPacket.AddParam(addressInfo3, idArray3);

ステップ3. バルクリードコマンドの送信

パラメータを追加したら、BulkReadPacketオブジェクトのSend()メソッドを呼び出すことでコマンドを実行できます:

readPacket.Send();

ステップ4. (オプション)バルクリードコマンド実行の結果の確認

コマンドのステータス、つまり正常に実行されたかどうかをオプションで確認できます。プロパティ"AllSuccess"をチェックすることで、コマンドが正常に実行された場合にtrueに設定されます。

if(readPacket.AllSuccess)
    Debug.Log("成功");

代わりに、直接BulkPacketオブジェクトを「if」条件式に配置して、コマンドが成功したかどうかを確認することもできます。

if(readPacket)
    Debug.Log("成功");

ステップ5. サーボモーターからの返された値の読み取り

バルクリードコマンドを実行した後にサーボモーターから読み取ったデータを読むには、BulkReadPacketのGetData()メソッドを以下のように使用します:

// サーボモーターからの結果を格納するためのuint配列を作成します
uint[] data = readPacket.GetData();

全体的に、以下のようにしてステップを実行し、結果を表示できます:
例:

if(var readPacket = dynamixelForUnity
    .BulkGetDataGroup()
    .AddParam(addressInfo1, idArray1)
    .AddParam(addressInfo2, idArray2)
    .AddParam(addressInfo3, idArray3)
    .Send())
{
    Debug.Log("成功");
    uint[] data = readPacket.GetData();
    Debug.Log("データ:" + string.Join(", ", data));
}

プロパティ

プロパティ名 説明
IDParamPair Dictionary<byte, ParamInfo>型。各IDと読み取るデータフィールドの情報を持つ。ParamInfoはアドレス、長さ、成功したか、読み取ったデータを持つ。読み取り専用。
AllSuccess 全てのパラメータの通信が成功したかを表す真偽値。読み取り専用。
HasSent コマンドが送信されたかを表す真偽値。読み取り専用。

AddParam

  • 宣言
    public BulkReadPacket AddParam(ushort ADDR, ushort LEN, byte[] ids = null)

  • 引数

    ushort ADDR
    読み取るデータフィールドのアドレス。モデルのControl Tableを参照。
    ushort LEN
    読み取るデータフィールドのサイズ。モデルのControl Tableを参照。
    byte[] id
    対象のID。nullが指定されるとDynamixelForUnityに保持されているIDが対象になる。
  • 返り値
    自身。

  • 説明
    読み取るデータフィールドの情報とデータをIDParamPairに格納する。


  • 宣言
    public BulkReadPacket AddParam(ControlTableAddressInfo addressInfo, byte[] ids = null)

  • 引数

    ControlTableAddressInfo addressInfo
    Control Tableの各データフィールドのアドレス、長さ、アクセスを含む構造体。
    byte[] id
    対象のID。nullが指定されるとDynamixelForUnityに保持されているIDが対象になる。
  • 返り値
    自身。

  • 説明
    AddParam(ushort ADDR, ushort LEN, byte[] ids = null)を使用して読み取るデータフィールドの情報をIDParamPairに格納する。

Send

  • 宣言
    public BulkReadPacket Send(bool discardAllWhenErrorHappen = false)

  • 引数

    bool discardAllWhenErrorHappen = false
    tureにすると、AddParam操作の間にエラーが発生した場合にコマンドを送信せずに処理を中断する。
  • 返り値
    自身。

  • 説明
    IDParamPairに基づいてGroupBulkReadコマンドをDynamxielモーターに送信する。

GetData

  • 宣言
    public uint[] GetData(bool resetStatus = false)

  • 引数

    bool resetStatus
    trueにすると、終了時にResetStatusメソッドを呼びます。
  • 返り値
    読み取ったデータ配列。

  • 説明
    読み取ったデータ配列を取得する。

GetSignedData

  • 宣言
    public int[] GetSignedData(bool resetStatus = false)

  • 引数

    bool resetStatus
    trueにすると、終了時にResetStatusメソッドを呼びます。
  • 返り値
    読み取ったデータ配列。

  • 説明
    読み取った符号付きデータ配列を取得する。

ResetStatus

  • 宣言
    public void ResetStatus()

  • 説明
    通信結果と送信フラグをリセットする。読み取ったデータも削除する。

ParamInfo

BulkWritePacket、BulkReadPacketでデータフィールドを表すクラス。

変数

変数名 説明
address データのアドレス。
length データの長さ。
isSuccess 通信に成功したかどうか
data BulkWritePacketでは書き込むデータ。BulkReadPacketでは読み取ったデータ。

ControlTables

各モーターのControl Tableを格納するクラス。Control Tableは指定された形式で書かれたテキストをコンストラクタの引数に入れることで初期化します。コンストラクタはstring[]かTextAsset[]のどちらかを引数に取ります。

DynamxielForUnity対応モデルのControl TableのテキストファイルがAssets/HatsuMuv/DynamixelForUnity/Resources/ControlTablesに配置されています。
使用例はControlTableInjectorFromResourcesを参照してください。

ControlTablesは初期化時に利用するモーターのControlTableを。
1行目はモデル名とモデル番号を":“を挟んで記述します。
そのほかの行はデータ名、アドレス、長さ、アクセスを記述します。アクセスは読み取り専用ならば"R”、書き込みも可能であるなら"RW"を指定します。
"#“や”//"で開始される行はコメントとして無視されます。

<モデル名>:<モデル番号> <データ名>, <アドレス>, <長さ>, <アクセス> <データ名>, <アドレス>, <長さ>, <アクセス> ...

プロパティ

プロパティ名 説明
ModelNames モデル番号に対応するモデル名を表すDictionary<int, string> 型。

GetAddrssInfo

  • 宣言
    public ControlTableAddressInfo GetAddrssInfo(int modelNumber, string dataName)
    public ControlTableAddressInfo this[int modelNumber, string dataName]

  • 引数

    int modelNumber
    モデル番号。
    string dataName
    データ名。
  • 返り値
    データフィールドの情報を表すControlTableAddressInfo型。

  • 説明
    指定したモデル番号とデータ名からデータフィールドの情報を表すControlTableAddressInfo型を返す。
    インデクサー[int modelNubmer, string dataName]でも同様の結果を得られる。

GetControlTable

  • 宣言
    public Dictionary<string, ControlTableAddressInfo> GetControlTable(int modelNumber)
    public Dictionary<string, ControlTableAddressInfo> this[int modelNumber]

  • 引数

    int modelNumber
    対象のモデル番号。
  • 返り値
    コントロールテーブルを表すDictionary<string, ControlTableAddressInfo>型。

  • 説明
    指定されたモデル番号からコントロールテーブルを表すDictionary<string, ControlTableAddressInfo>型を返す。
    インデクサー[int modelNubmer]でも同様の結果を得られる。

Contains

  • 宣言
    public bool Contains(int modelNumber)

  • 引数

    int modelNubmer
    調べるモデル番号。
  • 返り値
    指定されたモデル番号のControl Tableが存在すればtrue、そうでなければfalseを返す。

  • 説明
    指定したモデル番号のControl Tableが存在するかを返す。


  • 宣言
    public bool Contains(int modelNumber, string dataName)

  • 引数

    int modelNubmer
    調べるモデル番号。
    string dataName
    調べるデータ名。
  • 返り値
    指定されたモデル番号のControl Tableが存在し、かつ指定されたデータ名のデータフィールドが存在すればtrueを、そうでなければfalseを返す。

  • 説明
    指定したモデル番号のデータ名のデータフィールドが存在するかを返す。

GetModelsContains

  • 宣言
    public int[] GetModelsContains(string dataName)

  • 引数

    string dataName
    調べるデータ名。
  • 返り値
    指定されたデータ名のデータフィールドをControl Tableに持つモデル番号の配列。

  • 説明
    指定されたデータ名のデータフィールドをControl Tableに持つモデル番号の配列を返す。

ControlTableAddressInfo

Control Tableにおけるデータフィールドの情報を表す構造体。

変数

変数名 説明
address データのアドレス。
length データの長さ。
readWrite 書き込み可能ならtrue、読み取り専用ならばfalse。

IControlTableUser

コントロールテーブルを注入するインターフェース。DynamixelForUnityが実装します。

SetControlTables

  • 宣言
    public void SetControlTables(ControlTables controlTables)

Core

Dynamixel SDK互換のクラス。コンストラクタにHatsuVerifオブジェクトを入れインスタンスを生成してください。

6.Example Sceneの解説

HatsuMuv/DynamixelForUnity/Example/ExampleScene_x64.unity はDynamixelForUnityを用いたGUIアプリケーションのサンプルです。

このシーンには、DynamixelForUnityを使用してDynamixelサーボモーターと接続、サーボモーターのスキャン、サーボモーターの制御、サーボモーターから読み取ったさまざまな情報のGUIへの表示を行う、サンプルのグラフィカルユーザーインターフェース(GUI)アプリケーションが含まれています。

画面の解説

  1. ヒエラルキーメニューでDynamixelForUnityゲームオブジェクトを選択します。

  1. インスペクターウィンドウで、DynamixelForUnityコンポーネントに、メールアドレスSNコードを、デバイス名フィールドには、Dynamixelサーボモーターに接続されたUSBシリアルポートを入力します(ポート名はデバイスマネージャーから取得できます 例:「COM3」)。

  2. (オプション)操作するサーボモーターのボーレートを知っている場合は、事前にDynamixelForUnityのインスペクターでボーレートを設定します。分からない場合は空白のままにします。(後に記述するC#スクリプトのDynamixelForUnityのSetUpメソッドにてスキャンされますが、これには時間がかかることがあります)。

DynamixelForUnityのコンポーネントの設定が完了したら、再生ボタンをクリックしてください。DynamixelForUnityは自動的に指定されたポートを開き、利用可能なサーボモーターに接続します。

画面には接続されたDynamixelサーボモーターの一覧が表示されます。接続されたボーレートは画面の上部に表示されます。

画面下部の読み取り専用のデータ項目は常に更新されています(現在位置など)。

その他の項目は、接続時とデータが入力された場合に更新されます(例えば、サーボモーターの新しい位置を設定する場合など)。

ヒント: 実際の値と異なる場合は、データを再取得するためにリフレッシュボタンを押してください。

実装の解説

D4UExampleController

Start()

private async void Start()
{
    NeedWait = true;
    
    if (d4u == null)
    {
        Debug.LogError("[DynamixelForUnitySample] DynamixelForUnity is not assigned!");
        return;
    }

    // Varificate your licence here
    d4u.Varificate(new DevTest.HatsuVarif(licenceFilePath: "some", userID: "some", email: "example@sample.com", phonenumber: "123456789"));

    cancellationTokenSource = new CancellationTokenSource();

    var connectResult = await Task.Run(() => InitializeDynamixel(cancellationTokenSource.Token));
    if (connectResult) StartMotorMetrics();

    NeedWait = false;
}

NeedWaitプロパティは画面ロードアニメーションを再生するために外部から参照されています。

DynamixelForUnity d4uはInspectorから代入されることを期待しています。

d4u.Varificateメソッドを用いてライセンスの認証を行ってください。

cancellationTokenSourceは非同期タスクを中断するために作成します。

モーターに接続し初期化をするメソッドInitializeDynamixelを非同期で実行しこれを待ち受けます。

InitializeDynamixelによってDynamixelモーターを接続できたならばStartMotorMetricsmメソッドによってモーターの状態を連続して計測し続けるループが開始されます。


InitializeDynamixel(CancellationToken ct)

private async Task<bool> InitializeDynamixel(CancellationToken ct)
{
    if (d4u == null)
    {
        Debug.LogError("[DynamixelForUnitySample] DynamixelForUnity is not assigned!");
        return false;
    }

    if (!d4u.ConnectStatus)
    {
        var connectResult = d4u.ConnectDynamixel();
        if (!connectResult)
        {
            Debug.LogError("[DynamixelForUnitySample] Dynamixel is not connected. Connect it first.");
            return false;
        }

        await d4u.SetUpAsync(cancellationToken:ct);
    }

    motors = new List<DynamixelMotor>();
    ids = d4u.IDsForUse;

    if (ids.Length == 0) return false;

    await GetAllMotorProperty(ct);
    return true;
}

d4u.ConnectDynamixelメソッドでモーターと接続します。引数にポート名、ボーレートを指定することもできます。その場合Inspectorで入力された値は無視されます。

SetUpAsyncメソッドは二つのメソッドを呼んで、モーターのスキャンと見つかったIDの保持を一括で行います。

  1. ScanInBaudRateAsyncメソッドを呼んで指定されたポート名、ボーレートでDynamixelモーターをスキャンします。モーターが見つからなかった場合、すべてのボーレートでスキャンを再実行し、最初にモーターが見つかったボーレートにポートを設定します。
  2. SetIDsForUseメソッドを呼んで見つかったモーターのIDとモデル番号を保持します。

モーターIDとモデル番号をDynamixelForUnityに保持させることで、データフィールド名だけで命令を送信できる、一部の関数ですべてのモーターへの命令を送信する際に簡潔に書くことができる、などの恩恵があります。

ExampleSceneを作る上で、モーターの情報を扱う際にDynamixelMotorクラスを実装しています。あなたの作成するアプリケーションに応じてプロパティを追加してください。

GetAllMotorPropertyメソッドによってすべてのモーターのプロパティを取得し、DynamixelMotorインスタンスを作成します。


GetAllMotorProperty(CancellationToken ct)

private async Task GetAllMotorProperty(CancellationToken ct)
{
    OperatingMode[] operatingModes = 
        await Task.Run(() =>
            d4u.SyncGetDataGroupMultiModels("OperatingMode")
                .Select(d => (OperatingMode)d)
                .ToArray(),
            ct);

    float[] homingOffsets = await Task.Run(() => d4u.SyncGetSignedDataGroupMultiModels("HomingOffset").ToActualNumber(UNIT.POSITION).ToFloatArray(), ct);
    ...

    for (int i = 0; i < ids.Length; i++)
    {
        DynamixelMotor m;
        if (motors.Count < i + 1) 
        {
            m = new DynamixelMotor(ids[i]);
            motors.Add(m);
        }
        else
        {
            m = motors[i];
        }
        m.OperatingMode = operatingModes[i];
        m.HomingOffset = homingOffsets[i];
        ...
    }
}

DynamixelMotorクラスの一つ一つのプロパティについてSyncGetSignedDataGroupMultiModelsメソッドで取得しています。一つのプロパティを例に挙げて説明します。

float[] homingOffsets = 
    await Task.Run(
        () => d4u.SyncGetSignedDataGroupMultiModels("HomingOffset")
            .ToActualNumber(UNIT.POSITION)
            .ToFloatArray(),
        ct
    );

homingOffsetsを初期化する行にわかりやすく改行を入れたものです。

2行目のTask.Runメソッドは第一引数の関数を別スレッドで非同期に実行するものです。

3行目からは別スレッドで実行したい関数です。

d4u.SyncGetSignedDataGroupMultiModels(“HomingOffset”)はDynamixelForUnityに保持されているすべてのモーターに対して"HomingOffset"というデータフィールドの値を取得します。

.ToActualNumber(UNIT.POSITION)はSyncGetSignedDataGroupMultiModelsから得られた生のデータに単位を適用します。UNIT.POSITIONは定数です。

.ToFloatArray()はdouble[]をfloat[]に変換します。

MotorMetricsはGetAllMotorPropertyからいくつかのデータをそぎ落とした同様のメソッドです。


StartMotorMetrics()

MotorMetricsLoop(CancellationToken ct)

StopMotorMetrics()

private void StartMotorMetrics()
{
    if (!d4u.ConnectStatus)
        return;

    cancellationTokenSource = cancellationTokenSource ?? new CancellationTokenSource();
    var cancellationToken = cancellationTokenSource.Token;
    metricsLoop = MotorMetricsLoop(cancellationToken);
}

private async Task MotorMetricsLoop(CancellationToken ct)
{
    while (true)
    {
        if (ct.IsCancellationRequested)
            return;

        Debug.Log("[SampleController] Metrics");
        try
        {
            await MotorMetrics(ct);
        }
        catch (OperationCanceledException)
        {
            Debug.Log("[SampleController] Metrics canceled");
            return;
        }
        catch (Exception e)
        {
            Debug.LogError(e);
            return;
        }
    }
}

private async Task StopMotorMetrics()
{
    if (cancellationTokenSource != null && !cancellationTokenSource.IsCancellationRequested)
    {
        cancellationTokenSource.Cancel();
        cancellationTokenSource.Dispose();
        cancellationTokenSource = null;

        if(metricsLoop != null)
            await metricsLoop;
    }
}

連続的にモーターのデータを計測し続けるループを実行するメソッド群です。MotorMetricsLoopはCancellationTokenによってキャンセルされるまでMotorMetricsを呼び出し続けます。
StopMotorMetricsはMotorMetricsをキャンセルし、処理が終了するまで待ち受けます。

D4UExampleUI

UI要素の生成を行います。
D4UExampleControllerからモーター毎のパネルを生成し、それらのデータを更新させます。

D4UExampleUIElement

各パネルのフィールドを直接コントロールしている。UI要素から入力を受けるとD4UExampleContorllerに値を渡す。