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

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

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

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本当に楽しいので、興味ある方は是非参加してみてください。

感想

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

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

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

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

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

来年もいきます!