HEBI Robotics actuator

HEBI smart actuator　X-series を制御するためにやったこととメモ
# HEBI actuatorとは
https://docs.hebi.us/#

HEBI X-Series（以下HEBIモータ）はモータ・ドライバ・エンコーダが一つになったスマートアクチュエータです．FamilyNameとModuleNameを設定することで，複数台に同時に同じコマンドを送ったり，ロボットの部分（車輪やアーム）ごとの制御を簡単に行うことが出来ます．

# HEBI Scope
HEBIを直感的に操作するためのGUIアプリケーション．[Downloadページ](https://docs.hebi.us/downloads_changelogs.html)から様々なOSにインストールできる．
HEBI Scopeを用いた基本的な利用方法については，[こちらの記事](https://qiita.com/sorasso/items/f3a34922533c193608d8)を参考にしてください．

## 古いUbuntuへのインストール
:::note warn
HEBI Scopeは「libgcc-s1」パッケージに依存しているため，それを入れないとScopeもインストールできない．しかし，古いUbuntuではインストールコマンドを打っても「『libgcc-s1』なんて見つからないよ」と返される．
:::

以下の手順でインストールする
1. 「.deb」ファイルから必要なパッケージをインストールインストール
2. HEBI Scopeを通常通りインストール
    ```cpp:terminal
    $ cd ~/Download
    
    $ wget http://mirrors.edge.kernel.org/ubuntu/pool/main/g/gcc-10/gcc-10-base_10-20200411-0ubuntu1_amd64.deb
    
    $ wget http://mirrors.xmission.com/ubuntu/pool/main/g/gcc-10/libgcc-s1_10-20200411-0ubuntu1_amd64.deb
    
    ///////////////////////////////////////////////////////////////////////
    ///// HEBI Scopeのダウウンロードページから.debファイルをダウンロード ////
    ///////////////////////////////////////////////////////////////////////
    
    $ sudo apt install ./gcc-10-base_10-20200411-0ubuntu1_amd64.deb
    
    $ sudo apt install ./libgcc-s1_10-20200411-0ubuntu1_amd64.deb
    
    $ sudo apt install ./hebi-robotics-scope_1.8.0_amd64.deb
    ```

# API
HEBIモータには様々な言語で制御するためのAPIが用意されています．私は基本C++で動かしました．
C++で動かすには，C++用APIが必要です．ROSで動かすには，ROS APIにC++用APIが含まれているようです．[ROS wiki](http://wiki.ros.org/hebi_cpp_api_examples)を見る限り，ROSではC++以外の言語は使えなさそう...？です．

# 各種コマンド
HEBI Robotics の公式ドキュメントは非常に充実しているのですが，いくつかバージョン？があるようで，入り口によって記載内容が異なっていました．（内容が矛盾するとかではなく，全く別の内容が書かれていたりした）
なので，もう会えないかもしれないと思い(笑)，基本的なコマンドをメモしました．
| 参考サイト | URL |
|:-:|:-:|
| 公式 example code |   https://github.com/HebiRobotics/hebi-cpp-examples  |
| 公式ドキュメント１ |    http://docs.hebi.us/core_concepts.html#commands   |
| 公式ドキュメント２ |  http://docs.hebi.us/hardware.html#actuator-feedback |

## HEBIモジュールを見つける
同一ネットワーク内にあるHEBIモータを検索し，見つけたものの中で必要なモータのアドレスをfamilyとnameに対応させて記憶する．

1. HEBIモータを発見して表示する
    ```cpp: find_module.cpp
    #include "lookup.hpp"
    // Create Lookup and wait for it to populate
    hebi::Lookup lookup;　//Hebiを見つける
    std::this_thread::sleep_for(std::chrono::seconds(1));　//ちょっと待つ
    	
    // Take snapshot and print to the screen
    auto entry_list = lookup.getEntryList();　//見つけたHEBIのリストを受け取って表示
    std::cout << "Modules found on network (Family | Name):" << std::endl;
    for (auto entry : *entry_list)
    {
      std::cout << entry.family_ << " | " << entry.name_ << std::endl;
    }
    
    ```
2. 見つけたHEBIモータの中で，制御するものを（familyとnameを使って）指定する
	```cpp: choose_module.cpp
    ////////////////  HEBIの数が1つのとき  //////////////////
		
	// 事前に名前を定義するかlookup…の中にじかに入れるかは好み
	std::string family_name = {"family};
	std::string module_name = {"name1};
	// Create a group from a set of names
	auto group = lookup.getGroupFromNames({"family"}, {"name1"});　
	// "family"でfamily名を指定，"name"でname名を指定すると，それと一致するものだけ，記憶する
		
	////////////////  HEBIの数が複数のとき  /////////////////////
	// Can provide a different family for each module
	std::vector<std::string> families = {"mobile_base", "mobile_base", "arm", "arm"};　//覚えるfamily名を指定
	std::vector<std::string> names = {"left_wheel", "right_wheel", "shoulder", "elbow"};　//family名に対応するnameを指定
	std::shared_ptr<Group> group = lookup.getGroupFromNames(families, names);
		
	////////////////  以下，共通  //////////////////
	// 名前と一致するHEBIが見つからない場合は，そう教えてもらう
	if(!group){
	        std::cout << std::endl
	            << "Group not found! Check that the family and name of a module on network" << std::endl
	            << "matches what is given in the source file" << std::endl;
	        return -1;
	    }
		
	// 名前に一致するHEBIがあった時はその名前を表示する
	// この書き方だとfamily_nameとかが一つの場合のみ表示できる
	std::cout << std::endl
	        << family_name << " | " << module_name << "." << std::endl;
	return 0;
	// 複数のHEBIについて表示する場合はこんな感じ（一覧にしたければ，上のスナップショットを表示するコード参照）
	std::cout << std::endl << "Found group on network with " << group->size() << " modules." << std::endl;
	return 0;
    ```

## 制御する
位置，速度，加速度のいずれかを制御できる．グループ（同じfamily名を持つもの）ごとにgroup_commandで送信するのが通常．

:::note warn
位置と速度の情報を同時に入力すると，位置情報のほうが優先される.
例えば "位置＝0＆速度＝0.1" というコマンドを送信したとき，
* モータ位置が0 radになった後：回転停止（確認済み）
* モータ位置が0 radになる前：0.1 rad/sで回転する？（未確認）
:::

 ```cpp: control_hebi.cpp
    #include "group_command.hpp"
    hebi::GroupCommand group_command(group->size()); 
    // hebi::GroupCommand オブジェクトを用いて，コマンドを送信するので，それにグループの数を入力
    
    // 制御入力にはEigenというC++の行列ライブラリを利用
    Eigen::VectorXd positions(group->size());　//位置入力行列
    Eigen::VectorXd velocities(group->size());　//速度入力行列
    Eigen::VectorXd efforts(group->size());　//加速度入力行列
    //「group->size()」はグループの数なので，１グループ＆１モジュール（つまりモータ１個）しかなかったら「1」でもいい
    //多分，１グループ１列，各モジュールごとに１行ずつ割り振られるのだと思う．
    
    // hebiに送る情報を入力行列に入力．下は１グループの場合の例．（２グループ以上なら行が増える？）
    positions[0] = 1;　//角度[rad]で入力
    velocities[0] = 1;　//[rad/s]で入力
    efforts[0] = 1;
    
    //入力行列を専用コマンドにセットして，その値をグループ内の各モータに送信する
    group_command.setPosition(positions);　//setting
    group_command.setVelocity(velocities);
    group_command.setEffort(accelerations * inertia);
    group->sendCommand(group_command); //コマンドを送信．これがないとモータは動かない 
    
 ```


## コマンド・ライフタイム
PCが長時間固まったり，電波が悪かったりして，一定時間コマンドが送信されないと強制終了するプログラムになっている．その「一定時間」がコマンド・ライフタイムである．
したがって，コマンドを送るループ周期は，そのコマンド・ライフタイムより短く設定する必要がある
   
```cpp: command_liftime.cpp
    // Sets command timeout to 100 milliseconds
    group->setCommandLifetimeMs(100);　//command life time の設定
    // Command must be sent in loop at a faster rate than the lifetime in order to remain in effect.
    while (!stop_loop)
    {
      group->sendCommand(groupCommand);
      std::this_thread::sleep_for(std::chrono::milliseconds(50));　
    //command life timeより短い50 milliseconds待機したのち，ループを繰り返す
    }
```
## フィードバック情報の取得
   モータの実際の位置，速度，加速度情報を取得する
```cpp: get_feedback.cpp
    #include "group_feedback.hpp"
    //モータから送られてくる情報を記録する関数を作成
    hebi::GroupFeedback feedback(group->size());
    
    //feedback周期（情報を取得する周期）を設定
    group->setFeedbackFrequencyHz(200);　//200Hz
    //一度限りでfeedback情報を取得を要請
    group->sendFeedbackRequest(); 
    
    //フィードバックされた値の取得
    group->getNextFeedback(feedback)
    
    //各情報は，コマンド送信時と同様にグループごとの行列になっている．
    //行列に取得情報を入力
    Eigen::VectorXd positions = feedback.getPosition();
    //group_feedback.getPosition(positions);も☝と同じことをしている？
    Eigen::VectorXd velocities = feedback.getVelocity();
    Eigen::VectorXd efforts = feedback.getEffort();
```