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

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

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ライブラリを使うことにしましたので、タイトルを修正。

MakerFaireTokyo2017に出展しました。

8/5,6に行われた、MakerFairTokyo2017に、RoboCup SSLチーム Rootsとして出展してきました!

f:id:tilt_silvie:20170809002654j:plain

はじめに、展示に足をお運び頂いた皆様にお礼を伝えたいと思います。
展示を見て笑顔になって頂いたり、「すごいねー」と子供たちが食いついてくれたりしたことが展示者としての何よりの喜びでした。 少しでも皆様にRoboCupSSLの魅力がお伝えできたことを嬉しく思います。興味を持っていただいた方は、ぜひ本当の試合を見にJapanOpenなどの大会にお越しください!


今回の展示は、RoboCup SSL OBチームであるOP-AmPとRootsの共同でRoboCupSSLの実演を行いました。

このような展示会に出向いてRoboCup SSLの実演をしたのは、おそらく僕達が日本で初めてになります。 当然前例のない試みですので、不安だらけでした。 フィールドカメラの画角は足りるのか、画像処理の設定がうまくいくのか、電波は混線しないか等々…
しかし流石、OBチームが2チーム集まっただけあります。苦労はありましたが、OP-AmPさんの協力もあり、大会の際よりもむしろスムーズに設営が終わったのではないでしょうか。 あまりない経験だと思うので、設営に関する情報も後ほどまとめてPublishしようと考えています。

f:id:tilt_silvie:20170809002203j:plain

展示は、ロボットの実演やパーツごとの分解展示、動画での紹介を行いました。実演展示は、Rootsがディフェンス、OP-AmPがオフェンスという形で、1−2パスシュートのデモを行いました。また、OP-AmPさんの世界唯一のカーブシュートや、ロボットがボールを所定の位置まで運ぶBall Replacementの実演も行いました。

やはりと言うべきか、お客さんに一番興味を持っていただいたのは実演展示でした。1−2パスシュートが決まる姿を見て、その場で足を止めて見てくださる方が多かったです。 さらにロボットが完全自律でサッカーをしているとわかると、驚くお客さんがたくさん。
普段の大会だとRoboCupをご存知のお客さんが多いので、初めて見る方の反応は非常に新鮮でした。驚いたり笑顔になって頂いていたのが嬉しかったですね。


反省はいろいろありますが、実演展示でOP-AmPさんにおんぶにだっこだったのが、ありがたくも悔しいところです… 次の展示のときまでに対等に渡り合えるように実力をつけないとなぁ…と感じています。

最後になりますが、展示においでいただいた方々、MFT運営の皆様、そしてOP-AmPさん、Rootsのメンバー、本当にありがとうございました。そして、お疲れ様でした!

来年のMFTでまた会いましょう〜!

GoogleProtocolBuffer(C++)で、NULL文字(/0, 0x00)を含むデータをパースする方法

GoogleProtocolBufferのC++版で、NULL文字を含むデータをパースする際にハマったのでメモ。

socket通信のrecv()で受け取ったデータをParseFromString()でパースしようとした際、 データに"0x00"が出現するとヌル文字(\0)だと解釈して、パースが正常終了しませんでした。

結論としては、char型の配列をstring型に変換し、かつ変換の際にサイズを指定してやればOKでした。

  /*** 初期設定などなど省略 ***/

  char buf[BUF_SIZE];
  ProtobufClass proto;  // プロトコルバッファのメッセージ解析用インスタンス
  size_t recv_size;

  recv_size = recv(sock, buf, sizeof(buf), 0);

  std::string str(buf, recv_size);
  proto.ParseFromString(str);

一つのコールバック関数に複数のSubscriberを割り当てる方法

タイトルの通りです。

別々のTopicをSubscribeしている複数のSubscriberで、同じコールバック関数を呼び出す方法です。

準備

rostopic pub -r 10 chatter_0 std_msgs/String "message of chatter 0" &
rostopic pub -r 15 chatter_1 std_msgs/String "message of chatter 1" &

cpp

コールバック関数の型 ros::MessageEventがミソです。

ros::MessageEventにいくつかメソッドが用意されており、publisherの名前や topic名を取得できます。

#include "ros/ros.h"
#include "std_msgs/String.h"

void chatterCallback(const ros::MessageEvent<std_msgs::String const>& event)
{
    const ros::M_string& header = event.getConnectionHeader();
    std::string topic = header.at("topic");
    const std_msgs::StringConstPtr& msg = event.getMessage();

    ROS_INFO("[%s]:%s", topic.c_str(), msg->data.c_str());
}

int main(int argc, char **argv)
{
  ros::init(argc, argv, "listener");
  ros::NodeHandle n;
  ros::Subscriber sub_0 = n.subscribe("/chatter_0", 1000, chatterCallback);
  ros::Subscriber sub_1 = n.subscribe("/chatter_1", 1000, chatterCallback);

  ros::spin();

  return 0;
}

結果

[ INFO] [1476365549.987671394]: [/chatter_1]:message of chatter 1
[ INFO] [1476365550.027815654]: [/chatter_0]:message of chatter 0
[ INFO] [1476365550.054415232]: [/chatter_1]:message of chatter 1
[ INFO] [1476365550.121278541]: [/chatter_1]:message of chatter 1
[ INFO] [1476365550.127638866]: [/chatter_0]:message of chatter 0

Python

rospy.Subscriber()のコンストラクタ引数 "callback_args"がミソです。 "callback_args"に指定した値が、コールバック関数の第二引数として渡されます。

#!/usr/bin/env python
import rospy
import std_msgs.msg

def callback(data, id):
  rospy.loginfo("[ID:"+str(id)+"] : " + data.data)

def listener():
    rospy.init_node('listener', anonymous=True)

    sub_0 = rospy.Subscriber("/chatter_0", std_msgs.msg.String, callback, callback_args=0)
    sub_1 = rospy.Subscriber("/chatter_1", std_msgs.msg.String, callback, callback_args=1)

    rospy.spin()

if __name__ == '__main__':
    listener()

結果

[INFO] [WallTime: 1476365751.387734] [ID:1] : message of chatter 1
[INFO] [WallTime: 1476365751.427946] [ID:0] : message of chatter 0
[INFO] [WallTime: 1476365751.454669] [ID:1] : message of chatter 1
[INFO] [WallTime: 1476365751.521102] [ID:1] : message of chatter 1
[INFO] [WallTime: 1476365751.527823] [ID:0] : message of chatter 0

第49回 情報科学若手の会 に参加してきた

情報科学若手の会

情報科学若手の会という、情報系の若手(?)が集う勉強会に参加してきました。 結構歴史のある勉強会で、バックボーンに情報処理学会がいる、割とちゃんとした勉強会です。

とはいえ、想像していたよりもフランクな雰囲気で、あの空気感であの発表内容が聞ける会って本当に貴重だなと思います。

自分はもともとずっとロボットの人間だったので、正直情報系というクラスタには片足くるぶしぐらいまでしか入ってないのですが、 せっかくなのでたくさん刺激を貰いに行こうと思い参加しました。 (参加費は結構お財布に響きますが)

f:id:tilt_silvie:20160920000405j:plain:w300

会場の山喜旅館(静岡県伊東市)

発表

初参加だったのですが、せっかくなので発表をさせて頂きました。 参加者の皆さんのレベルが高くて(後述しますが、本当に) 僕なんかが同じ舞台で発表していいのかと思いましたが… なかなかこういうチャンスもないので頑張りました。

発表では、RoboCup SSLについての概要と、趣味で立ち上げているSSLチームの紹介をしました。 発表スライドは、下のリンクからアクセスできます。

docs.google.com

大変嬉しいことに、皆さん興味を持って聞いて頂けたみたいでした。自分の発表を聞いて、「RoboCup面白そうなので僕もやります!」と 言ってくださった方もいらっしゃいまして、発表者冥利に尽きる思いでした。

あと、SSLロボットを持って行って操縦体験もしてもらったのですが、みなさん楽しんで頂けたみたいです。嬉しい。

少しでもRoboCup界隈が賑わうといいなぁ…と思います。 RoboCup本当に楽しいので、興味ある方は是非参加してみてください。

感想

率直な感想としては、本当にたくさんの刺激を貰えて、とても楽しかったですし有意義でした。

仕事や趣味では全く知らない分野の動向が知れたり、某企業のヒミツの裏話が聞けたりとか、 参加してなければ知り得なかった情報が本当にたくさん手に入りました。

あと、参加されている方々が本当にすごくて、何かのコンテストで一位になりましたーとか、世界中がお世話になってる某企業で勤めてますーとか、 日本で指折りのブログ運営してますーとか、そんな方々ばっかり。 そういう方々とお酒を飲みながら色んな話を聞けたのは本当に楽しかったです。

(お知り合いになった方々、ぜひ今後共よろしくお願いします)

なかなかそういう方々とこんなにフランクな形で知り合える場は滅多に無いと思いますので、今後もこの会がずっと続いていけばいいなぁ…と思います。

来年もいきます!

rosコード(C++, Python)から現在存在するトピックのリストを取得する方法

C++

c++ - ROS - get current available topic in code (not command) - Stack Overflow

roscpp: ros::master Namespace Reference

ros::master::V_TopicInfo master_topics;
ros::master::getTopics(master_topics);

for (ros::master::V_TopicInfo::iterator it = master_topics.begin() ; it != master_topics.end(); it++) {
  const ros::master::TopicInfo& info = *it;
  std::cout << "topic_" << it - master_topics.begin() << ": " << info.name << std::endl;
}

Python

http://docs.ros.org/api/rospy/html/rospy.client-module.html#get_published_topics

import rospy
rospy.get_published_topics('/')

「rostest」 の使い方

rostest - ROS Wiki

rostestを使ううれしさ

  • launchファイルと同様の手法で多数のノードを起動したりパラメータを設定した後に、ユニットテストを走らせることができる
  • C++ならgtest, Pythonならunittestによって作成したテストコードを実行可能

使い方

rostest/Writing - ROS Wiki

rostestを用いるrosパッケージのサンプル構成を一番最後に置いておくので参考にしてください。

  1. テストケースを書く

    C++ならgtest、Pythonならunittestでテストケースを書きます。

  2. “.test"ファイルを作る

    rosユーザならおなじみのlaunchファイルと同様の形式で記述します。 唯一異なるのは、ノードを立ち上げる際に<test>タグを用いる点です。

    <test>タグの詳細はこちらから。

  3. CMakelists.txtの編集

    gtest(c++)の場合

    以下を追加。

if(CATKIN_ENABLE_TESTING)
  find_package(rostest REQUIRED)
  add_rostest_gtest(tests_mynode test/mynode.test test/test_mynode.cpp [more cpp files])
  target_link_libraries(tests_mynode ${catkin_LIBRARIES})
endif()

3行目のadd_rostest_gtest(tests_mynode test/mynode.test src/test/test_mynode.cpp [more cpp files]) の部分は、一つ目のパラメータがテスト実行用バイナリの名前、 2つ目が対応する.testファイル、3つ目以降がテストケースが記述されたcppファイルです。

4行目のtarget_link_librariesで、テスト実行用バイナリの名前を指定するのを忘れないでください。

###### unittest(python)の場合

調査中です。

  1. ビルド & テスト実行

    catkin_make run_tests

サンプル

GitHub - tilt-silvie/rostest_sample