APIリファレンス

このリファレンスでは、contact_gloveパッケージとcontact_glove_msgsパッケージが提供するトピック、メッセージ型、ノードパラメータ、座標系の規約を説明します。


パッケージが提供する型とノード

contact_glove_msgs

SDKのデータ構造に対応するROS 2メッセージを定義します。

ファイルSDKの型説明
msg/GloveData.msgGloveDataデバイス情報:バッテリー残量(%)、外部給電、接続状態、デバイスの表示色設定など
msg/Ergonomics.msgErgonomicsData指の開き角と曲げ角。ThumbPose・FingerPoseのサブメッセージを含みます
msg/Skeleton.msgSkeletonData25関節の位置と回転。geometry_msgs/Transform[25] jointsとNODE_*定数
msg/RawSkeleton.msgRawSkeletonDataリターゲット前のソーススケルトン25関節の位置と回転。座標系はSkeletonと異なります
msg/RawSensorTransforms.msgRawSensorTransformsData5つのセンサーの位置と回転。各フィールドはgeometry_msgs/Transformです
msg/Controller.msgControllerDataボタンの状態とアナログ入力
msg/HapticsBody.msgVibrateGlove()周波数・振幅・持続時間を指定する振動コマンド
srv/GetRetargetProfile.srvRequestRetargetProfileInfo() / RetargetProfileInfoプロファイルUUIDから表示用のメタデータを同期的に問い合わせます

contact_glove

このパッケージは2つの実行ファイルを提供します。

実行ファイル説明
contact_glove_nodeSDKに接続し、グローブのデータをROS 2トピックとしてpublishします
contact_glove_rviz_node同じnamespaceのleft/skeletonとright/skeletonを購読し、左右それぞれのRViz2用MarkerArrayとTFをpublishします

メッセージ定義を確認する

フィールド・定数・型・コメントの正本は、インストールされたROS 2のメッセージ定義です。次のコマンドで確認できます。

ros2 interface show contact_glove_msgs/msg/GloveData
ros2 interface show contact_glove_msgs/msg/Ergonomics
ros2 interface show contact_glove_msgs/msg/Skeleton
ros2 interface show contact_glove_msgs/msg/RawSkeleton
ros2 interface show contact_glove_msgs/msg/RawSensorTransforms
ros2 interface show contact_glove_msgs/msg/Controller
ros2 interface show contact_glove_msgs/msg/HapticsBody
ros2 interface show contact_glove_msgs/srv/GetRetargetProfile

このドキュメントでは、個々のmsgファイルからは読み取れない、インターフェース全体の規約(トピックの対応、QoS、座標系、複数グローブの選択ルールなど)を説明します。


トピック一覧

どのトピックも、メッセージのidフィールドでグローブを、is_leftで左右を識別します。ストリーミング・制御・可視化のトピックはすべて <hand>(leftまたはright)のサブトピックを持つため、片手だけを自然に購読したりリマップしたりできます。トピック名はパッケージ名を含まない、ノード相対の単純な名前(left/... と right/...)です。

contact_glove_nodeは既定でcontact_glove namespaceで起動するため、実際の名前は /contact_glove/left/... と /contact_glove/right/... になります。namespace:=... で変更できます。1台のPCで複数のDivingStationやグローブのペアを扱うには、ノードごとに別のnamespaceを付けるか、launchファイルで <group><push-ros-namespace> を使います。トピックごとのリマップは不要です。

namespaceを分けるだけでは足りません。receive_portは、そのノードがローカルPCで使うOSCの受信ポートです(既定は25788)。2つのノードが同じポートを使うと、2つ目以降のノードはConnectToHostで失敗します。ノードごとに異なるreceive_portを割り当ててください。

contact_glove_nodeがpublishするトピック

トピック型QoS説明
left/glove_data / right/glove_datacontact_glove_msgs/GloveDatadepth 16, transient_local, reliableデバイス情報
left/ergonomics / right/ergonomicscontact_glove_msgs/ErgonomicsSensor Data QoS(best effort, volatile)指の開き角と曲げ角
left/skeleton / right/skeletoncontact_glove_msgs/SkeletonSensor Data QoS(best effort, volatile)手のスケルトン
left/raw_skeleton / right/raw_skeletoncontact_glove_msgs/RawSkeletonSensor Data QoS(best effort, volatile)リターゲット前のソーススケルトン
left/raw_sensor_transforms / right/raw_sensor_transformscontact_glove_msgs/RawSensorTransformsSensor Data QoS(best effort, volatile)センサーの生の変換(raw sensor transform)
left/controller / right/controllercontact_glove_msgs/ControllerSensor Data QoS(best effort, volatile)コントローラー入力
left/joint_states / right/joint_statessensor_msgs/JointStatedepth 10, reliable, volatileURDF連携用に変換したErgonomicsのデータ。後述の「JointState + URDF」を参照

glove_dataはバッテリー残量や接続状態など、変化の少ない状態を含むため、transient_local(latched)のdurabilityを使います。片側のトピックは、idによって複数のグローブを多重化できます。depth 1でlatchすると、最後にpublishされたグローブしか保持されず、後から購読したノードは他のグローブを取りこぼします。

そこでcontact_glove_nodeは、認識しているすべてのグローブの最新の状態を保持します。いずれかのグローブが更新されるたびに、認識している全グローブの現在の状態を、is_leftに従ってleft/glove_dataまたはright/glove_dataへpublishし直します。これによりtransient localのキャッシュには、グローブごとに現在のサンプルが1つずつ入ります。そのためdepthは、ノードが同時に追跡できるグローブの最大数になります(既定は16)。同時に使うグローブがそれより多い場合は、depthを増やしてください。

重要: glove_dataのlatchされたキャッシュを受け取るには、購読側もtransient_localのdurabilityを要求する必要があります。volatileの購読側もtransient_localのpublisherに接続はできますが、接続時にキャッシュされたサンプルは受け取らず、その後にpublishされたメッセージだけを受け取ります。そのため、既定のcreate_subscription(topic, 10, callback) や、durabilityを指定しないros2 topic echoでは、グローブが新しいglove_dataを送るまで、現在のグローブの状態を受け取れません。

C++では、publisherと同じQoSを使います。

auto sub = create_subscription<contact_glove_msgs::msg::GloveData>(
  "left/glove_data", rclcpp::QoS(16).transient_local(), callback);

CLIで確認する場合は、durabilityを指定します。

ros2 topic echo /contact_glove/left/glove_data --qos-durability transient_local

ergonomics、skeleton、raw_skeleton、raw_sensor_transforms、controllerは高レートのストリームです。rclcpp::SensorDataQoS()(best effort、volatile、depth 5相当)を使い、サンプルの取りこぼしを防ぐことよりも、低遅延でpublishがブロックしないことを優先します。購読側も同じQoSか、互換性のあるbest effortを使う必要があります。reliableの購読側はQoSが互換でないため、このデータを受け取れません。

contact_glove_nodeが購読するトピック

トピック型QoS説明
left/haptics_body / right/haptics_bodycontact_glove_msgs/HapticsBodydepth 10, reliable, volatile振動コマンド。publishすると振動します

haptics_bodyは振動コマンドを失わないよう、既定のreliable/volatileのQoSのままにしています。左右どちらのトピックも、msg.idで対象のグローブを選びます。片側に複数のグローブを割り当てている場合は、idを明示してください。

contact_glove_nodeが提供するサービス

サービス型説明
get_retarget_profilecontact_glove_msgs/srv/GetRetargetProfile現在接続中のDivingStationに、プロファイルUUIDの表示用メタデータを問い合わせます

このサービスはノード相対です。既定の完全修飾名は /contact_glove/get_retarget_profile です。リクエストにはprofile_uuidだけを渡します。ROS 2 SDKが内部で正のリクエストIDを割り当ててC++ SDKに送り、それに対応する非同期の応答だけを待つため、呼び出し側がリクエストIDを管理する必要はありません。Skeletonメッセージの受信中に、プロファイルUUIDを自動で問い合わせたりキャッシュしたりはしません。

値定数説明
0STATUS_FOUNDプロファイルが存在します。profile_uuid、name、descriptionが有効です。
1STATUS_NOT_FOUNDUUIDの形式は正しいものの、DivingStationに一致するプロファイルがありません。nameとdescriptionは空です。
2STATUS_INVALID_QUERYDivingStationが問い合わせを無効として拒否しました。プロファイルUUIDなどの値は空です。
3STATUS_TIMEOUTretarget_profile_timeout_msの間にDivingStationから応答がありませんでした(ROS 2ブリッジ独自)。
4STATUS_SDK_REQUEST_REJECTEDUUIDの形式が不正であるなどの理由で、C++ SDKがリクエストを送信しませんでした(ROS 2ブリッジ独自)。

STATUS_TIMEOUTの後に届いた応答は破棄されます。nameとdescriptionは、STATUS_FOUNDのときだけ使ってください。

contact_glove_rviz_nodeがpublishするトピック

トピック型QoS説明
left/skeleton_markers / right/skeleton_markersvisualization_msgs/MarkerArraydepth 10, reliable, volatile関節の球(SPHERE_LIST)と骨(LINE_LIST)。メッセージのheader.frame_idのフレームで表されます
/tfTF-publish_placeholder_wrist_tf:=trueのとき、parent_frame_idからheader.frame_idへのプレースホルダのTFをbroadcastします。publish_joint_tf:=trueのとき、手首以外の24関節のTFをbroadcastします。namespaceに関係なく、常に絶対パスの/tfです。

contact_glove_rviz_nodeは、left/skeletonとright/skeletonをrclcpp::SensorDataQoS()で購読し、対応するMarkerArrayをpublishします。購読側のQoSは、contact_glove_nodeのpublisherのQoSと一致している必要があります。


ノードパラメータ

contact_glove_node

パラメータ既定値説明
ds_ip"127.0.0.1"DivingStationのIPアドレス
ds_port25791DivingStationのOSC受信ポート
receive_port25788このノードのOSC受信ポート
send_port0SDKからDivingStationへ送るOSCの、ローカル側のUDP送信元ポート。0ならOSが空いているポートを選びます。
timeout_duration1000無通信タイムアウト(ミリ秒)。期限が切れると自動で再接続します
retarget_profile_timeout_ms1000get_retarget_profileが非同期の応答を待つ時間(ミリ秒)。0以下は起動時のエラーになります
try_reconnecttrueタイムアウト時に、既存のソケットでConnectを再送するかどうか
left_joint_state_glove_id""left/joint_statesが表すグローブIDを固定します。空なら、その側で最初に受信したグローブを選びます。
right_joint_state_glove_id""右側も同じルールです

launch引数namespaceの既定値はcontact_gloveです。header.frame_idに埋め込むnamespaceと、ROS 2ノードのnamespaceの両方に使われます。

DivingStationの接続ポート

ds_port(既定は25791)は、SDKがデータを送るDivingStation側のOSC受信ポートです。DivingStationの設定で確認・変更できます。

  1. DivingStationのホーム画面で、右上の歯車アイコンをクリックしてSettingsを開きます。
  2. 左のメニューからDivingStationProtocolを選びます。
  3. DivingStationProtocol v2 portの番号が、SDKの接続先ポートです。

DivingStationProtocolの設定:v2ポートの番号(日本語UI)

DivingStationのv2ポートが25791以外の場合は、contact_glove_nodeのds_portを同じ値に設定してください。設定しないとOSCパケットがDivingStationに届かず、データをやり取りできません。

receive_portは、DivingStationからのコールバックを受けるローカルのUDPポートです。1台のPC上のSDKノードには、それぞれ異なる値を付けてください。別のPCのDivingStationに接続する場合は、実際に使うreceive_portをローカルPCのファイアウォールで許可してください。

send_portは、SDKがDivingStationへ送るUDPパケットの送信元ポートです。send_port:=0では、OSが空いているポートを選びます。送信元ポートを固定する必要がある場合だけ指定してください。

DivingStationは、送信元のUDPエンドポイントとreceive_portで、アクティブなセッションを識別します。明示的にDisconnectするか、最後のConnectから90秒たつと、セッションは期限切れになります。そのため、ポートを固定しても、次の接続に承認が引き継がれるわけではありません。

# To fix both the source port and callback receive port
ros2 launch contact_glove contact_glove.launch.xml \
  receive_port:=25788 \
  send_port:=25789

send_portとreceive_portは異なっていてもかまいません。1台のPC上で複数のSDKノードを動かす場合は、receive_portをそれぞれ異なる値にする必要があります。

contact_glove_rviz_node

パラメータ既定値説明
parent_frame_id"world"手首のTFフレームの親フレーム
publish_placeholder_wrist_tftrue左右の手が並んで見えるよう、プレースホルダの手首TFをpublishします。実際の手首の6DoFを別のソースがpublishする場合はfalseにします。
left_wrist_offset[0.0, -0.15, 0.0]parent_frame_id座標での、プレースホルダの左手首の位置(メートル)
right_wrist_offset[0.0, 0.15, 0.0]parent_frame_id座標での、プレースホルダの右手首の位置(メートル)
marker_lifetime0.4マーカーの寿命(秒)。手が切断されたり更新が止まったりすると、この時間で消えます。
publish_joint_tffalseデバッグ用に、手首以外の24関節を/tfにpublishします。手首は既存の配置の仕組みに従い、重ねてpublishはしません。

Skeletonメッセージの構造(25関節)

Skeleton.msgは、SDKのSkeletonData::transformsをROS 2で表したもので、SkeletonDataTypeの順に25要素が並びます。

  • geometry_msgs/Transform[25] jointsは、25関節すべての位置と回転(translationとrotation)を持ちます。単位、座標系、回転の規約は後述します。
  • NODE_WRISTからNODE_LITTLE_TIP(uint8、0〜24)は、関節のインデックス定数です。値と順序は、SDKのSkeletonDataTypeと完全に一致します。

各指のCMC関節は、NODE_WRISTを親に持ちます。それ以外の関節は、同じ指の1つ手前の関節を親に持ちます。contact_glove_rviz_node.cppのkBonePairsテーブルも、同じ定数を使って骨を描画しています。

子ノード親ノード
NODE_THUMB_CMCNODE_WRIST
NODE_THUMB_MCPNODE_THUMB_CMC
NODE_THUMB_IPNODE_THUMB_MCP
NODE_THUMB_TIPNODE_THUMB_IP
NODE_{INDEX,MIDDLE,RING,LITTLE}_CMCNODE_WRIST
NODE_{INDEX,MIDDLE,RING,LITTLE}_MCP対応するNODE_*_CMC
NODE_{INDEX,MIDDLE,RING,LITTLE}_PIP対応するNODE_*_MCP
NODE_{INDEX,MIDDLE,RING,LITTLE}_DIP対応するNODE_*_PIP
NODE_{INDEX,MIDDLE,RING,LITTLE}_TIP対応するNODE_*_DIP

RawSkeletonメッセージの構造(25関節)

RawSkeleton.msgは、SDKのRawSkeletonData::transformsを、Skeleton.msgと同じ25関節の順序とNODE_*定数で公開します。profile_uuidはありません。

  • 位置は、リターゲット前のソーススケルトンの、モデルを基準とした空間(model-rooted space)で表されます。header.frame_idは ds2/<namespace>/<glove_id>/source_wrist です。
  • rotationはSkeletonと同じく親関節に対するローカルの回転ですが、通信で届くのは19関節分の回転だけです。C++ SDKは、手首と指先の6関節の回転を単位クォータニオンで初期化します。これらを計測値や推定値として解釈しないでください。
  • RawSkeleton.tracking_stateとErgonomics.tracking_stateは、ソーススケルトンを求めるIKの状態です。同じ時刻のリターゲット後のSkeleton.tracking_stateとは異なる場合があります。たとえばリターゲットだけが失敗した場合、RawSkeletonとErgonomicsはTRACKEDで、SkeletonはIK_FAILEDになります。

contact_glove_rviz_nodeが可視化とTFのpublishを行うのは、リターゲット後のSkeletonだけです。RawSkeletonについては、MarkerArrayもTFもpublishしません。ソース側の座標を可視化する場合は、利用側で明示的に扱う必要があります。


メッセージの座標系

Skeleton、RawSkeleton、RawSensorTransformsの座標は、SDKのmodel-rooted space(右手系、単位はメートル)で表されます。Skeletonはリターゲットプロファイルの手モデルの座標系を、RawSkeletonはソーススケルトンの手モデルの座標系を使います。そのため、両者のheader.frame_idは異なります。

translationは、各メッセージの手首を基準とした、model-rooted spaceでの絶対位置です。1つ手前の関節やセンサーからの相対位置ではありません。rotationの規約は、メッセージによって異なります。

軸向き
+X指先から手首へ向かう向き
+Y手のひらから手の甲へ向かう向き
+Z右手では小指側から親指側へ、左手では親指側から小指側へ向かう向き
        +Y (back of hand)
         ^
         |
 wrist   |
   o-----+-----> +X (toward wrist)
        /
       /
      v
    +Z (right: little to thumb / left: thumb to little)

左右の手で+Zの向きを反転させることで、それぞれの手のローカル座標系が右手系(+X × +Y = +Z)に保たれます。そのため、tf2などの座標変換ライブラリは右手系を前提にできます。

translationとrotation

  • 単位: translation(geometry_msgs/Vector3のx、y、z)の単位はメートルです。
  • rotation(共通): 正規化されたクォータニオンで、geometry_msgs/Quaternionの標準のフィールド順(x、y、z、w)です。
  • Skeletonのrotation: model-rooted spaceでの絶対的な向きではなく、親関節に対するローカルのクォータニオンです。親子関係はSkeleton.msgのNODE_*のコメントを参照してください。通信では、すべてのCMC関節を含む19関節のローカル回転を送ります。SDKはNODE_WRISTと5つの*_TIPの回転を単位クォータニオンで補います。この6個は計測した回転ではありません。
  • RawSkeletonのrotation: Skeletonと同じく、親関節に対するローカルのクォータニオンです。送られるのは19関節分の回転だけで、手首と各指先の単位クォータニオンは、有効な回転の計測値ではありません。
  • RawSensorTransformsのrotation: translationと同じく、model-rooted spaceでの絶対的な向きです。各センサーのローカル座標系からmodel-rooted spaceへの向きを表します。親関節に対するローカルの回転であるSkeletonのrotationとは異なります。
  • 生データと較正済みデータ: Skeletonは、profile_uuidで指定したプロファイルを使ってDivingStation CoreのIK処理を行った、リターゲット後のデータです。SDKはその値をデコードし、IK処理は行いません。RawSkeletonは、較正済みのEMFトラッキングにソース側のIKと順運動学を適用した、リターゲット前のデータです。RawSensorTransformsは、リターゲットを行わない、個々のセンサーの生の変換を含みます。tracking_stateにはTRACKED、IK_FAILED、NOT_ATTEMPTEDの値があります。IK_FAILEDとNOT_ATTEMPTEDのサンプルは表示や診断には役立ちますが、制御・速度推定・補間には使わないでください。RawSkeletonとSkeletonのtracking stateは異なる場合があります。NOT_ATTEMPTEDは、必要なIK処理が実行されなかったことを表します。前段の処理失敗や入力不足のほか、更新間隔の制御で既存の姿勢を再利用するなど、姿勢計算が不要と判断された場合も含み、この状態自体はIKの失敗を意味しません。制御・速度推定・補間で有効なのはTRACKEDの値だけです。

対応する単位・範囲・座標の規約は、各msgファイルのコメントにも書かれています。ros2 interface showで確認できます。例:

ros2 interface show contact_glove_msgs/msg/Skeleton

ストリーミングのメッセージ(GloveData、Ergonomics、Skeleton、RawSkeleton、RawSensorTransforms、Controller)はすべて、先頭にstd_msgs/Header headerを持ちます。header.stampは、SDKのコールバックを受けてROS 2メッセージに変換したときのROS時刻です。header.frame_idは、対象のグローブの手首フレームです。ただしRawSkeletonだけは、ソーススケルトンの手首を使います。

generated_at_msは、SDK / DivingStation Protocol v2がフレームを生成したときのUnixタイムスタンプ(ミリ秒)です。Ergonomics、Skeleton、RawSkeleton、RawSensorTransformsに含まれます。GloveDataとControllerは、プロトコル上のデータに生成時刻がないため、このフィールドを持ちません。

TF frame名の命名規則

ROS 2のトピックのnamespace(launch引数のnamespace:=...)と、TFのフレーム名は別の概念です。

  • left/skeleton_markersなどの通常のトピックは、/contact_glove/left/skeleton_markersのようにノードのnamespaceの下に置かれ、namespaceごとに独立しています。
  • tf2_ros::TransformBroadcasterは、namespaceに関係なく、常に絶対パスの/tfトピックにpublishします。つまり**/tfは1つのグローバルなトピック**で、同時に動いているすべてのnamespace(複数のDivingStationやデモ環境など)のTFがここに送られます。
  • 複数のnamespaceの間でTFが衝突しないよう、frame_idとchild_frame_idの名前を一意にしてください。トピックのnamespaceを分けるだけでは不十分です。

手首のフレームには、通常は次の名前を使います。

ds2/<namespace>/<glove_id>/wrist

RawSkeletonは、ソーススケルトンの座標をリターゲット後のSkeletonと取り違えないよう、別の名前を使います。

ds2/<namespace>/<glove_id>/source_wrist
  • 既定のnamespaceはcontact_gloveです。header.frame_idのnamespaceとROS 2ノードのnamespaceの両方に、同じlaunch引数が使われます。
  • 実機のグローブIDはUUIDなので、glove_idだけで衝突することはまずありません。namespaceを含めることで、別々のDivingStationやデモ環境を人が見分けやすくもなります。
  • ds2は、DivingStation Protocol v2由来のフレームであることを示す固定の接頭辞で、/tf上で他のROS 2パッケージのフレームと衝突しにくくします。
  • contact_glove_with_rviz.launch.xmlは、ドライバとRVizノードに同じnamespaceを渡すため、両者は常に同じ手首フレーム名を参照します。
  • RVizの関節マーカーと骨は、対応するSkeleton.header.frame_idの手首フレームでpublishされます。

contact_glove_rviz_nodeは、各手のスケルトンを、手首を原点とするその手のheader.frame_idでpublishします。

  • publish_placeholder_wrist_tf:=true(既定)では、左右の手が並んで見えるよう、left_wrist_offsetまたはright_wrist_offsetの位置にプレースホルダの手首TFをpublishします。表示用であり、実際の手首の位置ではありません。
  • publish_placeholder_wrist_tf:=falseでは、手首のTFをpublishしません。手首の6DoFを持つ外部トラッカーなど、別のプログラムがparent_frame_idからheader.frame_idへのTFをpublishする必要があります。スケルトンは、その実際の手首の位置に追従します。

25関節のデバッグ用TF(publish_joint_tf)

contact_glove_rviz_nodeのpublish_joint_tf(既定はfalse)をtrueにすると、手首以外のSkeletonの24関節を/tfにpublishします。スケルトンの位置と回転を、TFとして直接デバッグするための機能です。ロボット連携や関節階層の可視化に使うJointState + URDFとは独立しており、どちらか一方だけを有効にすることもできます。

  • 手首は ds2/<namespace>/<glove_id>/wrist のままです。その配置は、publish_placeholder_wrist_tfのプレースホルダか、外部ノードからの実際の手首TFに従います。publish_joint_tfは手首の動作を変えません。

  • 手首以外の関節は、手首の直接の子として、次の名前になります。

    ds2/<namespace>/<glove_id>/<finger>/<joint>

    例:ds2/contact_glove/0123456789ABCDEF/thumb/cmc、ds2/contact_glove/0123456789ABCDEF/index/pip、ds2/contact_glove/0123456789ABCDEF/little/tip。<finger> はthumb、index、middle、ring、littleのいずれかです。<joint> はSkeleton.msgのNODE_*の接尾辞(cmc、mcp、pip、dip、ip、tip)です。

  • translationは、Skeleton.joints[NODE_*].translationをそのまま中継します。これはすでに、手首からの絶対位置です。Skeleton.joints[NODE_*].rotationは親関節に対するローカルの回転なので、ノードが親から子へ回転を合成し、手首からの絶対的な回転にしてからpublishします。変わるのは回転の値だけで、直列のTF階層は再構成しません。デバッグ用の配置は平坦なままで、すべての関節が手首の直接の子です。

  • TFのheader.stampは、対応するSkeleton.header.stampです。

  • publish_joint_tf:=falseのときは、これらの動作はいっさい行われず、既存のMarkerArrayやプレースホルダの手首TFの動作も変わりません。


JointState + URDF(ロボット連携)

contact_glove_nodeは、left/ergonomicsとright/ergonomicsのデータを、sensor_msgs/JointStateとしてleft/joint_statesとright/joint_statesにもpublishします。これらのトピックは、robot_state_publisherや、MoveItなどの標準的なROS 2のロボットスタック向けです。contact_gloveパッケージには、左右の手モデルのURDFファイル(urdf/left_hand.urdf.xacroとurdf/right_hand.urdf.xacro)が含まれます。

25関節のデバッグ用TFは、リターゲット後のSkeletonの位置と回転を確かめるためのものです。一方、JointState + URDFは、関節角度をURDFの名前付きjointに対応させ、一般的なロボットの可視化や連携に使えるようにします。

Ergonomicsの度からJointStateのラジアンへ

  • Ergonomics.msgの20フィールド(thumb.{cmc,mcp,ip,tilt}と{index,middle,ring,little}.{mcp,pip,dip,tilt})は、URDFの20のjointに1対1で対応します。
  • 変換は度からラジアンへの変換だけです。左右の手で値の符号は反転しません。 鏡像の見た目は、URDFの形状とjointの軸によって作られます。
  • JointState.nameは <side>_<finger>_<field>_joint の形式で、<side> はleftまたはrightです。例:left_thumb_cmc_joint、right_index_tilt_joint。
  • JointState.nameとJointState.positionは、Ergonomics.msgのフィールド順に並びます。指の順はthumb、index、middle、ring、littleです。親指以外の各指はmcp、pip、dip、tilt、親指はcmc、mcp、ip、tiltの順です。
トピック型QoS
left/joint_statessensor_msgs/JointStatedepth 10, reliable, volatile
right/joint_statessensor_msgs/JointStatedepth 10, reliable, volatile

高レートのergonomicsやskeletonのトピックと異なり、これらのpublisherはreliableです。robot_state_publisherやMoveIt、その他一般的なROS 2のロボットスタックは、ROS 2の既定であるreliableのQoSを使うことが多いためです。best effortのpublisherでは互換になりませんが、reliableのpublisherであれば、best effortを要求する購読側とも互換です。

既定の完全修飾名は /contact_glove/left/joint_states と /contact_glove/right/joint_states です。namespaceは、他のストリーミングトピックと同じルールで変更できます。

同じサイドに複数グローブがある場合

left/joint_statesは、1つのロボットモデルを表します。左手のグローブを同時に複数接続している場合、publishされるのはそのうち1つのデータだけです。

  • left_joint_state_glove_idまたはright_joint_state_glove_idが空(既定)の場合は、その側で最初に検出したグローブが自動で選ばれます。再接続した後は選び直されます。
  • 選ぶグローブを固定するには、パラメータにそのグローブIDを設定します。他のグローブのデータは無視され、警告がログに出ます。left/ergonomicsなどの通常のトピックは、引き続きすべてのグローブをpublishします。
  • 複数のグローブをURDFで別々に表示するには、グローブごとにnamespaceを分けて、contact_glove_nodeを複数起動します。ノードごとにreceive_portも一意にしてください。ポートが重複すると、2つ目以降のUDPのbindが失敗します。同じDivingStationを指すなら、ds_ipとds_portは共通のままでかまいません。left_joint_state_glove_idまたはright_joint_state_glove_idも明示してください。指定しないと、どのノードも同じ最初のグローブを選ぶことがあります。

URDFファイル

  • jointとlinkの名前はすべてleft_またはright_の接頭辞を持つため、両手のモデルを衝突なく読み込めます。
  • 各手は、Ergonomics.msgの20フィールドに対応する20のjointと、それらをつなぐlinkを持ちます。ルートのlinkは <side>_wrist です。
  • jointの位置と向き(origin)には、計測した基準点から求めた、較正済みの手の形状を使います。左手は右手の基準点のZ座標を反転させ、独自に形状を求めています。静止姿勢での指先の位置は、正確に鏡像(X/Yが等しく、Zが反転)になります。
  • jointの軸は、左右の手に符号も含めて同じErgonomicsの値を与えたとき、屈曲のjoint(cmc、mcp、pip、dip、ip)と開閉のjoint(tilt、外転/内転)の指の動きが正しく鏡像になるよう調整されています。
  • jointの可動範囲は、手モデルの範囲をラジアンで表したものです。実際のデバイスの正確な仕様ではありません。
  • 各linkのvisualは、隣り合うjointをつなぐ単純な円柱で、実際の手を再現したものではありません。
  • URDFは可視化と関節階層のためのものです。collisionやinertialの定義はなく、物理シミュレーションはこのパッケージの対象外です。

contact_glove_with_urdf.launch.xml

このlaunchファイルは、robot_state_publisherとRViz2のRobotModelによる、URDFベースのリアルタイム可視化を提供します。

ros2 launch contact_glove contact_glove_with_urdf.launch.xml hand:=both
引数既定値説明
namespace"contact_glove"contact_glove.launch.xmlと同じ意味です
ds_ip / ds_port / receive_port / send_port / timeout_duration / try_reconnectcontact_glove.launch.xmlと同じ既定値DivingStationの接続パラメータ
left_joint_state_glove_id / right_joint_state_glove_id""contact_glove_nodeの同名のパラメータにそのまま渡します
start_drivertrueSDKドライバcontact_glove_nodeを起動するかどうかを、手動で切り替えます。二重起動を自動で防ぐものではありません。
hand"both"both、left、rightのいずれかで、表示する手を選びます
parent_frame_id"world"left_wristとright_wristの親フレーム
enable_rviz2trueconfig/contact_glove_urdf.rvizでRViz2を起動するかどうか
publish_wrist_placementtrueparent_frame_idからleft_wristとright_wristへの、プレースホルダの静的TFを、left/right_wrist_offset_{x,y,z}の位置にpublishします。実際の手首の6DoFを外部のソースがpublishする場合はfalseにします。
left_wrist_offset_x/y/z, right_wrist_offset_x/y/z左 (0.0, -0.15, 0.0) / 右 (0.0, 0.15, 0.0)parent_frame_id座標での、プレースホルダの手首の位置(メートル)
left_wrist_offset_roll, right_wrist_offset_roll左 π (180°) / 右 0.0プレースホルダの手首の、X軸まわりの回転(ラジアン)。左右のURDFはローカル座標の規約が共通なので、左手だけを180°回転させると、向かい合わせに配置されます。

hand:=bothでは、左右のrobot_state_publisherを両方起動します。hand:=leftまたはhand:=rightでは、指定した側だけを起動します。robot_descriptionとjoint_statesは /<namespace>/left/... などの名前に解決され、contact_glove_nodeのleft/joint_statesと自動で一致します。リマップは不要です。

このlaunchファイルは、MarkerArrayや25関節のデバッグ用TFのためのcontact_glove_rviz_nodeは起動しません。両方の表示を使う場合は、このファイルとcontact_glove_with_rviz.launch.xmlを別々に起動しないでください。代わりにcontact_glove_with_urdf_and_rviz.launch.xmlを使います。 どちらのlaunchファイルもSDKドライバを起動するため、別々に起動するとcontact_glove_nodeが2つでき、同じreceive_portへのUDPのbindが衝突します。

URDFのTFフレーム名

left_wristやleft_thumb_cmc_linkなどのURDFのTFフレーム名には、namespaceが接頭辞として付きます。これにより、別々のnamespaceで複数のDivingStationを動かしても、TFが衝突しません。

<namespace>/left_wrist
<namespace>/left_thumb_cmc_link
<namespace>/right_wrist

publish_wrist_placementの静的TFも、同じ名前を使います。同梱のconfig/contact_glove_urdf.rvizとconfig/contact_glove_combined.rvizは、既定のnamespaceに合わせて、RobotModelのTF Prefixをcontact_gloveに設定しています。namespace:=...で変更した場合は、RVizのTF Prefixも同じ値(末尾のスラッシュなし)にしてください。

contact_glove_with_urdf_and_rviz.launch.xml

このlaunchファイルは、リターゲット後のSkeleton(MarkerArrayと25関節のデバッグ用TF)と、そこから求めたURDFのRobotModelを並べて表示します。主な用途は、両方の動きを比べて、ErgonomicsからJointStateへの変換とURDFのjointの軸を確かめることです。

前述のとおり、contact_glove_with_rviz.launch.xmlとcontact_glove_with_urdf.launch.xmlを別々に起動すると、SDKドライバが二重に起動します。両方の表示が必要なときは、必ずこの組み合わせ用のlaunchファイルを使ってください。

ros2 launch contact_glove contact_glove_with_urdf_and_rviz.launch.xml hand:=both

contact_glove_with_rviz.launch.xmlとcontact_glove_with_urdf.launch.xmlのすべての引数を、start_driverを除いて受け付けます。RViz2は、MarkerArray、TF、RobotModelを1つのウィンドウにまとめた、同梱のconfig/contact_glove_combined.rvizのプリセットで起動します。