APIリファレンス
このリファレンスでは、contact_gloveパッケージとcontact_glove_msgsパッケージが提供するトピック、メッセージ型、ノードパラメータ、座標系の規約を説明します。
パッケージが提供する型とノード
contact_glove_msgs
SDKのデータ構造に対応するROS 2メッセージを定義します。
| ファイル | SDKの型 | 説明 |
|---|---|---|
| msg/GloveData.msg | GloveData | デバイス情報:バッテリー残量(%)、外部給電、接続状態、デバイスの表示色設定など |
| msg/Ergonomics.msg | ErgonomicsData | 指の開き角と曲げ角。ThumbPose・FingerPoseのサブメッセージを含みます |
| msg/Skeleton.msg | SkeletonData | 25関節の位置と回転。geometry_msgs/Transform[25] jointsとNODE_*定数 |
| msg/RawSkeleton.msg | RawSkeletonData | リターゲット前のソーススケルトン25関節の位置と回転。座標系はSkeletonと異なります |
| msg/RawSensorTransforms.msg | RawSensorTransformsData | 5つのセンサーの位置と回転。各フィールドはgeometry_msgs/Transformです |
| msg/Controller.msg | ControllerData | ボタンの状態とアナログ入力 |
| msg/HapticsBody.msg | VibrateGlove() | 周波数・振幅・持続時間を指定する振動コマンド |
| srv/GetRetargetProfile.srv | RequestRetargetProfileInfo() / RetargetProfileInfo | プロファイルUUIDから表示用のメタデータを同期的に問い合わせます |
contact_glove
このパッケージは2つの実行ファイルを提供します。
| 実行ファイル | 説明 |
|---|---|
| contact_glove_node | SDKに接続し、グローブのデータを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_data | contact_glove_msgs/GloveData | depth 16, transient_local, reliable | デバイス情報 |
| left/ergonomics / right/ergonomics | contact_glove_msgs/Ergonomics | Sensor Data QoS(best effort, volatile) | 指の開き角と曲げ角 |
| left/skeleton / right/skeleton | contact_glove_msgs/Skeleton | Sensor Data QoS(best effort, volatile) | 手のスケルトン |
| left/raw_skeleton / right/raw_skeleton | contact_glove_msgs/RawSkeleton | Sensor Data QoS(best effort, volatile) | リターゲット前のソーススケルトン |
| left/raw_sensor_transforms / right/raw_sensor_transforms | contact_glove_msgs/RawSensorTransforms | Sensor Data QoS(best effort, volatile) | センサーの生の変換(raw sensor transform) |
| left/controller / right/controller | contact_glove_msgs/Controller | Sensor Data QoS(best effort, volatile) | コントローラー入力 |
| left/joint_states / right/joint_states | sensor_msgs/JointState | depth 10, reliable, volatile | URDF連携用に変換した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_body | contact_glove_msgs/HapticsBody | depth 10, reliable, volatile | 振動コマンド。publishすると振動します |
haptics_bodyは振動コマンドを失わないよう、既定のreliable/volatileのQoSのままにしています。左右どちらのトピックも、msg.idで対象のグローブを選びます。片側に複数のグローブを割り当てている場合は、idを明示してください。
contact_glove_nodeが提供するサービス
| サービス | 型 | 説明 |
|---|---|---|
| get_retarget_profile | contact_glove_msgs/srv/GetRetargetProfile | 現在接続中のDivingStationに、プロファイルUUIDの表示用メタデータを問い合わせます |
このサービスはノード相対です。既定の完全修飾名は /contact_glove/get_retarget_profile です。リクエストにはprofile_uuidだけを渡します。ROS 2 SDKが内部で正のリクエストIDを割り当ててC++ SDKに送り、それに対応する非同期の応答だけを待つため、呼び出し側がリクエストIDを管理する必要はありません。Skeletonメッセージの受信中に、プロファイルUUIDを自動で問い合わせたりキャッシュしたりはしません。
| 値 | 定数 | 説明 |
|---|---|---|
| 0 | STATUS_FOUND | プロファイルが存在します。profile_uuid、name、descriptionが有効です。 |
| 1 | STATUS_NOT_FOUND | UUIDの形式は正しいものの、DivingStationに一致するプロファイルがありません。nameとdescriptionは空です。 |
| 2 | STATUS_INVALID_QUERY | DivingStationが問い合わせを無効として拒否しました。プロファイルUUIDなどの値は空です。 |
| 3 | STATUS_TIMEOUT | retarget_profile_timeout_msの間にDivingStationから応答がありませんでした(ROS 2ブリッジ独自)。 |
| 4 | STATUS_SDK_REQUEST_REJECTED | UUIDの形式が不正であるなどの理由で、C++ SDKがリクエストを送信しませんでした(ROS 2ブリッジ独自)。 |
STATUS_TIMEOUTの後に届いた応答は破棄されます。nameとdescriptionは、STATUS_FOUNDのときだけ使ってください。
contact_glove_rviz_nodeがpublishするトピック
| トピック | 型 | QoS | 説明 |
|---|---|---|---|
| left/skeleton_markers / right/skeleton_markers | visualization_msgs/MarkerArray | depth 10, reliable, volatile | 関節の球(SPHERE_LIST)と骨(LINE_LIST)。メッセージのheader.frame_idのフレームで表されます |
| /tf | TF | - | 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_port | 25791 | DivingStationのOSC受信ポート |
| receive_port | 25788 | このノードのOSC受信ポート |
| send_port | 0 | SDKからDivingStationへ送るOSCの、ローカル側のUDP送信元ポート。0ならOSが空いているポートを選びます。 |
| timeout_duration | 1000 | 無通信タイムアウト(ミリ秒)。期限が切れると自動で再接続します |
| retarget_profile_timeout_ms | 1000 | get_retarget_profileが非同期の応答を待つ時間(ミリ秒)。0以下は起動時のエラーになります |
| try_reconnect | true | タイムアウト時に、既存のソケットで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の設定で確認・変更できます。
- DivingStationのホーム画面で、右上の歯車アイコンをクリックしてSettingsを開きます。
- 左のメニューからDivingStationProtocolを選びます。
- DivingStationProtocol v2 portの番号が、SDKの接続先ポートです。

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:=25789send_portとreceive_portは異なっていてもかまいません。1台のPC上で複数のSDKノードを動かす場合は、receive_portをそれぞれ異なる値にする必要があります。
contact_glove_rviz_node
| パラメータ | 既定値 | 説明 |
|---|---|---|
| parent_frame_id | "world" | 手首のTFフレームの親フレーム |
| publish_placeholder_wrist_tf | true | 左右の手が並んで見えるよう、プレースホルダの手首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_lifetime | 0.4 | マーカーの寿命(秒)。手が切断されたり更新が止まったりすると、この時間で消えます。 |
| publish_joint_tf | false | デバッグ用に、手首以外の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_CMC | NODE_WRIST |
| NODE_THUMB_MCP | NODE_THUMB_CMC |
| NODE_THUMB_IP | NODE_THUMB_MCP |
| NODE_THUMB_TIP | NODE_THUMB_IP |
| NODE_{INDEX,MIDDLE,RING,LITTLE}_CMC | NODE_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>/wristRawSkeletonは、ソーススケルトンの座標をリターゲット後の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_states | sensor_msgs/JointState | depth 10, reliable, volatile |
| right/joint_states | sensor_msgs/JointState | depth 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_reconnect | contact_glove.launch.xmlと同じ既定値 | DivingStationの接続パラメータ |
| left_joint_state_glove_id / right_joint_state_glove_id | "" | contact_glove_nodeの同名のパラメータにそのまま渡します |
| start_driver | true | SDKドライバcontact_glove_nodeを起動するかどうかを、手動で切り替えます。二重起動を自動で防ぐものではありません。 |
| hand | "both" | both、left、rightのいずれかで、表示する手を選びます |
| parent_frame_id | "world" | left_wristとright_wristの親フレーム |
| enable_rviz2 | true | config/contact_glove_urdf.rvizでRViz2を起動するかどうか |
| publish_wrist_placement | true | parent_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_wristpublish_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:=bothcontact_glove_with_rviz.launch.xmlとcontact_glove_with_urdf.launch.xmlのすべての引数を、start_driverを除いて受け付けます。RViz2は、MarkerArray、TF、RobotModelを1つのウィンドウにまとめた、同梱のconfig/contact_glove_combined.rvizのプリセットで起動します。