うごくものづくりのために

技術的な備忘録がメインです。

はじめまして、ROS2

めでたくROS2がリリースされたので、Macにインストールして遊んでみようと思います。

環境

OS : Mac OS Sierra 10.12.6
CPU : Core i5 2GHz
RAM : 8GB

インストール

以下のページを参考にしながら進めます。

github.com

事前に必要なもののインストール

ROS2本体をインストールする前に、いくつかインストールが必要なものがあります。

まず初めにbrewをインストール。

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

brewがインストールできたら以下のコマンドを実行し、brewを用いて他に必要なものをインストールします。

brew install python3

# install asio and tinyxml2 for Fast-RTPS
brew install asio

# ardent was compiled and released using tinyxml2 5.0.1
brew uninstall --force tinyxml2
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/0b3ee2f67043af1b4270096c13350420e3290399/Formula/tinyxml2.rb
brew pin tinyxml2

# install dependencies for robot state publisher
brew install tinyxml eigen pcre

# OpenCV isn't a dependency of ROS 2, but it is used by some demos.
brew install opencv

# install OpenSSL for DDS-Security
brew install openssl

# install Qt for RViz
brew install qt freetype assimp

次にこのコマンドを実行し、コマンドラインツールに関連するランタイムを入れます。

# Install additional runtime dependencies for command-line tools:
python3 -m pip install pyyaml setuptools argcomplete

System Integrity Protection(SIP)の無効化

System Integrity Protection(SIP)なる機能を無効化します。 OS X の version >=10.11 はSIPがデフォルトでONになっていて、そのままだと環境変数がうまく読み込めないらしいです。
以下の手順に従います。リカバリーOSでの作業が必要なので、手順を紙にメモる等してください。

Configuring System Integrity Protection

  1. Macを再起動します。起動時にCmd + Rキーを長押しして、リカバリーOSを立ち上げます。
  2. ユーティリティメニューから端末を立ち上げます。
  3. 以下のコマンドを実行します。
csrutil disable
reboot

再起動後、念のためのSIPが無効化されているかを確認します。

csrutil status
=> System Integrity Protection status: disabled.

ROS2のインストール

満を持して。

まずは、以下のリリースページからOS X向けの最新のパッケージをダウンロードします。
Releases · ros2/ros2 · GitHub

いくつかOS X向けのパッケージがありますが、今回はros2-ardent-package-osx-fastrtps-x86_64.tar.bz2を選択しました。 どうやらROS2に組み込まれているDDSの種類によってパッケージが分かれているようです。 ちなみに、opensliceと含まれているものをインストールする場合は、opensliceを別途インストールする必要があります。
DDSに関して、詳しくは以下のページを参照してください。
DDS and ROS middleware implementations · ros2/ros2 Wiki · GitHub

DLはそこそこ時間がかかります。200MB強ある上にサーバの回線が細いらしく、僕の環境だと1時間ぐらいかかりました。のんびり待ちましょう。

DLが完了したら、解凍します。

mkdir -p ~/ros2_install
cd ~/ros2_install
tar xf ~/Downloads/ros2-ardent-package-osx-fastrtps-x86_64.tar.bz2

ROS2の環境設定

以下のコマンドを実行します。行末の zshの部分は、使っているターミナルソフトに合わせて変更してください。僕はzshなので。
. ~/ros2_install/ros2-osx/setup.zsh


以上でインストールは完了!

サンプルを動かしてみる

端末を立ち上げて、以下のコマンドを実行します。

 . ~/ros2_install/ros2-osx/setup.zsh
ros2 run demo_nodes_cpp talker

次に、別の端末で以下のコマンドを実行します。

 . ~/ros2_install/ros2-osx/setup.zsh
ros2 run demo_nodes_cpp listener

f:id:tilt_silvie:20171223113518p:plain

動いた〜!


2017/12/23 追記 公式のInstallationが修正されたので、記事も修正しました。
Mac OS Binary Installation fail caused by version difference of tinyxml2 · Issue #440 · ros2/ros2 · GitHub

Pythonのbytesの生成に関して

勘違いしていたので備忘録。

bytes(255)

b'¥xff'

とはならず、

b'¥x00¥x00¥x00 ... ¥x00'

と、255個の¥x00で満たされたbytesになる。

a = 255
a.to_bytes(1, "big")

とすれば

b'¥xff'

が得られる。

参考

python3ならintとbytesの変換が楽勝になる - BlankTar

4. 組み込み型 — Python 3.6.1 ドキュメント

Python pandasのメモ

Python pandasが難しいので、メモ。

データ構造

Series : 1次元のデータ
DataFrame : 2次元のデータ

時間系

Time Series / Date functionality — pandas 0.20.3 documentation

TimestampとPeriodの2種類がある。 Timestampは、ある時点を表す。 Periodは、ある期間を表す。

TimestampをDataFrameのIndexとして取ると、DateTimeIndexとなる。 時系列データはこれで表すと良い。 DateTimeIndexで表現されるデータを処理するための関数群が用意されている

リサンプリング

pandas.DataFrame.resample()を使う。 アップサンプリング・ダウンサンプリングともに可能。

リサンプリング時のデータの補完/集計方法はpandas.Resamplerのメソッドとして定義されている

アップサンプリングの例(線形補間)

import pandas as pd

time_index = pd.date_range("2017-10-01 10:00:00", periods=3, freq="S")
print(time_index)

df = pd.DataFrame([0, 100, 300], index=time_index)

print("-----------------------")
print("Before resampling")
print(df)

df_resampled = df.resample("500ms").interpolate("linear")

print("-----------------------")
print("After resampling")
print(df_resampled)
DatetimeIndex(['2017-10-01 10:00:00', '2017-10-01 10:00:01',
               '2017-10-01 10:00:02'],
              dtype='datetime64[ns]', freq='S')
-----------------------
Before resampling
                       0
2017-10-01 10:00:00    0
2017-10-01 10:00:01  100
2017-10-01 10:00:02  300
-----------------------
After resampling
                             0
2017-10-01 10:00:00.000    0.0
2017-10-01 10:00:00.500   50.0
2017-10-01 10:00:01.000  100.0
2017-10-01 10:00:01.500  200.0
2017-10-01 10:00:02.000  300.0

リサンプリングの注意点

サンプリング周期によっては、元データと同じ時間がリサンプリング後に現れない場合がある。(1秒間隔のデータを300ms周期でリサンプリングした場合など )
その場合、リサンプリング後に現れなかった時間のデータは補完されない。

import pandas as pd
import matplotlib.pyplot as plt

time_index = pd.date_range("2017-10-01 10:00:00", periods=5, freq="S")
print(time_index)

df = pd.DataFrame([0, 100, 300, 50, 0 ], index=time_index)

df_500ms_resample = df.resample("500ms").interpolate("time")
print("-----------------------")
print("500ms resampling")
print(df_500ms_resample)


df_300ms_resample = df.resample("300ms").interpolate("time")
print("-----------------------")
print("300ms resampling")
print(df_300ms_resample)

plt.clf()
df_500ms_resample.plot()
df_300ms_resample.plot()
plt.show()
DatetimeIndex(['2017-10-01 10:00:00', '2017-10-01 10:00:01',
               '2017-10-01 10:00:02', '2017-10-01 10:00:03',
               '2017-10-01 10:00:04'],
              dtype='datetime64[ns]', freq='S')
-----------------------
500ms resampling
                             0
2017-10-01 10:00:00.000    0.0
2017-10-01 10:00:00.500   50.0
2017-10-01 10:00:01.000  100.0
2017-10-01 10:00:01.500  200.0
2017-10-01 10:00:02.000  300.0
2017-10-01 10:00:02.500  175.0
2017-10-01 10:00:03.000   50.0
2017-10-01 10:00:03.500   25.0
2017-10-01 10:00:04.000    0.0
-----------------------
300ms resampling
                            0
2017-10-01 10:00:00.000   0.0
2017-10-01 10:00:00.300   5.0
2017-10-01 10:00:00.600  10.0
2017-10-01 10:00:00.900  15.0
2017-10-01 10:00:01.200  20.0
2017-10-01 10:00:01.500  25.0
2017-10-01 10:00:01.800  30.0
2017-10-01 10:00:02.100  35.0
2017-10-01 10:00:02.400  40.0
2017-10-01 10:00:02.700  45.0
2017-10-01 10:00:03.000  50.0
2017-10-01 10:00:03.300  50.0
2017-10-01 10:00:03.600  50.0
2017-10-01 10:00:03.900  50.0



<matplotlib.figure.Figure at 0x11268e7f0>

500msサンプリング時

f:id:tilt_silvie:20171001165109p:plain

300msサンプリング時

f:id:tilt_silvie:20171001165117p:plain

これに対する今のところの対策は、一回十分に短いサンプリング周期でアップサンプリングして、その後所望のサンプリング周期にダウンサンプリングするという手法でなんとかなる。

Trelloのカード移動をSlackに通知してくれるbotを作った。

Python3 + Flask + py-trello + slackerでTrelloの更新情報をslackに流す(1) - うごくものづくりのために

Python3 + Flask + py-trello + slackerでTrelloの更新情報をslackに流す(2) - うごくものづくりのために

上記の記事で作成したシステムを整理して、botとしてまとめてGithubで公開しました。

github.com

できること

Trelloの指定のボードで、ToDoリストからDoingリストへ、またはDoingリストからDoneリストへのカードの移動があったときに、Slackに任意のメッセージをPostする

できないこと

上記以外のこと

インストール方法、使い方等

Githubのreadmeに書いてあるので、読んでください。 英語がつらいという人はTwitter(@silvie_c)に教えてください… 日本語のreadmeを作るかもしれません。

機能追加はある?

あるかもしれませんが、あまり期待しないで待っていてください。

Python3 + Flask + py-trello + slackerでTrelloの更新情報をslackに流す(2)

前回 の記事で、py-trelloでwebhookの登録をし、Flaskでサーバを立ててTrelloの通知を受け取ることができました。

今回は、拾ってきたTrelloの通知情報をslackに投げるところを作りたいと思います。

ゴール

Trelloのウェルカムボードのカードの移動を、Flaskで作ったサーバにWebhookで通知させる。 サーバに通知がきたら、slackにPostする。

Trelloのwebhookから必要な情報だけを抜き出す

カードの移動に関する通知だけを抜き出す

Trelloのwebhookは、登録したボードに関する更新情報をすべて通知します。 今回必要なのはカードの移動だけなので、通知のうちカードの移動に関するものだけを抽出します。

通知がTrelloのどのアクションと紐付いているかは、webhookで届くデータの以下に格納されています。

action -> display -> translationKey

カードの移動に関する通知の場合は、このフィールドが action_move_card_from_list_to_list になります。

詳細な情報を抜き出す

今回は、以下の4つのデータを取ってくることにします。

  • 移動されたカード名
  • 移動前のリスト名
  • 移動後のリスト名
  • カードを移動させた人の名前

カード移動に関する通知を受け取ると、上の4つの情報を表示するように webhook_server.pywebhook()を変更しました。

webhook_server.webhook()

@app.route('/webhook', methods=['POST'])
def webhook():
    if request.method == 'POST':
        action_type = request.json["action"]["display"]["translationKey"]

        if action_type == "action_move_card_from_list_to_list":
            card_name = request.json["action"]["data"]["card"]["name"]
            before_list = request.json["action"]["data"]["listBefore"]["name"]
            after_list = request.json["action"]["data"]["listAfter"]["name"]
            user_name = request.json["action"]["memberCreator"]["username"]
    
            message = user_name + "さんが「" +card_name + "」を「" + before_list + "」から「" + after_list + "」へ移動しました!"
            print(message)

        return '', 200
    else:
        abort(400)

動かすとこんな感じ。

f:id:tilt_silvie:20170922160809p:plain

いい感じですね。

slackerを用いてslackに投稿する

Slack API Tokenを取得する

以下のを参考に、SlackBotアカウントの作成を行います。

PythonでSlackbotを作る(1) – ビットログ

Slackbotのアカウントができたら、APIトークンを secret_token.pyに追加しておきます。

slack_token = "#Your slack token HERE#"

trello_api = "#Your trello API HERE#"
trello_token = "#Your trello token HERE#"

実装

slackerライブラリを用いてSlackに投稿する仕組みは、以下の記事を参考にします。

PythonでSlackbotを作る(3) – ビットログ

先程実装したwebhook()の中で表示したメッセージをそのままslackにPOSTしてみます。

webhook_server.py

rom flask import Flask, request, abort
from slacker import Slacker
import secret_tokens

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def webhook():
    if request.method == 'POST':
        action_type = request.json["action"]["display"]["translationKey"]

        if action_type == "action_move_card_from_list_to_list":
            card_name = request.json["action"]["data"]["card"]["name"]
            before_list = request.json["action"]["data"]["listBefore"]["name"]
            after_list = request.json["action"]["data"]["listAfter"]["name"]
            user_name = request.json["action"]["memberCreator"]["username"]

            message = user_name + "さんが「" +card_name + "」を「" + before_list + "」から「" + after_list + "」へ移動しました!"

            # print(message)
            slack.chat.post_message("sandbox", message, as_user=True)

        return '', 200
    else:
        abort(400)

@app.route("/webhook", methods=["HEAD"])
def webhook_creation():
    if request.method == "HEAD":
        return "", 200
    else:
        abort(400)


if __name__ == '__main__':
    slack = Slacker(secret_tokens.slack_token)
    app.run(port=5000, host="0.0.0.0")

結果

f:id:tilt_silvie:20170922185149p:plain

Trelloでカードを動かした情報を無事Slackに投稿できました!

ゴール達成!

参考

PythonのslackbotライブラリでSlackボットを作る - Qiita

PythonでSlackbotを作る(1) – ビットログ

PythonでSlackbotを作る(2) – ビットログ

PythonでSlackbotを作る(3) – ビットログ

Python3 + Flask + py-trello + slackerでTrelloの更新情報をslackに流す(1)

弊チーム(https://github.com/SSL-Roots/Roots_home/wiki)では、タスクの管理にTrelloを使っています。 また、最近チャットツールとしてslackを導入したので、チームの活動に関する全情報をslack上に集約したいと思っています。

そこで、TrelloでタスクがDoneリストに入ったらslackで報告してくれるbotを作ります。 Trelloとslackの連携は公式でサポートされていますが、通知が英語だし味気ないので、タスク完了したら可愛い女の子が褒めてくれる仕組みにしたいです… ちなみに、RaspberryPi3 上で動かします。

というわけで。

ゴール

Trelloのウェルカムボードのカードの移動を、Flaskで作ったサーバにWebhookで通知させる。 サーバに通知がきたら、slackにPostする。

今回の記事では、Flaskでサーバを立てて、そこにTrello Webhookの通知がくるところまでを目指します。

インストール

Python3, pip3は入ってるという前提です。

sudo pip3 install flask py-trello slacker

Flaskでサーバを用意

Trello Webhookを受け取るためのサーバをFlaskで用意します。

webhook_server.py

from flask import Flask, request, abort
import pprint

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def webhook():
    if request.method == 'POST':
        pprint.pprint(request.json)
        return '', 200
    else:
        abort(400)

@app.route("/webhook", methods=["HEAD"])
def webhook_creation():
    if request.method == "HEAD":
        return "", 200
    else:
        abort(400)

if __name__ == '__main__':
    app.run(port=5000, host="0.0.0.0")

Trello Webhookの登録

Trello Webhookに、作ったサーバを登録します。 py-trelloを用いてTrello APIにアクセスしますが、その際にAPIキーとトークンが必要になります。 APIキーとトークンの取得方法は、以下のページを参考にしてください。

Trello の Webhook API をPHPで受け取ってみた - きじとら

APIキーとトークンの情報をまとめたsecret_tokens.pyを作っておきましょう。

secret_tokens.py

trello_api = "#Your API Key HERE#"
trello_token = "#Your token HERE#"

さて、Webhookの登録をします。 Webhookの登録には、WebhookサーバのURLと、通知をもらいたいアイテム(ボードだったり、カードだったり)のIDが必要です。 py-trelloを使ってアイテムのIDを取得する方法は以下のページに詳しいです。

Python + py-trelloで、Trello APIを使ってみた - メモ的な思考的な

今回は、Trelloに登録したときに必ず存在するはずの、ウェルカムボードを登録します。

trello_regist_webhook.py

from trello import TrelloClient
import secret_tokens

def main():
    server_url = "http://[自分のグローバルIP]:5000/webhook"

    client = TrelloClient(
        api_key=secret_tokens.trello_api,
        token=secret_tokens.trello_token
        )

    all_boards = client.list_boards()

    for board in all_boards:
        if board.name == "ウェルカムボード":
            welcome_board = board
            break

    webhook = client.create_hook(callback_url = server_url, id_model = welcome_board.id)

    if webhook == False:
        print("Failed webhook registration")
    else:
        print("Success!")

if __name__ == "__main__":
    main()

先程作ったwebhook_server.pyを実行させた状態でtrello_regist_webhook.pyを実行すると、Webhookが登録されます。

python3 webhook_server.py &
python3 trello_regist_webhook.py

Success!と表示されればWebhookの登録が正しく完了しています。

実験

さて、webhook_server.pyを起動している状態で、Trelloのウェルカムボード上で何かしてみましょう。 試しに、一番左の「やってみる」リストから、真ん中の「やったこと」リストにカードを移動させてみましょう。 すると…

f:id:tilt_silvie:20170917191224p:plain

Trelloの更新情報がJSONで送られてきました!

実際は、Python内部では送信されたJSONをパースして辞書形式で持っています。 あとは、必要な情報をその辞書から抜き出してやればOKです。

今回はここまで。 次回はslackに投稿する部分を作って、システムを完成させます。

参考

Python + py-trelloで、Trello APIを使ってみた - メモ的な思考的な

Trello の Webhook API をPHPで受け取ってみた - きじとら

GitHub - sarumont/py-trello: Python API wrapper around Trello's API

trelloのboardにwebhookを登録する - pblog

2017/09/22 記事修正

slackbotライブラリの代わりにslackerライブラリを使うことにしましたので、タイトルを修正。