ROSパッケージの依存関係をちゃんと書く

皆さんはROSパッケージの依存関係をちゃんと書いていますか?依存関係を正しく書いておけば別環境に移行してもちゃんと動くパッケージを作ることができるほか、依存パッケージのアップデートを一括で取り込む事も出来ちゃいます。package.xmlと.rosinstallを書いてsudo apt installから解放されましょう。

rosdep

多くのROSパッケージ(特にROS公式パッケージ)はrosdepで依存パッケージをインストールするのが最も簡単です。rosdepはpackage.xmlに書かれたdependタグからパッケージ名を特定して必要なものをapt installします。

package.xmlを書く

package.xmlはパッケージの名前や管理者の情報、そして依存関係を記述するためのROS用ファイルです。書き方は以下のような感じです。

<?xml version="1.0"?>
<package format="2">
  <name>my_package</name>
  <version>0.0.0</version>
  <description>The package to calculate 3D pose.</description>

  <maintainer email="ppdr@xxx.com">ppdr</maintainer>

  <license>MIT</license>

  <buildtool_depend>catkin</buildtool_depend>
  <depend>roscpp</depend><!-- ビルド時&実行時に依存するパッケージ -->
  <build_depend>eigen_conversions</build_depend><!-- ビルド時に依存するパッケージ -->
  <build_depend>my_other_package</build_depend><!-- ビルド時に依存するパッケージ -->
  <exec_depend>rospy</exec_depend><!-- 実行時に依存するパッケージ -->
  <exec_depend>python3-scipy</exec_depend><!-- 実行時に依存するパッケージ -->

  <export>
  </export>
</package>

前半はパッケージに関する情報を記述している部分です。後半のこの部分で依存関係を定義しています。

<buildtool_depend>catkin</buildtool_depend>
  <depend>roscpp</depend><!-- ビルド時&実行時に依存するパッケージ -->
  <build_depend>eigen_conversions</build_depend><!-- ビルド時に依存するパッケージ -->
  <build_depend>my_other_package</build_depend><!-- ビルド時に依存するパッケージ -->
  <exec_depend>rospy</exec_depend><!-- 実行時に依存するパッケージ -->
  <exec_depend>python3-scipy</exec_depend><!-- 実行時に依存するパッケージ -->

<build_depend>はビルド時の依存でC++ライブラリ等が該当します。<exec_depend>は実行時の依存でpythonライブラリ等が該当し、<depend>は<build_depend>と<exec_depend>の両方を記述したのと同じ効果があります。

rosdepではこれらは特に区別しないので、どれかに書いておけばインストールしてくれます。

<depend>のパッケージ名は何を書いたら良い?

ここにはrosdepで管理されているパッケージ名を書く必要がありますが、管理されているパッケージ名はrosdep dbコマンドで一覧取得できます。例えばvelodyneパッケージのrosdepでのパッケージ名を知りたい場合は、

rosdep db | grep velodyne

のようにすることで分かります。velodyneの場合は

velodyne -> ros-noetic-velodyne
velodyne_driver -> ros-noetic-velodyne-driver
velodyne_laserscan -> ros-noetic-velodyne-laserscan
...

のような出力が表示されるので、velodyneやvelodyne_driverを<depend>タグに書けばOKです。ちなみに右側の表示は環境によって異なり、noeticならros-noetic-xxx、melodicならros-melodic-xxxのように環境ごとにインストールすべきパッケージを判断してくれます。

rosパッケージだけでなく、libyaml-devやpython3-scipyなどもrosdepでインストールすることができ、UbuntuやFedora、Debianなど環境ごとに適切なパッケージを判断してくれるのでとても便利です。

なお、管理されているパッケージの一覧はここでも見ることができます。
base.yaml

rosdep updateとrosdep install

package.xmlを書いたらrosdepによるパッケージのインストールが可能です。パッケージリストのアップデートはrosdep updateコマンド、パッケージの新規インストールorアップグレードはrosdep installコマンドで行います。

# リストのアップデート
rosdep update

# パッケージインストール
rosdep install -iry --from-paths .

rosdep installコマンドはパッケージを指定してインストールすることもできますが、新しくクローンしてきたパッケージとかだとrospackリストに登録されていないと使えないので、今いるディレクトリ以下の全てのpackage.xmlに対して適用するオプションである”–from-paths .”をよく使います。

またオプション”-iry”は「ローカルにインストールされているパッケージ(自作パッケージ等)を無視する」「エラーがあっても中断しない」「インストール時の質問にはyesで答える」の意味です。

package.xmlに記述されているパッケージのインストールが全て完了したら、

#All required rosdeps installed successfully

が表示されると思います。これでrosdepによるパッケージインストールは完了です。

.rosinstall

ではrosdepによる管理がされていないパッケージを使いたい場合はどうしたら良いでしょうか。例えばDENSO製ロボット用のパッケージであるdenso_robot_rosパッケージはrosdep dbでは出てこず、githubから直接cloneしなければいけません。このようなgit cloneで取得する依存パッケージの記述方法として、.rosinstallファイルとvcstoolによるインストールがあります。

【Arduino】センサのノイズをデジタルフィルタで低減する

Arduinoでセンサの値やアナログ電圧を読み取るとき、ノイズにより値がガタついてしまうことがあります。今回はこのようなノイズをソフト(プログラム)で低減させる方法をご紹介します。

ノイズフィルタの種類

フィルタにはどのような信号をフィルタリングするかによって以下のような分け方があります。

  • ローパスフィルタ
    信号の低周波成分のみを通過させ、高周波成分を低減します。低いものだけを通すのでロー(low)パス(pass)フィルタです。センサ値はゆっくり変化しますが、ノイズは高周波であることが多いのでこのフィルタをよく使います。
  • ハイパスフィルタ
    信号の高周波成分のみを通過させ、低周波成分を低減します。オーディオ信号や交流電流等、変化する信号を取り出したいときに使います。
  • バンドパスフィルタ
    高周波と低周波の両方を低減し、真ん中の周波数帯だけ通したいときに使います。

本記事ではセンサ値のノイズを除去したいという目的なので、ローパスフィルタについてご紹介します。

FIRフィルタとIIRフィルタ

デジタルフィルタは実装の仕方にも種類があり、FIRフィルタとIIRフィルタに分けられます。FIRフィルタもIIRフィルタも係数を適切に選ぶことでローパス/ハイパス/バンドパスのいずれにも使えます。

FIRフィルタとIIRフィルタの違いについて調べたところ、大体次のような特徴があるようです。

FIRフィルタ

  • 波形が変形しない(群遅延が一定である)
  • 必ず安定である(出力が発散しない)
  • 通過域と減衰域の境目が緩やかである
  • 高性能にしたければ演算量が多くなる
  • カットオフ周波数が低い場合、性能が悪くなる

IIRフィルタ

  • 少ない演算量でも通過域と減衰域がはっきり分かれる
  • 通過域の信号をほとんど変化させない(通過域のゲインが0)
  • サンプリング周波数に対してカットオフ周波数が低い場合もフィルタリングが可能
  • 不安定なことがある(出力が振動/発散する)
  • 波形が変化する(群遅延が一定でない)

波形が変化するとは、信号の周波数によって遅延時間が変化するということで、それがあると例えばセンサ値がゆっくり変化したときは遅延が少ないが、早く変化したときは遅延が大きいということが起こりえます。

通過域と減衰域の境目がはっきりしていないと、例えば30Hz以上の信号を遮断するローパスフィルタを設計しても、40Hzの信号を50%だけ通過させてしまうといったことが起こります。

FIRフィルタを使うかIIRフィルタを使うかは、メリット・デメリットをよく考えて使う必要があるそうです。(参考1)

よくあるローパスフィルタの実装(RCフィルタ)

Arduino関連の記事でセンサ値のノイズを除去する方法として紹介されているのがRCフィルタです(参考2、参考3)。IIRフィルタに分類されるフィルタです。実装が簡単ですが性能はそこそこで、波形の変形も起こりますし通過域と減衰域の境目も緩やかです。

RCフィルタを実装して、加速度センサの出力をフィルタリングしてみました。ノイズは低減できましたが、もとの信号の波形が一部変わってしまっています。

#include <ADXL345.h>

double x_rc = 0;
ADXL345 adxl;  // 加速度センサ:ADXL345

void setup() {
    Serial.begin(9600);
    adxl.powerOn();  // 加速度センサ起動
}

void loop() {
    int x, y, z;
    adxl.readXYZ(&x, &y, &z);  // 加速度センサのxyz軸の値を読み取り、変数に格納する

    x_rc = x_rc * 0.85 + x * 0.15;  // RCフィルタ

    // 結果を出力
    Serial.print(x);  // フィルタ前
    Serial.print(" , ");
    Serial.print(x_lpf);  // フィルタ後
    Serial.println("");
    delay(10);
}
青:センサ出力 赤:RCフィルタ後

フィルタ後の波形は振幅がもとの信号よりも小さくなってしまいました。また分かりづらいですが遅延時間が所々変わっています。

(準備)ライブラリのインストール

本記事では以下のライブラリを使用します。Arduinoのライブラリ管理ツールから検索しても出てこないので、git cloneするか、zipファイルをダウンロードしてArduinoのlibrariesフォルダに展開します。

// git cloneする場合
c/Users/ppdr/Documents/Arduino/libraries$ git clone https://github.com/tttapa/Filters.git

FIRフィルタでより高性能なノイズ除去

FIRフィルタを設計してセンサ値をフィルタリングするとどうなるか見てみます。FIRフィルタの設計は本来難しい理論があるらしいのですが、ここは周波数等の特性を入れるだけでFIR/IIRフィルタの設計を行ってくれる以下のサイトを利用しました。

石川高専 山田洋士 研究室ホームページ Digital Filter Design Services

#include <ADXL345.h>
#include <FIRFilter.h>

// 設計サイトで求めたフィルタの係数
// フィルタ長=31、正規化遮断周波数=0.1
const double a[] = {
    6.237074932031003e-19,  1.203468256554930e-03,  2.789059944207731e-03,  4.234500608097008e-03,
    3.949464324766553e-03,  -2.416866536162014e-18, -8.270810074278890e-03, -1.861479317048980e-02,
    -2.543297162983507e-02, -2.127139910497268e-02, 6.003184622079840e-18,  3.965539443147850e-02,
    9.204504649186659e-02,  1.453456837490316e-01,  1.852171297065770e-01,  2.000000000000000e-01,
    1.852171297065770e-01,  1.453456837490316e-01,  9.204504649186659e-02,  3.965539443147850e-02,
    6.003184622079840e-18,  -2.127139910497268e-02, -2.543297162983507e-02, -1.861479317048980e-02,
    -8.270810074278890e-03, -2.416866536162014e-18, 3.949464324766553e-03,  4.234500608097008e-03,
    2.789059944207731e-03,  1.203468256554930e-03,  6.237074932031003e-19};

ADXL345 adxl;      // 加速度センサ:ADXL345
FIRFilter fir(a);  // FIRフィルタ

void setup() {
    Serial.begin(9600);
    adxl.powerOn();  // 加速度センサ起動
}

void loop() {
    int x, y, z;
    adxl.readXYZ(&x, &y, &z);  // 加速度センサのxyz軸の値を読み取り、変数に格納する

    // 結果を出力
    Serial.print(x);  // フィルタ前
    Serial.print(",");
    Serial.print(fir.filter(x));  // フィルタ後
    Serial.println("");
    delay(10);
}
青:センサ出力 赤:FIRフィルタ後

RCフィルタに比べて波形の変形が少なくなりました。しかし、元波形よりも振幅が小さくなってしまい、遅延が出てしまいました。これはカットオフ周波数が低い場合、性能が低くなるというFIRフィルタの特徴になります。ちなみにこの遅延時間は設計段階で知ることができ、今回は15サンプル遅延でサンプリング間隔は10msなので150msの遅れです。

IIRフィルタで遅延が少ないフィルタを実現する

FIRフィルタでは波形は変形しませんでしたが、遅延が大きくなってしまいました。また元波形よりも振幅が小さくなってしまいました。

この点を改善するため、低いカットオフ周波数に対しても遅延が少ないIIRフィルタを設計します。またIIRフィルタの特性の一つであるバタワース特性では通過域のゲインがほぼ0なので、波形の振幅を変えてしまうことも防げます。

それでは実装と結果です。

#include <ADXL345.h>
#include <IIRFilter.h>

// 設計サイトで求めたフィルタの係数
// バタワース特性、サンプリング周波数=100、パスバンド周波数=5、ストップバンド周波数=30
// 注意:設計サイトはb[1]以降の値なので、b[0]=1を追加しています
const double a[] = {2.87258084601274277e-01, 5.74516169202548554e-01, 2.87258084601274277e-01};
const double b[] = {1, -1.27955098077267904e+00, 4.77550934586849885e-01};
const double k = 1.72318869709970568e-01;

ADXL345 adxl;         // 加速度センサ:ADXL345
IIRFilter iir(a, b);  // IIRフィルタ

void setup() {
    Serial.begin(9600);
    adxl.powerOn();  // 加速度センサ起動
}

void loop() {
    int x, y, z;
    adxl.readXYZ(&x, &y, &z);  // 加速度センサのxyz軸の値を読み取り、変数に格納する

    // 結果を出力
    Serial.print(x);  // フィルタ前
    Serial.print(",");
    Serial.print(iir.filter(x * k));  // フィルタ後
    Serial.println("");
    delay(10);
}
青:センサ出力 赤:IIRフィルタ後

IIRフィルタでは遅延がかなり少なくなりました。また、振幅も元波形とほとんど同じです。

今回のようにセンサ出力が10Hz以下くらいであれば、IIRフィルタが遅延/波形の変形の両方で有利なようです。ただ、FIR/IIRのどちらが良いかは一概には言えないので、色々なパラメータで試してみる必要がありそうだと感じました。

ここまで読んでいただきありがとうございました。今回は以上です。

参考

  1. FIRとIIRの選択 – Allisone
  2. Arduinoでローパスフィルタ(センサのノイズを無くそう)
  3. ArduinoでSTEM教育​ 基礎編:デジタル入力フィルタ
  4. Arduinoで信号処理をする上で便利なフィルタのライブラリ

PythonでGoogle Homeを喋らせる(2021年最新)

PythonでGoogle Homeに任意の言葉を喋らせる方法です。(正確には音声ファイル(.mp3や.wavなど)をGoogle Homeに再生させる方法)。pychromecastというPythonモジュールを使えば簡単ですが、2020/7/20からバージョンが変わったらしく、IPアドレスでGoogle Homeに接続する方法が使えなくなっていたので最新の方法をご紹介します。

Google Homeの名前からIPアドレスを調べて接続(2020/7/20以前の方法)

pychromecast ver7.2.0以前では、まずLAN内に存在するcastデバイス(Google Homeなど)の一覧を取得し、その中から探したいデバイスの名前を検索してIPアドレスを取得し、IPアドレスでGoogle Homeに接続していました。参考までにその方法を載せておきます。

#!/usr/bin/python3
# coding:utf-8
import pychromecast
import sys


def main():
    # LAN内に存在するcastデバイスの一覧を取得
    chromecasts = pychromecast.get_chromecasts()

    # castデバイスが見つからない場合、終了
    if len(chromecasts) == 0:
        print("Google Homeが見つかりませんω")
        sys.exit(1)

    # 名前が"Living room"のデバイスを探す
    googleHome = next(
        cc for cc in chromecasts if cc.device.friendly_name == "Living room")

    # ver7.2.0以降ではgoogleHome.hostが削除されているためエラー
    print("IP address: ", googleHome.host)

    # 喋らせる
    googleHome.wait()
    mc = googleHome.media_controller
    voice_url = "http://address/to/voice/file"

    # 他の音楽を再生中の場合
    if mc.status.player_is_playing:
        print("Music is running. Stop music.")
        mc.stop()
        mc.block_until_active(timeout=30)

    # 音声を再生する
    print("Playing ", voice_url)
    mc.play_media(voice_url, "audio/mp3")


if __name__ == "__main__":
    main()

IPアドレスが分かっている場合は、

googleHome = pychromecast.Chromecast("192.168.0.10")

とすればGoogle Homeに接続できますが、わからない場合やルーターを再起動する等でIPアドレスが変わった場合は上のコードを使う必要がありました。

デバイス名で接続(2020/7/20以降)

pychromecastの最新バージョンでは、IPアドレスを使わずにGoogle Homeに接続するように変更されていました。

def main():
    # Chromecastデバイス名で検索する
    chromecasts, browser = pychromecast.get_listed_chromecasts(friendly_names=[             "Living room"])
    # castデバイスが見つからない場合、終了
    if len(chromecasts) == 0:
        print("Google Homeが見つかりません")
        sys.exit(1)

    # 最初に見つかったもの(同名のデバイスが複数あった場合)
    googleHome = chromecasts[0]

    '''
    以降、同じ
    '''

以前のバージョンだと、castデバイス一覧の取得に10秒ほどかかっていた印象ですが、この方法では1秒もかからずcastデバイスを見つけてきます。
接続高速化のために過去に接続したGoogle HomeのIPアドレスをファイルに保存していましたが、もう必要なさそうです。とりあえず時報やニュースを読み上げさせて遊んでいます。

Volumio2に再生コマンドを送る!

はじめに

こんにちは。何も考えずにラズパイzeroを購入してしまったせいで使い道が無くて持て余していたので、volumioをインストールしてミュージックサーバーにしてみたパパドラゴンです。

volumioのインストールはこちらのページで紹介されているDACをそのまま買って組んでSDカード焼くだけでめっちゃ簡単でした!
でもvolumioはブラウザから操作することを考えて作られているらしく、別のラズパイから再生コマンドを送る方法がいまいち分かりにくかったので紹介します。

やりたいこと

  • Volumio2を「他のラズパイ」から「コマンドラインで」操作したい
  • Google Homeを使って操作したい
  • WiFi経由でArduinoから操作したい

方法

結論から言いますと、Volumioの公式ドキュメントにAPI一覧が書いてあります。
ネットワーク関係は全く素人なので理解できたとこだけ読んだところ(よくない)、GETリクエストを送る方法が一番カンタンで早い気がします。

仕組みはともかく使い方だけなら、ブラウザでもラズパイのターミナル上でもpython上でもとにかく「http://volumio.local/api/v1/commands?cmd=〇〇」のアドレスにGETリクエストを送れば、結果(成功すれば現在の状態、失敗すればエラー)が辞書型で帰ってきます。
「volumio.local」の部分は「192.168.0.5」のようなアドレスに変える必要があるかも知れません。

例えばchromeだとアドレスバーに上記を打ち込めばこんな感じに帰ってきて再生が始まります。

「192.168.0.5」の部分は自分のvolumioのアドレスに変えてください

ラズパイのターミナルだとcurlコマンドを使ってこんな感じ。

ppdr@raspberrypi:~ $ curl http://volumio.local/api/v1/commands?cmd=stop
{"time":1538585873376,"response":"stop Success"}

pythonならこんな感じです。

import urllib2

res = urllib2.urlopen("http://volumio.local/api/v1/commands?cmd=play",timeout=5)
print( res.read() )

 

どんなコマンドが送れるの?っていうとアドレスの「?」以降(クエリ)の部分に色々指定して送れば良さそうです。

 

クエリ動作
cmd=play再生
cmd=stop停止
cmd=pause一時停止
cmd=prev前の曲へ
cmd=next次の曲へ
cmd=volume&volume=80音量を80に
cmd=playplaylist&name=Rockプレイリスト「Rock」を再生
cmd=repeatリピートをON&OFF
cmd=randomランダム再生をON&OFF

他のコマンドについては公式ドキュメントを参照してくださいm(_ _)m。

これが出来ればとりあえず汎用性は高そうです。プレイリストやキューの一覧を取得するコマンドもあるので帰ってきた辞書型を判断して処理を分けるとかもできそうです。
時間があればGoogle Homeから起動とかも試してみたいです。お部屋BGMライフが始まりそう。