ロボット開発記録

ロボット開発の進捗や記録をするブログ

続・ソフトウェア構成とコードの整理Ver2 [ヒューマノイド動歩行]

前回の振り返り

 前回の記事では、ソフトウェアアーキテクチャを再考案しました。今回も引き続き、ソフトウェアアーキテクチャに取り組んでいきます。

odome.hatenablog.com

今回の概要

 今回は前回に引き続き、ソフトウェアアーキテクチャを考えていきます。前回は旧アーキテクチャの問題点と、それを改善できるであろう新アーキテクチャを示しました。今回は追加で発生した問題とそれに応じた新アーキテクチャを示します。

新たに発生した問題

ソフトウェアアーキテクチャVer2

 前回考案した新しいアーキテクチャ(Ver2)への移行をする途中で、一つの問題が発生しました。それは、同期Service通信の中で、もう一つ別の動機Service通信を行うと、処理が完全に止まってしまう問題です。具体的には、上図での、Webots Robot Handler , Robot Manager間の同期Service通信の中で Robot Manager , Walking Pattern Generator や Robot Manager, Walking Stabilization Controller 間の同期Service通信を実行すると、Requestは送れてもResponseが全く帰ってこない状態になります。
 この問題は、アーキテクチャVer2ではService通信のみを用いてシーケンシャルな構成をとっていることから、非常によろしくありません。早急に解決するべき問題です。

問題とその解決方法の調査

 この問題について、先行事例がないか少し調べてみたところ、やはりいくつかissueや質問が確認できました。以下に、参考になるものをいくつか挙げます。

Calling a Service from an other Service leading to a std::runtime_error · Issue #312 · ros2/rclcpp · GitHub
Exception when using parameterclient while having node spinning in other thread · Issue #206 · ros2/rclcpp · GitHub
How to call spin_some or spin_until_future_complete in member functions - ROS Answers: Open Source Q&A Forum
[ROS2] How to implement a sync service client in a node? - ROS Answers: Open Source Q&A Forum
rclcpp (ROS Client Library for C++) を読み解く (2) | Yutaka Kondo
Using Callback Groups — ROS 2 Documentation: Humble documentation

以上の記事などから、よわよわ英語力と推測マシマシで考えると、

  • "spin"は、1つのスレッドに1つしか存在できない。
  • 複数の"spin"を1つのnodeで使いたいときには、MultiThreadExecutorでnodeをマルチスレッドで動かす。
  • 複数の"spin"を持つ状態で同期Service通信を行う場合は、spin_until_future_complete を使わずに、future型のや.wait_for()などで擬似的に同期を取る。
  • 複数のCallback を用いるときには、Callback Groupを分ける必要がある。デフォルトだと同一の相互排他的Callback GroupでCallbackを回そうとしてしまう。
  • Callback Groupは、3つのTypeが存在する。デフォルト(nullptr)と、上でも挙げたこちらの記事で挙げられている2つ。
  • main関数においてexecutorやspinに渡す引数(nodeを定義したclass)は、一旦別変数にmake_sharedに渡して、それを用いる必要がある。


まとめると、

  • 複数のspinを持つnodeは、spin_until_future_completeではなくwait_for()などで同期をとり、MultiThreadExecutorを用いてnodeをマルチスレッドで動かし、すべてのCallbackのCallback Groupを分ける必要がある。


と解釈しました。
 この解決方法を実際に試してみました。ただ今回はService内でのServiceではなく、上でも挙げた公式のドキュメントを参考に、create_wall_timerで周期的に実行される関数内で同期Service通信を行いました。なお、Callback GroupのTypeはservice, create_wall_timerどちらも Reentrant としました。
 結果、成功しました。他の先行事例を見ると、大体同様の方法でService内Serviceを行っているので、恐らくこの方法で問題は解決できると考えられます。参考に、成功したcommitを以下に示します。robot_manager/src/ と walking_stabilization_controller/src/ 内のプログラムを用いて行いました。

github.com

問題の解決方法の試用と新アーキテクチャVer4

 発生した問題の解決方法とアーキテクチャの改善を考えて、ソフトウェアアーキテクチャVer4を考案しました。Ver4への過程で生まれたVer3も含めて、以下に示します。

ソフトウェアアーキテクチャVer3

ソフトウェアアーキテクチャVer4

 Ver2と大きく変わった点としては、歩行パターンを生成するnodeが制御周期の外に出た点が挙げられます。制御周期の外に出した理由としては、歩行パターンの生成にはFeedbackを用いないので、毎回わざわざService通信で歩行パターンを生成するよりも、数秒分の歩行パターンを一気に生成してまとめてマネージャに送り、そこから歩行パターンを参照してロボットに適用したほうがオーバーヘッドが少なく済むと考えたためです。こうすることで、制御周期内で行う処理が減り、処理を10[ms]以内に収めやすくなりました。
 また、コントローラとマネージャとの通信をTopic通信に変更し、制御周期をマネージャ内で指定するようにしました。こうすることで、マネージャとコントローラの関係をより疎にし、コントローラを変更しやすくしました。
 

 現在はこのVer4を実装する方向で考えています。ただ、Ver2のほうがシーケンシャルで考えやすいアーキテクチャをしているので、状況に応じて臨機応変に対応しようと考えています。

 ソフトウェアアーキテクチャの移行作業は、以下の tidy-up branch で行っています。

github.com

まとめ

 今回は前回に引き続き、ソフトウェアアーキテクチャの改善を行いました。同期Service通信内で同期Service通信ができない問題が発生しましたが、その解決策となり得る情報を得、実際にそれが解決できるであろう方法を実装できることを確認しました。この解決方法やさらなる改善を考え、ソフトウェアアーキテクチャVer3とVer4を示しました。
 今後はソフトウェアアーキテクチャVer4を実装する方向で進めていきます。ただ最近、理論の方をほったらかしにしていましたので、特に6月からは動歩行の実装のほうに取り組んでいきます。

ソフトウェア構成とコードの整理Ver.2 [ヒューマノイド動歩行]

前回の振り返り

 前回の記事では、静歩行を実装して、実際にWebots上で歩かせることができました。今回は、再度ソフトウェアアーキテクチャに取り組んでいきます。

odome.hatenablog.com

今回の概要

 今回は、動歩行に取り組む前に、新たにソフトウェアアーキテクチャを考えます。理由としては、前前回考えたアーキテクチャにいくつか問題があり、それが原因でオフセットや外乱が全くない静歩行で転倒してしまう問題が発生したためです。そのアーキテクチャの問題を元に、その点を改善したものを考え、改めて実装していきます。

アーキテクチャの問題

 以下に、問題のソフトウェアアーキテクチャを簡単に記した図を示します。

ソフトウェアアーキテクチャVer1

 静歩行動作が転倒する原因を調査した結果から、このアーキテクチャには以下の問題点が存在する可能性が考えられます。専門用語が乏しいせいで適切な表現ができていない可能性があります。

  • FK, IK nodeとWalkingStabilizationController nodeにおいて、複数のclientから同時にrequestされた時に、何かしらのrequestの処理がcallbackの実行待ち状態になってしまい、制御周期に影響を与えている可能性。
  • 同上nodeにおいて、複数のclientから異なった周期でrequestが送られてくることにより、望まれるcallbackの実行の順番が崩れ、望まないresponseを受け取る可能性。
  • WalkingPatternGenerator - WalkingStabilizationController間でのpub/sub周期と、WebotsRobotHandler - WalkingStabilizaitonController間でのrequest/response周期と、2重に制御周期が存在していることによる問題に加え、異なる周期で回っていることによる、歩行パターンが正常にロボットに反映されていない可能性。
  • そもそも、与えている歩行パターンも悪さをしている可能性。ただこれは変更した場合でも正常な静歩行はできていなかったので、根本的な原因ではないと思われる。

以上のように、多くの原因が考えられる結果となりました。問題を解決するためにはこれらの可能性を潰すように修正を加える必要がありますが、数が多いことに加えてまだ小規模なアーキテクチャなので、1から作り直した方が早いと判断しました。

新しいアーキテクチャ

 以上の原因の可能性を踏まえて、新しいアーキテクチャを考案しました。以下に示します。

ソフトウェアアーキテクチャVer2

 見て分かる通り、結構大きく変更しました。以下、主な変更点です。

  • ソフトウェア内でのデータの遷移を、シーケンシャルに変更。制御周期が1つになり、分かりやすさを重視。
  • RobotManager nodeを追加し、制御に関する処理をロボットを直接制御するWebotsRobotHandlerから切り離した。ロボットの環境(シミュレータ、実機)の変更にも柔軟に対応できるように。
  • FK, IKの処理を含むKinematicsを、共有ライブラリに変更。FK, IKを用いる際の通信によるオーバーヘッドを削減。同時requestによるcallbackの実行待ちも発生しない(シーケンシャルなので構成として発生しないが)。
  • 通信を全て同期Service通信で行うように変更。これも処理をシーケンシャルに行うため。

 以上の変更で、旧アーキテクチャでの問題は改善すると考えられます。
 これに加えて、Compornent指向に沿った実行方式への移行や、LifeCycleによる起動順序の調整など、幾つかの改善も考えています。

 以上に示した新しいアーキテクチャに沿って、実装を進めていきます。(絶賛移行中)

まとめ

 今回は、発生した問題からソフトウェアアーキテクチャの問題点を列挙し、それを元に新しいアーキテクチャを考案しました。また、これに沿って移行を進めています。
 次からは、ソフトウェアアーキテクチャの移行が完了し問題が解決した後、本題の動歩行動作の実装に取り組んでいく予定です。

静歩行動作の実装 [ヒューマノイド動歩行]

前回の振り返り

 前回の記事では、考案したソフトウェア構成に沿ってコードを整理しました。今回は、逆運動学を用いて静歩行動作を実装しました。

odome.hatenablog.com

今週の概要

 ソフトウェアの整備が完了したので、本題の動歩行動作をWalkingPatternGeneratorに実装し始めても良いのですが、その前段階、寄り道として静歩行動作を実装しました。静歩行動作とは、重心を床に投影した重心投影点が支持多角形の内側に常に存在する歩行動作のことです。詳しくは専門的な文献を参照していただくのが良いと思います。

静歩行動作の実装結果

 今回実装した静歩行動作の動画へのリンクを、以下に示します。GitHubのissueの方に動画を上げています。(はてなに動画は貼れないようなので...)

github.com

また、静歩行動作を実装したリポジトリも以下に示します。branchはmainを使用してください。それ以外だと歩いてくれません。

github.com

静歩行動作の内部

 ここでは、静歩行動作を実現した、ソフトウェアの実行の流れを示します。以下に、流れを表した図を示します。

基本的に、上に示したものが全てです。
改めて書くと、

  1. ロボットのローカル座標系で、脚の置く位置座標を歩行パターンとする。その歩行パターンと、脚の各関節の回転速度、制御周期を適当に決める。
  2. 歩行パターンから逆運動学を解くことで、脚の各関節の回転角度を得る。
  3. 得られた脚の各関節の回転角度をロボットに適用して、動かす。

この3段階を1ステップとして、歩行パターンを1ステップごとに順に読んでいく、という流れで実行しています。今回の場合はゴール地点を決めていないので、歩行パターンをループさせて、永遠に歩けるように組んでいます。
 実世界の場合は外乱が必ず存在しますが、今回のシミュレーション環境は外乱やオフセットが存在しない理想的な環境となっているので、静歩行動作で転倒することはないはずです。(ただ、プログラムによっては転倒します。現在もプログラムが原因で途中で転倒してしまいます。)

ソフトウェア内部での処理の流れは、以下のようになっています。


WalkingPatternGenerator
 ↓
 ↓ Service IK request
 ↓
IK
 ↓
 ↓ Service IK response
 ↓
WalkingPatternGenerator
 ↓
 ↓ Publish walking_pattern
 ↓
 ↓ Subscribe walking_pattern
 ↓
WalkingStabilizationController

WebotsRobotHandler
 ↓
 ↓ Service WalkingStabilization Controller repuest
 ↓
WalkingStabilizationController
 ↓
 ↓ Service IK request
 ↓
IK
 ↓
 ↓ Service IK response
 ↓
WalkingStabilizationController
 ↓
 ↓ Service WalkingStabilizationController response
 ↓
WebotsRobotHandler
 ↓
ROBOTIS OP2


上記2つの流れを(恐らく)並行して実行しており、WalkingStabilizationControllerには複数がアクセスしている状態になっています。(ここがバグの温床にならないことを祈るばかりだが、既に転倒の原因はここにありそう...)

まとめ

 今回は、静歩行動作を実装しました。理論的な部分は逆運動学だけで、他の値は実験的に与えた感じなので、結構簡単に実装することができました。
 次からは本題の、動歩行動作の実装に向けた活動をしていきます。

github.com

歩行制御ソフトウェアの、ソフトウェア構成とコードの整理 [ヒューマノイド動歩行]

はじめに

2月はじめ頃から先週まで約2ヶ月間に渡り、ソフトウェア構成の構想と、それに基づいたコードの整理をおこなってきました。

odome.hatenablog.com

odome.hatenablog.com

それが完了したので、その結果をここにまとめていきます。

なぜソフトウェア構成とコードの整理が必要だったのか

 1番の理由は、メンテナンス性や可読性、拡張性が皆無になってしまっていたことが挙げられます。元々機能をnodeごとに分ける構想は合ったのですが、右往左往しながら機能を継ぎ足していった結果、「動くけど自分にしか理解できないコード」が生まれてしまいました。これではOSSである価値はほとんどなく、今後の開発にも良くないのは明らかです。なのでこの2ヶ月間、拡張性とメンテナンス性を考慮したソフトウェア構成の構想とコードの整理に取り組みました。
 OSSにする以上、ソフトウェアの拡張性やメンテナンス性、コードの可読性は重要視する必要のある要素だとも思いますしね。

今回からの変更点

 移行にあたり、同時に色々と変更点があるので、以下にまとめます。

 これに加えて、Humbleに移行したことでwebots_ros2もHumble対応版に移行する必要があります。webots_ros2はfoxyとhumbleで多少仕様が変わってて、Webots関連の型宣言周りが変更されています。なので、Foxyで開発したものをHumbleで動かすことはできないはずです(私はそうでした)。詳しくは私のRepositoryのCommit logを見てもらえれば良いと思います。

ソフトウェア構成の構想と決定

 ソフトウェアの構成を考える上では、以下のことを意識しました。

  • シンプルさ:シンプルであることで、構成を理解しやすくなる。
  • node同士の疎な関係:疎であることで、nodeの切り離しが容易になる。
  • 基本的な歩行パターン生成器の構成に準拠:歩行パターンジェネレータ、歩行安定化制御系、ロボット、この3要素で構成する。

 1つの完成形として、使いたい歩行パターン生成法や歩行安定化制御系に応じて、nodeの付け替えだけでそれに対応できるようにしたいと考えています。
 あと、制御ソフトウェアであることから周期とリアルタイム性も重要な要素としてあります。しかし今回はシミュレータ上であることと私の知識不足が著しいことから、決めるのは後回しということにしています。

 以上のことを踏まえて、以下のソフトウェア構成を考案しました。

<ここにソフトウェア構成図。既存のやつのnode名を調整したやつ>
<ここにソフトウェア構成についての説明>

ソフトウェア構成に基づくコードの整理

 以上のソフトウェア構成に基づいて、プログラム全体とコードを整理しました。以下、ソースコードを公開しているGitHubです。

github.com

 また、ディレクトリ・ファイル構造は以下のようになっています。(ソフトウェア構成内に無い無関係なものは除外しています)

.
├── kinematics
│   ├── CMakeLists.txt
│   ├── include
│   │   └── kinematics
│   │       ├── FK.hpp
│   │       └── IK.hpp
│   ├── package.xml
│   └── src
│       ├── FK.cpp
│       ├── FK_main.cpp
│       ├── IK.cpp
│       └── IK_main.cpp
├── msgs_package
│   ├── CMakeLists.txt
│   ├── msg
│   │   └── ToWalkingStabilizationControllerMessage.msg
│   ├── package.xml
│   └── srv
│       ├── ToKinematicsMessage.srv
│       └── ToWebotsRobotHandlerMessage.srv
├── walking_pattern_generator
│   ├── CMakeLists.txt
│   ├── include
│   │   └── walking_pattern_generator
│   │       └── WalkingPatternGenerator.hpp
│   ├── package.xml
│   └── src
│       ├── WalkingPatternGenerator.cpp
│       └── WalkingPatternGenerator_main.cpp
├── walking_stabilization_controller
│   ├── CMakeLists.txt
│   ├── include
│   │   └── walking_stabilization_controller
│   │       └── WalkingStabilizationController.hpp
│   ├── package.xml
│   └── src
│       ├── WalkingStabilizationController.cpp
│       └── WalkingStabilizationController_main.cpp
└── webots_robot_handler
    ├── CMakeLists.txt
    ├── include
    │   └── webots_robot_handler
    │       └── WebotsRobotHandler.hpp
    ├── launch
    │   ├── __pycache__
    │   │   └── robot_launch.cpython-38.pyc
    │   ├── robot_launch.py
    │   └── start_launch.py
    ├── package.xml
    ├── resource
    │   ├── webots_robot_handler
    │   └── webots_robotis_op2_description.urdf
    ├── src
    │   └── WebotsRobotHander.cpp
    ├── webots_robot_handler.xml
    └── worlds
        └── webots_simple_world.wbt

 見てわかるように、コードはC++で記述し、Launchの記述にはPython3を使用しています。

 今のところ通信はプロセス間でやる想定で組んでいますが、いずれプロセス内通信を部分的に適用しようと考えています。
 また、Life Cycle の適用も考えています。Parameterの取得やinit動作があることを考慮すると、採用すればよりキレイな構成になると見込んでいます。まだ構成は白紙ですが、今年中には取り組みたい課題です。

起動方法(Ubuntu系)

 現段階での起動方法(Ubuntu系)をここに示します。あくまで現段階なので、今後変わる可能性が大きいです。
 他の必要Packageは、ROS 2 HumbleWebotsR2023awebots_ros2(humble)です。それぞれのインストール方法は、リンク先をご参照ください。
1. まず、ROS 2のWorkSpaceを作成します。既存のものがあればそれでもOKです。

mkdir -p ~/ros2_ws/src/
cd ~/ros2_ws/src/
  1. 次に、GitHubからRepositoryをCloneしてきます。現在のDefault Branchは開発用のものになっているので、必要に応じてBranchを変更してください。基本的にdevelが最新で、一段落しているVersionがmainという感じです。
git clone https://github.com/open-rdc/ROS2_Walking_Pattern_Generator.git
  1. 最後に、buildとsetupをした後にLaunchファイルから起動すればOKです。
cd ~/ros2_ws/
colcon build --symlink-isntall
. install/setup.bash
ros2 launch webots_robot_handler start_launch.py

これで、以下のようにWebotsが起動して、ロボットが動くはずです。(開発途中なので、この通り動かなかったらごめんなさい)

起動時の様子


最後に

 こんな感じで、開発環境の移行と、ソフトウェア構成に則ってコードを整理しました。現在の進行状況として、運動学とRobotHandlerはほぼ完了しているので、WalkingPatternGeneratorとWalkingStabilizationControllerに取り組んでいく予定です。その2つが完成すれば、目標達成となるでしょう。

[第23週〜(随時更新)] ソフトウェアの整理 再構築 [ヒューマノイド動歩行]

先週の振り返り

 先週の記事では、ソフトウェア構成を考えました。今回はその構成を元に、ソフトウェアを再構築していきます。

odome.hatenablog.com

今週の概要

 先週までに考えたソフトウェア構成を元に、現在のソフトウェアを再構築していきます。つまりは、書き直しです。この記事では、毎週、または隔週で、再構築の進捗を淡々と記録していきます。
 以下に、先週決めたソフトウェア構成図を示します。ただ、今後変わる可能性もあります。

ソフトウェア構成

進捗記録

~2023/2/20

  • 整理用のbranch - tidy-up - を作成
     開発branch - devel - で整理をすると、結構ぐちゃぐちゃになってしまうので、整理用のbranchを作成した。そこで整理をおこなう。

  • 整理の開始
     ひとまず、現状のコードを参考に部品ごとにプログラムを分けて、別々でコードを記述することにした。現状では、以下のように分けている。

    ROS2_Walking_Pattern_Generator
    | --- TidyBox
       | --- FK.hpp:順運動学を解くプログラムのヘッダファイル
       | --- FK.cpp:順運動学を解くプログラム
       | --- IK.hpp:解析的な逆運動学を解くプログラムのヘッダファイル
       | --- IK.cpp:解析的な逆運動学を解くプログラム
       | --- Kinematics.CMakeLists:運動学のプログラムのCMakeLists.txt
       |
       | --- WalkingPatternGenerator.hpp:歩行パターンジェネレータのプログラムのヘッダファイル
       | --- WalkingPatternGenerator.cpp:歩行パターンジェネレータのプログラム
       | --- WalkingPatternGenerator.CMakeLists:歩行パターンジェネレータのプログラムのCMakeLists.txt
       |
       | --- WalkingStabilizationController.hpp:歩行安定化制御系のプログラムのヘッダファイル
       | --- WalkingStabilizationController.cpp:歩行安定化制御系のプログラム
       | --- WalkingStabilizationController.CMakeLists:歩行安定化制御系のプログラムのCMakeLists.txt
       |
       | --- WebotsRobotHandler.hpp:C++Pluginのプログラムのヘッダファイル
       | --- WebotsRobotHandler.cpp:C++Pluginのプログラム
       | --- WebotsRobotHandler.CMakeLists:C++PluginのプログラムのCMakeLists.txt
       |
       | --- Launch.py:Launchファイル(packageごとに必要なはず)
       | --- ToKinematics_msgs:Kinematicsとの通信に用いるmessage型の定義ファイル
       | --- ToCppPlugin_msgs:C++Pluginとの通信に用いるmessage型の定義ファイル
       | --- ToWalkingStabilizationController_msgs:歩行安定化制御系への通信に用いるmessage型の定義ファイル

     現状はこのように分けているが、ファイル名などをコロコロと変えるかもしれない。
     まずは上記のように分けたプログラムをそれぞれ完成させ、各プログラムの動作をテストする単体テストに移行する方針でいる。

2023/3/9

  • 新Packageの作成
     TidyBox内でのコード作成が進んできて、もうそろそろ単体テストが近づいてきたので、ソフトウェア構成案に沿って複数のPackageを生成した。以下、現段階での想定Package構成。

    root(ROS2_Walking_Pattern_Generator)
    | --- kinematics:汎用的な運動学を解くnodeを提供するpackage
    | --- include
    | --- kinematics
    | --- FK.hpp
    | --- IK.hpp
    | --- src
    | --- FK.cpp
    | --- IK.cpp
    | --- package.xml
    | --- CMakelists.txt
    |
    | --- msgs_package:message_fileの定義をするpackage
    | --- include
    | --- msgs_package
    | --- src
    | --- msg
    | --- ToWalkingStabilizationControllerMessage.msg
    | --- srv
    | --- ToKinematicsMessage.msg
    | --- ToWebotsRobotHandlerMessage.msg
    | --- package.xml
    | --- CMakelists.txt
    |
    | --- walking_pattern_generator:歩行パターンを提供するpackage
    | --- include
    | --- walking_pattern_generator
    | --- WalkingPatternGenerator_hpp
    | --- src
    | --- WalkingPatternGenerator.cpp
    | --- launch
    | --- robot_launch.py
    | --- resource
    | --- walking_pattern_generator
    | --- webots_robotis_op2_description.urdf
    | --- worlds
    | --- webots_simple_world.wbt
    | --- package.xml
    | --- CMakelists.txt
    |
    | --- walking_stabilization_controller:歩行安定化制御系を提供するpackage
    | --- include
    | --- walking_stabilization_controller
    | --- WalkingStabilizationController.hpp
    | --- src
    | --- WalkingStabilizationController.cpp
    | --- package.xml
    | --- CMakelists.txt

     基本的にこの構成を基準にいく。まだ雑多なので、徐々にきれいにしていきたい。各nodeのコード作成も進行中。ライフサイクルの機能も入れたいが、難しく理解が追いついていないので、理解ができたら構成の中に組み込んでいきたい。


  • message_fileの定義
     message_fileを定義した。今後変わる可能性はあるが、まずはビルドをしたいので、ひとまずコレでいく。

    ToKinematicsMessage.msg:KinematicsとのServiceに用いるmessage
     request
      double[6] R_target_R
      double[3] P_target_R
      double[6] Q_target_R
      double[6] R_target_L
      double[3] P_target_L
      double[6] Q_target_L
     response
      double[3] P_result_R
      double[6] Q_result_R
      double[3] P_result_L
      double[6] Q_result_L

    ToWebotsRobotHandlerMessage.msg:WebotsRobotHandlerとWalkingStabilizationControllerのServiceに用いるmessage
     request
      double[3] Accelerometer_now
      double[3] Gyro_now
      double[6] Q_now_R
      double[6] Q_now_L
     response
      double[3] Q_fix_R
      double[6] dQ_fix_R
      double[3] Q_fix_L
      double[6] dQ_fix_L

    ToWalkingStabilizationControllerMessage.msg:WalkingStabilizationControllerとWalkingPatternGeneratorのTopic通信に用いるmessage
      double[3] P_target_R
      double[6] Q_target_R
      double[6] dQ_target_R
      double[3] P_target_L
      double[6] Q_target_L
      double[6] dQ_target_L

     messageのサイズが非常に大きくなりそうだが、その影響は後々分かってくるだろう。問題があり次第、その都度修正を行う予定。

2023/3/24

  • パッケージ kinematics, msgs_package, webots_robot_handlerの完了
     以上の3つのパッケージの作成(現時点)を完了した。今回のソフトウェアの整理で目標としていたものが完成した。詳しくは、GitHubのtidy-up branchを確認していただきたい。
  • (既存のbugの修正)
     message_fileに関しても、多少の修正を加えている。errorと変更どちらもおこなった。

    今後は、各node同士の通信周りを構築して、結合テスト・全体テストに合格できれば、今回のソフトウェアの整理は完了となる。3月中には終わらせたい。

2023/4/2

  • ソフトウェア構成とコードの整理が完了した。詳細は改めてまとめる。

[第19週〜第22週] 歩行パターン生成器のソフトウェア構成を錬る [ヒューマノイド動歩行]

先週までの振り返り

 先週の記事では、逆運動学のプログラムを作成・実装し、Webots内のロボットを実際に動かすことができました。

odome.hatenablog.com


今週の概要

 先週までで逆運動学が解けてロボットを動かすところまでいけました。ちょうど一区切りついたことと、今のコードが非常に煩雑になっていることから、今週からはソフトウェア構成の策定と現コードの整理、そして開発環境のバージョンアップをしようと思います。

  • ソフトウェア構成の策定
  • コードの整理と書き直し
  • ROS2のバージョンをFoxyからHumble、Webotsのバージョンを2022bから2023aに、開発OSをUbuntu20.04からUbuntu22.04に移行

進捗記録

~2023/1/26

ソフトウェア構成の第1案を作成(-> 要再検討)

  • ソフトウェア構成の第1案を考えた。手書きで超見づらい。

    ソフトウェア第1案

  • 今回考えているヒューマノイドロボットの制御ソフトウェアは、大きく分けて3つの部品に分けている。以下、私の部品に対する理解。

  • ロボット本体
     制御対象のロボットそのもので、関節のアクチュエータに対する入力と、姿勢やアクチュエータの現在値などの出力を持っている。
  • 歩行パターンジェネレータ
     開発中の制御ソフトウェアにおいて、一番のコアとなる部分。次に足を置く場所に脚を動かすために、対象のロボットのアクチュエータに対する命令を出力する。入力は(いまのところ)無いと考えている。安定化を考えなければ、ロボット本体と歩行パターンジェネレータだけで歩行が実現できる。が、現実的ではない。
  • 歩行安定化制御系
     文字通り、歩行を安定に行うための部品。ロボットを安定して歩行させるために、ロボット本体から出力される姿勢情報を元に、歩行パターンジェネレータから出力される命令に補正をかけて、ロボットのアクチュエータに命令を出力する。つまり、歩行パターンジェネレータとロボット本体の間に置く部品で、ロボットからのフィードバックを元に命令に補正をかける部品。

    (制御の構成の参考:梶田秀司、『ヒューマノイドロボット(改訂2版)』)

  • この構成には問題がある。
     特にTopic通信で同期を取ろうとしているところ。pub/subの周期を合わせれば同期が取れそうだが、遅延が必ず発生するので難しい。フラグで同期を取る方法も、二重に通信が必要になって、疎な通信とは言えなくなっている。
     この問題を改善した第2案を考える必要がある。

開発環境のバージョンアップに関する調査

  • 開発環境を移行するために、まず現コードをROS2 Humbleでコンパイルしたときのエラーを確認した。
     結果、Webots関連の型宣言でエラーが出た。その原因を探るためにwebots_ros2のソースコードWikiを確認したところ、Webots独自の型宣言の方法が変わっていた。このことから、型宣言方法を変えてやることが必要であることを確認できた。
     この結果から、まずは型宣言の箇所を書き換えて、またコンパイラを通してみることにする。

~2023/2/8

ソフトウェア構成の第2、3案を考案

  • まず、第1案の問題点を踏まえて、以下の第2案を考案した。Serviceだけでまとめたことで、結構すっきりした。

 ただ、歩行パターンジェネレータから歩行安定化制御系への通信は、歩行安定化制御系から歩行パターンジェネレータへのfeedbackは必要ない。また、FK・IKが歩行パターンジェネレータと歩行安定化制御系両方で必要となるであろうことから、FK・IKは別nodeに分けたほうがいいだろう。

  • 以上の第2案を踏まえて、以下の第3案を考案した。FK・IKnodeを作り、通信方法も一部変更した。

ソフトウェア構成第3案

 PluginがClient・歩行安定化制御系がServerとなる非同期のService、歩行パターンジェネレータがPublisher・歩行安定化制御系がSubscriberとなるTopic通信で通信経路を組んだ。こうすることで、歩行パターンジェネレータが司令塔となり、他nodeがそれに従属する形にすることができた。
 また、FK・IKnodeを作成し、歩行パターンジェネレータや歩行安定化制御系との間を同期型Serviceで接続した。これでFK・IKの処理プログラムを1つにまとめることができた。(DeadLockには注意が必要だが)
 処理の流れとしては、
1. 歩行パターンジェネレータで、その歩行パターンに掛かる時間を計算し、それに合わせて歩行安定化制御系にPublishする。
2. 歩行安定化制御系は定期的にPulginからロボットの姿勢情報のfeedbackを受け、現在の歩行パターンと姿勢情報から安定した姿勢をとるための関節角度をPulginに出力する。

ひとまずは、この第3案をソフトウェア構成として、開発を進めていくこととする。

~2023/2/20

ソフトウェア構成・第3案の整理とより詳細な検討

  • 手書きで雑多な構成案をまとめ直した。上記で示した手書きの第3案を、下記のようにきれいにまとめ直した。書かれていることは同じ。

ソフトウェア構成案3-再構築

  • より詳細な構成の検討。特にROS2まわり。
    • 各nodeをそれぞれ別packageで管理する方法。
        別にすることで管理のしやすさを確保する。
    • FK, IKを含む、Kinematics周りのnodeを、別々に分けるか1つにまとめるか問題。
        別nodeに分ければ通信で扱われるmessageが持つ情報を少なくできる(例えば、IKとFKを同serviceにまとめると各関節角度と目標位置をまとめて送受信する必要があるが、別serviceつまり別nodeにすればそのどちらかを送受信すれば良くなり、messageが持つ情報を少なくできる)。しかしこの方法だと、IKでもFKを使うため、冗長にならざる負えない(FKnodeとIKnodeを繋がなかったとしても)。
        一方で1つのnodeにまとめた場合、最小限のコードで済む反面、messageが持つ情報量が大きくなってしまう。また、起きない見込みではあるが、DeadLockなど、共通資源であることから通信が競合しないかも問題としてあり、1つのnodeにまとめたほうが競合が起こりやすい。
        ひとまず、第3案では1つのnodeに収めるとしたが、現在でも迷っている。いずれにしろ、どちらかの形で開発した後に、検証できればいいと思う。

[第15週〜第18週] 逆運動学を解いてロボットを制御する プログラム作成・実装(解析的手法)編 [ヒューマノイド動歩行]

先週までの振り返り

 先週の記事では、逆運動学を解くためにプログラムの試作などを行いました。
 数値解法への挑戦、pinocchioの試用、解析的手法で腕と脚に挑戦などを行いました。

odome.hatenablog.com


今週の概要

 先週までにおこなった逆運動学の解析的手法に関して、脚を解析的に解き、プログラムに起こします。そして、そのプログラムをWebots_ros2に実装してロボットを制御します。

進捗記録

2022/12/29

  1. 逆運動学を解析的に解く
    ・ROBOTIS-OP2の左脚の逆運動学を解析的に解いた。ROBOTIS-OP2の左脚の機構とステータスは、以下のようになっている。ここでは左脚としているが、world -> p1 のリンクのy要素の正負を逆にすれば右脚となる。
    左脚の構成とステータス

    参考: GitHub, cyberbotics, "webots/projects/robots/robotis/darwin-op/protos/Darwin-op.proto", https://github.com/cyberbotics/webots/blob/R2022b/projects/robots/robotis/darwin-op/protos/Darwin-op.proto
    GitHub, ROBOTIS-GIT, "ROBOTIS-OP2-Common/robotis_op2_description/urdf/robotis_op2.structure.leg.xacro", https://github.com/ROBOTIS-GIT/ROBOTIS-OP2-Common/blob/master/robotis_op2_description/urdf/robotis_op2.structure.leg.xacro

これを元に、解析的に逆運動学を解くと、以下の式を得る。

逆運動学の解析解

参考: 梶田秀司, 『ヒューマノイドロボット(改訂2版)』, 第1刷, p65~p67

  1. 解析解を元にプログラムを作成する
    ・上で得られた解を元に、プログラムに起こして正確な解が得られるかを確認する。
    ・実装する場合はC++で記述する予定だが、今回は試作に近い段階であることからPython3で記述している。(個人の練度的にPythonのほうが作りやすい)
     拙いが、以下のプログラムを作成した。
#!/usr/bin/env python3

import numpy as np

def identityMatrix():
    return np.matrix([[1, 0, 0],
                      [0, 1, 0],
                      [0, 0, 1]])

def Rx(theta1):
    return np.matrix([[1, 0            , 0               ], 
                      [0, np.cos(theta1), -np.sin(theta1)], 
                      [0, np.sin(theta1), np.cos(theta1) ]]
    )
def Ry(theta2):
    return np.matrix([[np.cos(theta2) , 0, np.sin(theta2)],
                      [0              , 1, 0             ],
                      [-np.sin(theta2), 0, np.cos(theta2)]]
    )
def Rz(theta3):
    return np.matrix([[np.cos(theta3), -np.sin(theta3), 0],
                      [np.sin(theta3), np.cos(theta3) , 0],
                      [0             , 0              , 1]]
    )

Pw1 = np.matrix([-0.005, 0.037, -0.1222])
P12 = np.matrix([0, 0, 0])
P23 = np.matrix([0, 0, 0])
P34 = np.matrix([0, 0, -0.093])
P45 = np.matrix([0, 0, -0.093])
P56 = np.matrix([0, 0, 0])

# Target_Point
Rw6_target = identityMatrix()
Pw6_target = np.array([0.05, 0.1, -0.194])


""" IK """
qqq = Pw1 - Pw6_target
P16_target = np.array((Rw6_target * qqq.T).T)[0]

a = np.abs(P34[0,2])
b = np.abs(P45[0,2])
c = np.sqrt(P16_target[0]**2 + P16_target[1]**2 + P16_target[2]**2)

Q3 = -np.arccos((a**2 + b**2 - c**2)/(2 * a * b)) + np.pi
alfa = np.arcsin((a * np.sin(np.pi - Q3))/c)
Q4 = -np.arctan2(P16_target[0], np.sign(P16_target[2]) * np.sqrt(P16_target[1]**2 + P16_target[2]**2)) - alfa
Q5 = np.arctan2(P16_target[1], P16_target[2])
# print(P16_target[1], P16_target[2], np.arctan2(P16_target[1], P16_target[2]))
# When general Rw6_target
R1_2_3 = np.dot(Rw6_target, np.dot(Rx(Q5).T, np.dot(Ry(Q4).T, Ry(Q3).T)))
Q0 = np.arctan2(-1*R1_2_3[0, 1], R1_2_3[1, 1])
Q1 = np.arctan2(R1_2_3[2, 1], -1*R1_2_3[0, 1] * np.sin(Q0) + R1_2_3[1, 1] * np.cos(Q0))
Q2 = np.arctan2(-1*R1_2_3[2, 0], R1_2_3[2, 2])
# When identity-matrix Rw6_target
# Q[0] = np.arctan2(0, np.cos(Q[5]))
# Q[1] = np.arctan2(np.sin(Q[5]), np.cos(Q[5]) * np.cos(Q[0]))
# Q[2] = np.arctan2(np.cos(Q[5]) * np.sin(Q[3] + Q[4]), np.cos(Q[5]) * np.cos(Q[3] + Q[4]))

Q = np.array([Q0, Q1, Q2, Q3, Q4, Q5])

print("target[m]: ", Pw6_target)
print("Angles[rad]: ", Q, ",\n______[deg]: ", (Q*180/np.pi).astype(np.int16))


""" FK """
Rw1 = Rz(Q0)
R12 = Rx(Q1)
R23 = Ry(Q2)
R34 = Ry(Q3)
R45 = Ry(Q4)
R345 = Ry(Q2+Q3+Q4)
R24 = Ry(Q2+Q3)
R56 = Rx(Q5)

Pw1 = np.matrix([[-0.005], [0.037], [-0.1222]])
P12 = np.matrix([[0], [0], [0]])
P23 = np.matrix([[0], [0], [0]])
P34 = np.matrix([[0], [0], [-0.093]])
P45 = np.matrix([[0], [0], [-0.093]])
P56 = np.matrix([[0], [0], [0]])
P6a = np.matrix([[0], [0], [0]])

FK_result = Rw1 @ R12 @ R24 @ P45 + Rw1 @ R12 @ R23 @ P34 + Pw1

print("FK_result[m]: ", FK_result.T, "\n")

github.com

アルゴリズムは手書きした式とほぼ同じで、それをプログラムに起こしただけとなっている。

・このプログラムを実行することで、以下の結果が得られた。

target: 目標位置, Angles: IKを解いた結果得られた各関節角度[PelvYL, PelvL, ~, FootL], FK_result: IKの結果を用いて解いたFKの結果
-----axis-z-----
target[m]:  [[-0.005   0.037  -0.1687]]
Angles[rad]:  [-0.          0.         -1.31811607  2.63623214 -1.31811607  0.        ] ,
______[deg]:  [  0   0 -75 151 -75   0]
FK_result[m]:  [[-0.005   0.037  -0.1687]] 

target[m]:  [[-0.005   0.037  -0.2152]]
Angles[rad]:  [-0.          0.         -1.04719755  2.0943951  -1.04719755  0.        ] ,
______[deg]:  [  0   0 -59 119 -59   0]
FK_result[m]:  [[-0.005   0.037  -0.2152]] 

target[m]:  [[-0.005   0.037  -0.2617]]
Angles[rad]:  [-0.          0.         -0.72273425  1.4454685  -0.72273425  0.        ] ,
______[deg]:  [  0   0 -41  82 -41   0]
FK_result[m]:  [[-0.005   0.037  -0.2617]] 
-----
-----axis-x-----
target[m]:  [ 0.15    0.037  -0.2152]
Angles[rad]:  [-0.          0.         -1.26831795  0.47588225  0.7924357   0.        ] ,
______[deg]:  [  0   0 -72  27  45   0]
FK_result[m]:  [[ 0.15    0.037  -0.2152]] 

target[m]:  [ 0.05    0.037  -0.2152]
Angles[rad]:  [-0.          0.         -1.48504012  1.90193948 -0.41689936  0.        ] ,
______[deg]:  [  0   0 -85 108 -23   0]
FK_result[m]:  [[ 0.05    0.037  -0.2152]] 

target[m]:  [-0.1     0.037  -0.2152]
Angles[rad]:  [-0.          0.          0.02150706  1.549058   -1.57056506  0.        ] ,
______[deg]:  [  0   0   1  88 -89   0]
FK_result[m]:  [[-0.1     0.037  -0.2152]] 
-----
-----axis-y-----
target[m]:  [-0.005   0.15   -0.2152]
Angles[rad]:  [-0.          0.88218221 -0.66515354  1.33030708 -0.66515354 -0.88218221] ,
______[deg]:  [  0  50 -38  76 -38 -50]
FK_result[m]:  [[-0.005   0.15   -0.2152]] 

target[m]:  [-0.005   0.1    -0.2152]
Angles[rad]:  [-0.          0.59540988 -0.92238109  1.84476219 -0.92238109 -0.59540988] ,
______[deg]:  [  0  34 -52 105 -52 -34]
FK_result[m]:  [[-0.005   0.1    -0.2152]] 
-----
target[m]:  [ 0.05   0.1   -0.194]
Angles[rad]:  [-0.          0.72020877 -1.45894155  1.87302709 -0.41408554 -0.72020877] ,
______[deg]:  [  0  41 -83 107 -23 -41]
FK_result[m]:  [[ 0.05   0.1   -0.194]] 

github.com

この結果から分かるように、targetと逆運動学の解析解が一致している。このことから、このプログラムであれば正しい逆運動学の解析解が得られることが分かる。

・以上のことから、実装するプログラムは上記のプログラムと同じアルゴリズムを用いることにする。


2023/1/18

1.試作プログラム(C++編)
・上記で示したPythonの試作プログラムとそのアルゴリズムを元に、C++で試作プログラムを書いた。C++で行列計算を行うために、Eigen3という、行列計算に特化した外部ライブラリを用いている。
・以下に、ソースコードと出力結果を示す。

draft_LeftLeg_IK.cpp

#include "iostream"
#include "cmath"
#include "Eigen/Dense"

bool DEBUG = true;
#define PI 3.141592

using namespace Eigen;
using namespace std;


Matrix3d Rx(double theta) {
    Matrix3d R;
    R << 1, 0, 0,
         0, cos(theta), -sin(theta),
         0, sin(theta), cos(theta);
    return(R);
}
Matrix3d Ry(double theta) {
    Matrix3d R;
    R << cos(theta) , 0, sin(theta),
         0          , 1, 0         ,
         -sin(theta), 0, cos(theta);
    return(R);
}
Matrix3d Rz(double theta) {
    Matrix3d R;
    R << cos(theta), -sin(theta), 0,
         sin(theta), cos(theta) , 0,
         0         , 0          , 1;
    return(R);
}
Matrix3d IdentifyMatrix(void) {
    Matrix3d R;
    R << 1, 0, 0,
         0, 1, 0,
         0, 0, 1;
    return(R);
}

double sign(double arg) {
    return((arg >= 0) - (arg < 0));
}


namespace Parameters {
    Matrix3d R_target;
    Vector3d P_target;

    array<Vector3d, 7> P;

    array<float, 6> Q;

    void set_Parameters(void) {
        // パラメータの正式な取得方法は、は他ファイルに記述してそれを読み込む、 他nodeから読み込む
        if(DEBUG == true) {
            Vector3d P_w1(-0.005, 0.037, -0.1222);
            Vector3d P_12(0, 0, 0);
            Vector3d P_23(0, 0, 0);
            Vector3d P_34(0, 0, -0.093);
            Vector3d P_45(0, 0, -0.093);
            Vector3d P_56(0, 0, 0);
            Vector3d P_6a(0, 0, 0);

            P = {P_w1, P_12, P_23, P_34, P_45, P_56, P_6a};
        }
        else if(DEBUG ==false) {
            // 正式な処理
        }
    }
}


namespace Kinematics {
    using namespace Parameters;

    Vector3d FK(void) {
        array<Matrix3d, 6> R;
        R = {Rz(Q[0]), Rx(Q[1]), Ry(Q[2]), Ry(Q[3]), Ry(Q[4]), Rz(Q[5])};

        return (
            R[0] * R[1] * R[2] * R[3] * R[4] * R[5] * P[6] 
            + R[0] * R[1] * R[2] * R[3] * R[4] * P[5]
            + R[0] * R[1] * R[2] * R[3] * P[4]
            + R[0] * R[1] * R[2] * P[3]
            + R[0] * R[1] * P[2]
            + R[0] * P[1]
            + P[0]
        );
    }

    void IK(void) {
        Vector3d P_16;
        P_16 = R_target.transpose() * (P[0] - P_target);

        double A, B, C;
        A = abs(P[3](2));
        B = abs(P[4](2));
        C = sqrt(pow(P_16(0), 2) + pow(P_16(1), 2) + pow(P_16(2), 2));

        Q[3] = -1 * acos((pow(A, 2) + pow(B, 2) - pow(C, 2)) / (2 * A * B)) + PI;
        
        double alfa;
        alfa = asin((A * sin(PI - Q[3])) / C);

        Q[4] = -1 * atan2(P_16(0), sign(P_16(2)) * sqrt(pow(P_16(1), 2) + pow(P_16(2), 2))) - alfa;
        Q[5] = atan2(P_16(1), P_16(2));

        Matrix3d R_w3;
        R_w3 = R_target * Rx(Q[5]).transpose() * Ry(Q[4]).transpose() * Ry(Q[3]).transpose();

        Q[0] = atan2(-R_w3(0, 1), R_w3(1, 1));
        Q[1] = atan2(R_w3(2, 1), -R_w3(0, 1) * sin(Q[0]) + R_w3(1, 1) * cos(Q[0]));
        Q[2] = atan2(-R_w3(2, 0), R_w3(2, 2));
    }
}


int main(void) {
    using namespace Parameters;

    // 最初に、パラメータ設定を行う
    bool SETUP;  // 他nodeからのフラグで決めたい
    if(DEBUG == true) {SETUP = true;}
    if(DEBUG == true || SETUP == true) {set_Parameters();}

    if(DEBUG == true) {
        Q = {0, 0, 0, 0, 0, 0};
        R_target = IdentifyMatrix();
        P_target << -0.005, 0.037, -0.1687;
    }
    else if(DEBUG ==false) {  // 他nodeからの情報を記録
        Q = {0, 0, 0, 0, 0, 0};
        R_target = IdentifyMatrix();
        P_target << -0.005, 0.037, -0.1687;
    }

    Kinematics::IK();
    Vector3d result_FK;
    result_FK = Kinematics::FK();

    cout << "Target Points[m]: " << P_target.transpose() << endl; 
    cout << "Target Rotation-Matrix: \n" << R_target << endl;
    cout << "Result IK: Joint-Angles[rad]: ";
    for(const auto &el : Q) {
        cout << el << ", ";
    }
    cout << endl;
    cout << "Result FK: Target Points[m]: " << result_FK.transpose() << endl;


    return(0);
}
Target Points[m]:  -0.005   0.037 -0.1687
Target Rotation-Matrix: 
1 0 0
0 1 0
0 0 1
Result IK: Joint-Angles[rad]: -0, 0, -1.31811, 2.63623, -1.31812, 0, 
Result FK: Target Points[m]: -0.00500004       0.037     -0.1687

・以上の出力結果から、Pythonでの試作プログラムと同様に、正しい逆運動学解が得られていることが分かる。このプログラムをWebots_ros2の外部Pluginに適用してやれば、逆運動学解をWebots上のロボットに反映できるだろう。

2.Webots上のロボットを動かす
・上記のC++の試作プログラムを、そのまま外部PluginのWalkingPatternGenerator.cppに適用して、Webots上のロボットを動かした。
・上記のC++試作プログラムの出力結果に示されている関節角度[rad]をWebots上のロボットの左脚に適用した。

・以下に、その動作後の様子を示す。左脚をただ動かしただけなので、当然、自立できずに転倒してしまっているが、脚の関節角度は指示通りであることが見て取れる。

以上で、逆運動学解を元にロボットを動かせた。