「IoT×API de 街ハック!#MA_2017 名古屋ハッカソン予選」に参加してきました

「IoT×API de 街ハック!#MA_2017 名古屋ハッカソン予選」に参加してきました

f:id:ueponx:20171020001426p:plain

mashupawards.connpass.com

10月14日・15日に名古屋工業大学さんで行われたMashupAward名古屋予選に参加してきました。 今回は、一般参加でもあり、運営のお手伝いもさせていただきました。

f:id:ueponx:20171019144024j:plain

f:id:ueponx:20171019144038j:plain

会場に到着して会場準備作業へ、一番の難関はPepper2台の運搬。天気は微妙でしたがなんとか雨は保ってくれました。

f:id:ueponx:20171019233814j:plain

当日は欠席者もほとんど出ず、逆にお手伝いに駆けつけてくれる方も来てくれてかなり助かりました。

今年のMA_2017の初期のカレンダーをみると、実は名古屋の開催はスケジュールに含まれていなかったのですが、 名古屋で開催してほしいと要望を出したところ、うまくことが運び、開催に至ることができました。 ただ、この日は色々な行事との重なりなどがあり、事前ではあまり参加者の伸びが芳しくなかったです。 ただ、告知やLTなどにも行くようにしたこともあり、初参加の方とベテランの方のバランスがいい感じになったような気がします。(直前のIoTLT、GeekBar、Code for Nagoyaが効いたかなと思います)

f:id:ueponx:20171019144504j:plain

今回のイベントは鈴木まなみさんがファシリテーションを行ってもらっていたのですが、自分はまなみさんの ファシリテーションは初めてだったのでかなり新鮮でした。

f:id:ueponx:20171019144636j:plain

ハッカソンのテーマは「IoT×APIで街ハック」の3つのキーワードを絡めてでした。「街」というと町おこしというイメージがありますが、生活するフィールドとして捉えて、それを楽しく!尖った感じ!にアイデアを考えようと捉えていました。

f:id:ueponx:20171019144610j:plain

自分としては街にある既存のものを面白くするのもどうかなと思っていたのですが、それだとアイデアとしては「ありきたり」なのでもっとぶっ飛んだ何かを作らないとだめかなと思いました(MAは一応コンテストなので評価されるものなので)

念のため審査基準

  • チャレンジ度(ぶっ飛び度、変化球、新規性、アイデア
  • ギーク度(突き抜け度、こだわり度、完成度)
  • ワクワク感(表現手法、体験、人を楽しませる仕組み、デザイン)

後は、ハッカソンのテーマ性がこれに加わる感じです。(今回であれば「街」というキーワードとなります。)

一日目の午前中は、参加者みんなでアイデアソンと企業の皆さんからのインプットと続き、その後チームビルディングへと進みます。冒頭で自己紹介があったのは初めて体験したような気がします。

f:id:ueponx:20171019233400j:plain

私は一日目のこの時間が一番楽しい時間だと思っているので、まだハッカソンを経験したことのない方は是非体験してほしいと思っています。実際にものづくりになると、個人のスキルなどに依存する作業が多くなります。しかし、この時点でのアイデア出しはそういうこととは関係なくテーマに対して自由な発想で考えて行くことができます。

今回ご提供頂いたテクニカルサポート企業(APIなど)は以下のような感じでした。

f:id:ueponx:20171019234521j:plain f:id:ueponx:20171019234554j:plainf:id:ueponx:20171019234602j:plainf:id:ueponx:20171019234609j:plainf:id:ueponx:20171019234620j:plain

他にも持ち込み機材として

がありました。

自分が今回出したアイデアは過去の既視感を音楽や環境音で表現するような感じで、訪れた場所や人に身に着けたセンサーなどでその音楽を味付けする感じのもの(過去あった建造物ものに関連する環境音とか)だったような気がします。(あんまり記憶がない…)タイトルは「デジャーブ」

f:id:ueponx:20171019160611j:plain

その後、マホーのキーワード

f:id:ueponx:20171019145241j:plain

これを唱えながら、チーム結成を行っていきます。

今回は比較的似ていたアイデアを融合させて以下のような、「JamPepper」を作ることにしました。 Pepper、センサーなどを組み合わせるところが技術的なポイントです。これを街のホットスポットに設置してジャム・セッションを行って、街を楽しくしていこうというものになります。

【JamPepper草稿】 f:id:ueponx:20171019145322j:plain

このアイデアではPepperとセンサーを通信でつなぐのですが、自分はセンサーデータをサーバ側にアップロードする担当になりました。持ち込みのセンサーに3次元ジャイロ(Grove接続)があったのでそれと、個人で持ち込んでいたNefryBT(きびだんごで購入)を使い、Wifi経由でサーバ(今回はローカルサーバ)へセンサー情報を送っています。

ジャイロセンサー(温度センサーも入っている…すげ)

www.seeedstudio.com

NefryBT(ESP32でWiFiもBLEもいけて、Groveコネクタもある)

kibidango.com

ジャイロのデータはすぐ取得できた(さすがGrove接続!)ので通信部分を頑張っていたのですが、グローバル通信ではうまくいくのにローカルでは送れないという状況になり、そこで半日ほど使ってしましました。全体的な開発状況としては30%も行ってなかったような感じでかなり課題の残る一日目でした。

一日目の夜にNefryBTの作者のわみさんに通信動作に関してDメッセージで質問したりしたのですが、アドバイス頂いた処理を使っても、結局動作できず一日目の深夜が終わりました。深夜にご対応していただいてありがとうございます>わみさん

2日目の早朝にもう一度通信テストを行ってみると、問題なく動作していました。うまく行かなかったソースがなにもしなくて動作していたので小人の仕業かと思ってしました。でも少しソースをいじって(IPアドレス程度)NefryBTに書き込みをすると通信ができなくなる様でした。

個人的な予想なのですが、開発している際にリセットなどを頻繁に行っているためにIPアドレスの取得がうまく行かなくなることがあるのかなと考えました。(起動ログではIPは取れていると出ていますがpingすると疎通できていない感じ)この現象は簡単には解決できそうになかったので、まずはセンサー情報をシリアル経由でサーバ側にアップすることにし、時間があれば通信を行うという方向にしました。(デモでは無線通信はつかえませんけど)

かたや、Pepper側もなかなか難しい状況で開発環境のCoregraphの調子やPepperとの接続が悪くなったりということが突然やってきたりして大変だったようです。

そんなこんなで2日目の午前はあっという間に過ぎ、2日目の昼食が終わると残り2時間という感じになります。デモ中心の発表になるのでそこからはどれぐらいエッセンスを見せられるかを絞っての開発・調整となりました。

15:30ハッキングタイム終了です。

今回のハッカソンでは合計5チーム(※)が結成されて、発表をしています。(※1チームはボッチソンでしたが)

審査員は名古屋大学の河口先生、北名古屋市議の桂川さん、IAMAS小林先生だったので、かなり仕上がりとプレゼンの出来をバキうちされるのではないかと、ヒヤヒヤしていたのですがなんとかなったようです。

開発ばかりで頭が辛かったので、今回のプレゼンではペッパーとダンスをするパフォーマンスをしてみました。

f:id:ueponx:20171019145018j:plain f:id:ueponx:20171019145029j:plain

結果!ハッカソン最優秀賞は「CoT-Claimer of Toilet-」チーム名(QoT)となりました。汚いトイレの公開処刑はいいワードです。

f:id:ueponx:20171019144859j:plain f:id:ueponx:20171019235649j:plain f:id:ueponx:20171019144828j:plain

自分のチームはKonashi賞をいただけました(ありがたい)

hacklog.jp

センサーをハッカソンで使うのはかなり予習をしていないとだめだなーというのが今回の反省です。でも、久しぶりに頑張ってコードを書いていたので満足感半端なかったです。通信部分も含めてもう少しブラッシュアップしたいなと思っています。(予選通過したものよりその後ブラッシュアップしたもののほうが2次予選でいい成績を残すものも多いらしいので。)

ただPepperはないからなあ…と思っていたら、期間中は名古屋工業大学さんで使えるようになるそうです。

今回、名古屋でMAのハッカソンがないのはヤダーというところからのスタートでしたが、無事に開催し終了を迎えることができて本当に良かったです。こういうイベントは一度なくなるとなかなか復帰するのが大変そうだったので、あえて無理をしてでも開催したいと思っていました。

今回、無理を言って会場をお貸し出しいただいた白松先生本当にありがとうございました。 また、テクノロジーサポート企業の方々、まなみん、伴野さん、そして参加されたみなさんも本当にありがとうございます。

来年も名古屋でハッカソンのイベントやりたいです!

ちなみにこの一件をきっかけにIT系のコミュニティを立ち上げました。Mashup名古屋というコミュニティになります。正式にはまた告知をしようと思っています。

RaspberryPiでRFID-RC522を使ってみる【失敗編】

RaspberryPiでRFID-RC522を使ってみる【失敗編】

このエントリーは失敗編です。失敗に至るメモのため、情報が基本ないので読み飛ばしてください。

以前の勉強会で教えてもらった(もう数年も前になりますが)NFCのタグシールを何かに使えないかと思って購入してみました。そのまま使うのもなんなのでArduinoやRaspberryPiでも使えないかなと思って以下も同時に購入してみました。

リーダーもこの値段で購入できるんですね。いい時代なのか、それともちょっとやそっとではお金を稼ぐことができない時代なのか。

このリーダーをネットで検索すると、こちらのリンクにヒットしました。結構Arduinoのネタは多いようなんですが、RaspberryPiはすくないようです。

webcache.googleusercontent.com

ふむふむと読んでいくと…

・the kernel to use Device Tree?
 で Noを選択します。(コレ重要!)

raspi-configコマンドで表示されるメニューで設定値が表示されるようなのですが、うちのRaspberryPiでは表示されないようです。なぜ?カーネルのバージョンなどで違いがあるのでしょうか。念のため、昔使っていたRaspberryPi2でも同様の設定は出ませんでしたし。ネットを検索してもそれっぽい情報はありませんでした。

とりあえず、とても嫌な予感がしますが、そのまま進めていくことにします。

候補としては以下の2つになると思います。そろそろC言語はつらいので…

  • SPI-Py(SPI制御ライブラリ)&MFRC522-pythonの組合せ
  • pi-rc522

今回は、候補の中で使用するのはPythonからこのリーダーを扱えるpi-rc522モジュールになります。 リンク先のページではgitでCloneしてインストールしていますが、ヌルいPython使いの自分はpipでインストールしました。

github.com

$ pip install pi-rc522
Collecting pi-rc522
  Downloading pi-rc522-2.2.1.tar.gz
Collecting RPi.GPIO (from pi-rc522)
  Downloading RPi.GPIO-0.6.3.tar.gz
Collecting spidev (from pi-rc522)
  Downloading spidev-3.2.tar.gz
Building wheels for collected packages: pi-rc522, RPi.GPIO, spidev
  Running setup.py bdist_wheel for pi-rc522 ... done
  Stored in directory: /home/pi/.cache/pip/wheels/83/45/21/0bc28b47c8dfb87d00d806b10c8ffb733e36a8b927668ce42b
  Running setup.py bdist_wheel for RPi.GPIO ... done
  Stored in directory: /home/pi/.cache/pip/wheels/ae/4d/3b/e924997dbf06810adf3b2e37f1d9627b2327eb9cbb285949c9
  Running setup.py bdist_wheel for spidev ... done
  Stored in directory: /home/pi/.cache/pip/wheels/e4/9b/5f/cf0ec030fc958b72315a15412130e4e1dc6040cdb490aa21fb
Successfully built pi-rc522 RPi.GPIO spidev
Installing collected packages: RPi.GPIO, spidev, pi-rc522
Successfully installed RPi.GPIO-0.6.3 pi-rc522-2.2.1 spidev-3.2

同時にGPIOやSPIのモジュールもインストールされるようです。GitでCloneするとサンプルコードも同時に手に入るのですが、pipインストールでは手に入らないので、Githubにいってサンプルを取得します。

【サンプルページ】 github.com

$ wget https://raw.githubusercontent.com/ondryaso/pi-rc522/master/examples/Read.py
--2017-09-18 03:08:03--  https://raw.githubusercontent.com/ondryaso/pi-rc522/master/examples/Read.py
raw.githubusercontent.com (raw.githubusercontent.com) をDNSに問いあわせています... 151.101.72.133
raw.githubusercontent.com (raw.githubusercontent.com)|151.101.72.133|:443 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 983 [text/plain]
`Read.py' に保存中

Read.py             100%[===================>]     983  --.-KB/s    in 0s

2017-09-18 03:08:03 (6.98 MB/s) - `Read.py' へ保存完了 [983/983]

RFIDのリーダーとの接続は RC522側の IRQだけ接続不要です。

RC522モジュール RaspberryPi
1 3.3V GPIO +3.3V(3V3)
2 RST GPIO GPIO25
3 GND GPIO GND(GND)
4 MISO GPIO MISO(GPIO9)
5 MOSI GPIO MOSI(GPIO10)
6 SCK GPIO SCLK(GPIO11)
7 SDA GPIO CE0(GPIO8)

以下のように実行すればうまく…

【Read.py】

#!/usr/bin/env python

import signal
import time
import sys

from pirc522 import RFID

run = True
rdr = RFID()
util = rdr.util()
util.debug = True

def end_read(signal,frame):
    global run
    print("\nCtrl+C captured, ending read.")
    run = False
    rdr.cleanup()
    sys.exit()

signal.signal(signal.SIGINT, end_read)

print("Starting")
while run:
    rdr.wait_for_tag()

    (error, data) = rdr.request()
    if not error:
        print("\nDetected: " + format(data, "02x"))

    (error, uid) = rdr.anticoll()
    if not error:
        print("Card read UID: "+str(uid[0])+","+str(uid[1])+","+str(uid[2])+","+str(uid[3]))

        print("Setting tag")
        util.set_tag(uid)
        print("\nAuthorizing")
        #util.auth(rdr.auth_a, [0x12, 0x34, 0x56, 0x78, 0x96, 0x92])
        util.auth(rdr.auth_b, [0x74, 0x00, 0x52, 0x35, 0x00, 0xFF])
        print("\nReading")
        util.read_out(4)
        print("\nDeauthorizing")
        util.deauth()

        time.sleep(1)

はずだ…

$ python Read.py
Starting

10分後…全く動かねえ…

ではということで

方法その1であった、SPI-Py(SPI制御ライブラリ)&MFRC522-pythonの組合せもやってみます。

$ git clone https://github.com/lthiery/SPI-Py.git
$ cd SPI-Py
$ sudo python setup.py install
$ cd ..
$ git clone https://github.com/mxgxw/MFRC522-python.git
$ cd MFRC522-python
$ sudo python Read.py

これでも動作せず。/(^o^)\オワタ。

もう少し調べてみる。

ハード側が怪しいということも疑って、Aruduinoでも使用可能なのでそちらでハード側のテストを行ってみました。 Arduinoでやっている例は結構あるので、こちらのほうが楽かなとは思いますが、Unoでは電圧が5V系なのでその部分は注意が必要です。(リーダー系は3.3V系なので)

Arduinoで近距離無線通信 RFID-RC522 NFC by ボクにもわかる地上デジタル

github.com

f:id:ueponx:20170918044058p:plain

実際に動作させてみると…一応、リードできてる?UIDは普通に読めていますがデータブロックは読み取れていません。 (たまにブロックは読み込めることもある…。RFIF側のUIDの読み取りに空振ることもあるぽい…。) ハード的にはかなり怪しいようです。

粘ってみる…その1

更に情報をネットを検索すると

Raspberry PI 3 and RFID-RC522 Problem reading data - Raspberry Pi Forums

なる情報もdtoverlay=spi0-hw-cs/boot/config.txtの設定の追加のようです。

$ sudo vim /boot/config.txt

【変更前】

(略)
# Uncomment some or all of these to enable the optional hardware interfaces
dtparam=i2c_arm=on
#dtparam=i2s=on
dtparam=spi=on
(略)

↓ 【変更後】

(略)
# Uncomment some or all of these to enable the optional hardware interfaces
dtparam=i2c_arm=on
#dtparam=i2s=on
dtparam=spi=on
dtoverlay=spi0-hw-cs
(略)

dtはDeviceTreeの略のようです、最近ではraspi-configではなく/boot/config.txtで設定を行う様です。 でも、こちらを追加したのですが駄目でした。

粘ってみる…その2

ならばということでそもそもOSを変えてやれってバージョンを2つ落として2015-05-05-raspbian-wheezyにしてみました。 これで動かなかったらハードだろぐらいの気持ちです。

wheezyでは、jessie以降にはなかったDevice Treeの設定はちゃんとraspi-configコマンド内にも有効無効は存在しています。 それを設定すると検索でヒットするものと同じ状態になるみたいです。

$ dmesg  | grep spi
[    6.733677] bcm2708_spi bcm2708_spi.0: master is unqueued, this is deprecated
[    7.008711] bcm2708_spi bcm2708_spi.0: SPI Controller at 0x20204000 (irq 80)

$ lsmod |grep spi
regmap_spi              2307  1 snd_soc_wm8804
spi_bcm2708             6018  0

$ ls /dev/spidev* -l
crw-rw---T 1 root spi 153, 0  11  1970 /dev/spidev0.0
crw-rw---T 1 root spi 153, 1  11  1970 /dev/spidev0.1

ですが、動作しません。ムキー。

f:id:ueponx:20170918155520p:plain

粘ってみる…その3

よくわからない気分になってきたので、更に粘ってみました。今度はIchigoJamです。

fukuno.jig.jp

ドライバーもなにもないので、これでだめならハードが悪い(もうそんな気分ですが)と諦められます。

f:id:ueponx:20170918165753j:plain

結果、駄目でした…読み取り待ちにはなってくれるのですが、それ以上の処理には続かないようです。 このためにIchigoJamの使い方をさらに勉強とは、すごい自分にもやる気があるんだなと思いました(棒)

f:id:ueponx:20170918155910p:plain

終わりに

ハードさえ動けばやりたいこと全部やりきった感あります。 新規にハード買うかして再チャレンジしたいと思います。

f:id:ueponx:20170918160821p:plain

2日使ったのでかなり寝たいです。

RaspberryPiでTwitterのタイムラインを読み上げてみる(Amazon Polly & Twitter & Python)

RaspberryPiでTwitterのタイムラインを読み上げてみる(Amazon Polly & Twitter & Python

以前のエントリーでAmazon Polly(以下Polly)を使っていましたが、流石にもう少しちゃんと動くものを作ってみようかなと思います。具体的にはTwitterのタイムラインを取得してそれを読み上げようと思います。これまでも似たようなものとして、OpenJTalkで読み上げを行っていましたがOpenJTalkでは文章量が長くなると途中できれてしまうということが稀にあり、Pollyではそんなこともないと信じたいのでチャレンジしてみました。(Pollyは有料サービスなんでそんなには使えないですけどね)

f:id:ueponx:20170914195730p:plain f:id:ueponx:20170914195636p:plain

この組み合わせは Polly & Twitterバードの鳥鳥コンビなのはちょっと楽しい。

インストール

TwitterのタイムラインをPythonで取得するtweepyは以前のエントリーでも使っていますが、かなり前の話なので念のためインストールについても書いておきます。といってもpipを使えばそれでOKです。

uepon.hatenadiary.com

$ sudo pip install tweepy
Collecting tweepy
  Downloading tweepy-3.5.0-py2.py3-none-any.whl
Requirement already satisfied: six>=1.7.3 in /usr/lib/python2.7/dist-packages (from tweepy)
Requirement already satisfied: requests>=2.4.3 in /usr/local/lib/python2.7/dist-packages (from tweepy)
Requirement already satisfied: requests-oauthlib>=0.4.1 in /usr/lib/python2.7/dist-packages (from tweepy)
Requirement already satisfied: oauthlib>=0.6.2 in /usr/lib/python2.7/dist-packages (from requests-oauthlib>=0.4.1->tweepy)
Installing collected packages: tweepy
Successfully installed tweepy-3.5.0

とても簡単です。

あとはPollyですが、これでも以前のエントリーを参考にしてインストールしておきます。bot3のインストールを行えばOKです。

uepon.hatenadiary.com

これでPythonからTwitterPollyを使う準備が整いました。

読み上げプログラムを作ってみる

行おうとしているプログラムは基本的には以下の動作から構成されます。

  • Twitterの認証(tweepyモジュールの使用)
  • タイムラインの取得(tweepyモジュールの使用)
  • タイムラインの不要な部分の削除(正規表現:reモジュールの使用)
  • Pollyへテキストの送信とファイルへの保存(bot3へのアクセス)
  • pygameのmixerで再生

こちらがソースの全体となります。(関数化とかしてない…)

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import tweepy
import re
import datetime

from boto3 import Session
from botocore.exceptions import BotoCoreError, ClientError
from contextlib import closing
import os
import sys
import subprocess
import pygame.mixer
import time

# 個別にTwitterクライアント作成時に設定されるCONSUMER_KEY、CONSUMER_SECRETを設定してください
CONSUMER_KEY = 'XXXXXXXXXXXXXXXXXXX'
CONSUMER_SECRET = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'

auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
auth_url = auth.get_authorization_url()

print('Please authorize: ' + auth_url)
verifier = raw_input('PIN: ').strip()

auth.get_access_token(verifier)

print("ACCESS_KEY = '%s'" % auth.access_token)
print("ACCESS_SECRET = '%s'" % auth.access_token_secret)

ACCESS_KEY = auth.access_token
ACCESS_SECRET = auth.access_token_secret

auth.set_access_token(ACCESS_KEY, ACCESS_SECRET)
api = tweepy.API(auth)
print(api.me().name)

now = datetime.datetime.now()
print(now)

# api.update_status('Tweepy! Sample start! ' + str(now))

session = Session(region_name="us-west-2")
polly = session.client("polly")

public_tweets = api.home_timeline()
for i, tweet in enumerate(public_tweets):
    print('---{}---'.format(i))
    print(tweet.author.screen_name + ":")
    # print(tweet.text)

    text = re.sub('RT', "", tweet.text)
    text = re.sub(r'https?://[\w/:%#\$&\?\(\)~\.=\+\-…]+', "", text)
    text = re.sub(r'[##]([\w一-龠ぁ-んァ-ヴーa-z]+)', "", text)
    text = re.sub(r'@([\w]+):', "", text)
    text = re.sub('\s', "", text)

    print(text)

    try:
        response = polly.synthesize_speech(
            Text=text, OutputFormat="mp3", VoiceId="Mizuki")
    except (BotoCoreError, ClientError) as error:
        print(error)
        sys.exit(-1)
    if "AudioStream" in response:
        with closing(response["AudioStream"]) as stream:
            output = "speech.mp3"
            try:
                with open(output, "wb") as file:
                    file.write(stream.read())
            except IOError as error:
                print(error)
                sys.exit(-1)
        print("synthesize_speech OK ->>" + output)
    else:
        print("Could not stream audio")
        sys.exit(-1)

    pygame.init()
    pygame.mixer.init()
    pygame.mixer.music.load("speech.mp3")
    pygame.mixer.music.play()
    print("PLAY")
    while pygame.mixer.music.get_busy() == True:
        continue
    print("END")

    pygame.mixer.music.stop()
    pygame.mixer.quit()
    pygame.quit()

ソースの説明

以下では部分的に説明をしていきます。

Twitterの認証(tweepyモジュールの使用)

以下の部分でTwitterに関する認証を行っています。 CONSUMER_KEYCONSUMER_SECRETTwitterのアプリの作成画面に出ます。これを使って認証用のURLを呼び出し、PIN生成を行っていきます。 表示されるURLをブラウザに与えることでPIN表示まで持っていきます。

f:id:ueponx:20170916083004p:plain

起動すると以下のようなPINの入力待ち画面になります。

f:id:ueponx:20170914222010p:plain

この処理を行っているのが以下の処理になります。

CONSUMER_KEYCONSUMER_SECRETの値がXXXXX…となっていますがTwitterのアプリケーション設定画面の

  • Consumer Key (API Key)
  • Consumer Secret (API Secret)

の値を入れるようにしてください。

f:id:ueponx:20170914222407p:plain

Pythonの処理部分】

# 個別にTwitterクライアント作成時に設定されるCONSUMER_KEY、CONSUMER_SECRETを設定してください
CONSUMER_KEY = 'XXXXXXXXXXXXXXXXXXX'
CONSUMER_SECRET = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'

auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
auth_url = auth.get_authorization_url()

print('Please authorize: ' + auth_url)
verifier = raw_input('PIN: ').strip()

表示されたURLをクリックすると

f:id:ueponx:20170914222645p:plain

認証画面に遷移します。【連携アプリを認証】をクリックします。(ログインを事前にしていないと、ログインを促された後にこの画面になるかもしれません。)

f:id:ueponx:20170914222912p:plain

すると7桁のPINコードが表示されるので、この値をコンソールに入力します。

PINを入力すると実際に使用するACCESS_TOKENACCESS_TOKEN_SECRETを取得する処理となります。

Pythonの処理部分】

auth.get_access_token(verifier)

print("ACCESS_KEY = '%s'" % auth.access_token)
print("ACCESS_SECRET = '%s'" % auth.access_token_secret)

ACCESS_KEY = auth.access_token
ACCESS_SECRET = auth.access_token_secret

auth.set_access_token(ACCESS_KEY, ACCESS_SECRET)
api = tweepy.API(auth)

これでアプリケーションの使用に必要なACCESS_KEYACCESS_TOKEN_SECRETの取得ができました。あとは認証処理に与えれば完了となりますauth.set_access_token(ACCESS_KEY, ACCESS_SECRET)の部分になります。これでTwitter APIの処理を行える様になりました。 本来であればこれらキーはファイルに保存して再利用するのですが、今回は端折っています。保存する場合にはアクセス権などには注意してください。

タイムラインの取得(tweepyモジュールの使用)

APIの使用ができるようになればタイムラインの取得は簡単です。今回はHomeタイムラインを取得しています。

Pythonの処理部分】

public_tweets = api.home_timeline()
for i, tweet in enumerate(public_tweets):
    print('---{}---'.format(i))
    print(tweet.author.screen_name + ":")
    # 表示する場合には以下のコメントを外してください
    # print(tweet.text)

取得したタイムラインはデフォルト件数は20件になります。必要であれば引数に追加を与えることになりますが、Pollyが有料サービスなのでこのままにしておきます。

タイムラインの不要な部分の削除(正規表現:reモジュールの使用)

タイムラインの文面から不要なURLなどを正規表現で削除して読み上げに適した文面に変換します。さすがにURLを読み上げられても?となってしまうので。

以下は正規表現でURL、【RT】接頭辞、ハッシュタグ、ユーザ名の削除を行いプレーンな文章に変換をして行きます。

Pythonの処理部分】

    text = re.sub('RT', "", tweet.text)
    text = re.sub(r'https?://[\w/:%#\$&\?\(\)~\.=\+\-…]+', "", text)
    text = re.sub(r'[##]([\w一-龠ぁ-んァ-ヴーa-z]+)', "", text)
    text = re.sub(r'@([\w]+):', "", text)
    text = re.sub('\s', "", text)

これで完璧というわけではないのですがおおよそ大丈夫かなと思います。

Pollyへテキストの送信とファイルへの保存(bot3へのアクセス)

得られたタイムラインのデータをbot3にアクセスして、Pollyが読み上げした音声のデータを取得していきます。

このあたりは過去エントリー参照で大丈夫でしょう。

uepon.hatenadiary.com

Pythonの処理部分】

    print(text)

    try:
        response = polly.synthesize_speech(
            Text=text, OutputFormat="mp3", VoiceId="Mizuki")
    except (BotoCoreError, ClientError) as error:
        print(error)
        sys.exit(-1)
    if "AudioStream" in response:
        with closing(response["AudioStream"]) as stream:
            output = "speech.mp3"
            try:
                with open(output, "wb") as file:
                    file.write(stream.read())
            except IOError as error:
                print(error)
                sys.exit(-1)
        print("synthesize_speech OK ->>" + output)

この処理が終わるとPollyで生成された音声ファイルはspeech.mp3とされて保存されます。

pygameのmixerで再生

あとは変換された音声を再生するだけです。

Pythonの処理部分】

    pygame.init()
    pygame.mixer.init()
    pygame.mixer.music.load("speech.mp3")
    pygame.mixer.music.play()
    print("PLAY")
    while pygame.mixer.music.get_busy() == True:
        continue
    print("END")

    pygame.mixer.music.stop()
    pygame.mixer.quit()
    pygame.quit()

こちらも先程のエントリーを参考にしてもらえればと思います。(初期化処理・終了処理は毎回しなくてもいいかなとは思いますが。)

終わりに

ようやくPythonを使ってTwitterのタイムラインを取得して読み上げることができました。 まあ、読み上げられた文面もなんとか分かるかなという感じですがAAや絵文字なんかがあると謎の文面になってしまいますね。さすがに仕方ないですね。 長い文章での読み上げがうまくいくなら、ネットワークアクセスがある分遅くなってしまいますのでOpenJTalkの方がいいかも。

コンソールTwitterクライアント```rainbowstream```をつかってみた

コンソールTwitterクライアントrainbowstreamをつかってみた

なんとなく、linux環境でもコンソールでTwitterのタイムラインを見てみたいなあと思って、クライアントをさがしていました。(家で使うのはRaspbianだけなんで気まぐれな欲求なんですが)

その中でもなかなかいいなと思ったのがrainbowstreamでした。

テーマで色分けができたりというのがかなり見やすくていいかなと思いますし、気合を入れれば画面もそれなりにわかるというのもすごいと思います。そういう用途はGUI環境でやればいいとは思いますが。

ってことでインストールしてみました。

rainbowstreamのインストール

以下のリンクを参照にすれば簡単にインストールできます。環境はpythonなので本体はpipでのインストールになりますが、いろいろLibraryの依存があるのでそれも一緒にインストールする必要があります。

github.com

ドキュメントにはvirtualenvを使った方がいいということでしたが、今回は端折ってそのままインストールしています。

$ sudo apt-get install python-dev libjpeg-dev libfreetype6 libfreetype6-dev zlib1g-dev
$ sudo pip install rainbowstream

【参考】 Pipenv & Virtual Environments — The Hitchhiker's Guide to Python

インストールは問題なく終わると思います。(本体以外のLibraryもインストール済みのものがほとんどでした。)

rainbowstreamを実行してみる

実行は以下のようにするだけです。

$ rainbowstream

初回の実行ではTwitterアプリケーションのPIN認証が求められるので

f:id:ueponx:20170913232333p:plain

画面に表示されたURLをクリックしてブラウザで認証を行います。リンクをクリックすると以下のような画面になるので

f:id:ueponx:20170910153740p:plain

【連携アプリを認証】ボタンをクリックします。すると7桁数字のPINが表示されるのでそれをコピーなどしてコンソールに入力します。(画面では伏せています)

※2回目以降の起動では初回起動時の認証情報が~/.rainbow_oauthに格納されるでの認証は不要です。

f:id:ueponx:20170910154556p:plain

するとログインが行われて

f:id:ueponx:20170910154656p:plain

コマンドモードになりますが、このままほっておくと自分のタイムラインが表示されます。

f:id:ueponx:20170910155018p:plain

コマンドマニュアルはこちらになります。

Rainbow Stream — RainbowStream 1.3.7 documentation

おわりに

複数のTLを監視しない範囲であればコマンドラインでの実行でもかなりいいかなと思います。数世代前のPCでLinuxを動かす場合にはこちらは軽快かもしれませんねです。(Aspire one 753 Celeron U3400 / 2GBメモリー / USBメモリブート/ RaspberryPixel86)

本当は、この結果が標準出力されて、それを使ってpollyとかから読み上げでも行おうと思っていたのですが、標準出力はしてくれないようです。残念。 次回では真面目にPythonでTweepyを使ってタイムラインを拾って読み上げをさせてみたいと思います。

yapfの導入

yapfの導入

個人の開発スタイルのような感じですが、Windowsなどでpythonのソースを書いていて、vimへコピー・ペーストをすることがよくあるのですが、何故かインデントのフォーマットが異なってしまうので、そのあとの手間が多いのでなんとかしないと行けないなあと思っていました。(他の言語ならそれほど大きく問題はありませんが、pythonではそういうわけにも行きません。)

そこで、yapfとういうフォーマッタがあることがわかりました。

github.com

googleさん謹製のようです。

yapfのインストール

readmeを見ればわかりますが以下の様にインストールします。

$ pip install yapf
Collecting yapf
  Downloading yapf-0.17.0-py2.py3-none-any.whl (151kB)
    100% |????????????????????????????????| 153kB 1.1MB/s
Installing collected packages: yapf
Successfully installed yapf-0.17.0

raspberrypiでインストールするとyapf~/.local/bin/yapfにインストールされるようです。

実行するとこんな感じになります。

$ ~/.local/bin/yapf --version
yapf 0.17.0

$ ~/.local/bin/yapf --help
usage: yapf [-h] [-v] [-d | -i] [-r | -l START-END] [-e PATTERN]
            [--style STYLE] [--style-help] [--no-local-style] [-p]
            [files [files ...]]

Formatter for Python code.

positional arguments:
  files

optional arguments:
  -h, --help            show this help message and exit
  -v, --version         show version number and exit
  -d, --diff            print the diff for the fixed source
  -i, --in-place        make changes to files in place
  -r, --recursive       run recursively over directories
  -l START-END, --lines START-END
                        range of lines to reformat, one-based
  -e PATTERN, --exclude PATTERN
                        patterns for files to exclude from formatting
  --style STYLE         specify formatting style: either a style name (for
                        example "pep8" or "google"), or the name of a file
                        with style settings. The default is pep8 unless a
                        .style.yapf or setup.cfg file located in one of the
                        parent directories of the source file (or current
                        directory for stdin)
  --style-help          show style settings and exit; this output can be saved
                        to .style.yapf to make your settings permanent
  --no-local-style      don't search for local style definition
  -p, --parallel        Run yapf in parallel when formatting multiple files.
                        Requires concurrent.futures in Python 2.X

テスト

公式サイトのサンプルを使って実験してみます。

github.com

x = {  'a':37,'b':42,

'c':927}

y = 'hello ''world'
z = 'hello '+'world'
a = 'hello {}'.format('world')
class foo  (     object  ):
  def f    (self   ):
    return       37*-+2
  def g(self, x,y=42):
      return y
def f  (   a ) :
  return      37+-+a[42-x :  y**3]

f:id:ueponx:20170910140531p:plain

$ ~/.local/bin/yapf sample.py
Traceback (most recent call last):
  File "/home/pi/.local/bin/yapf", line 11, in <module>
    sys.exit(run_main())
  File "/home/pi/.local/lib/python2.7/site-packages/yapf/__init__.py", line 296, in run_main
    sys.exit(main(sys.argv))
  File "/home/pi/.local/lib/python2.7/site-packages/yapf/__init__.py", line 188, in main
    parallel=args.parallel)
  File "/home/pi/.local/lib/python2.7/site-packages/yapf/__init__.py", line 236, in FormatFiles
    in_place, print_diff, verify)
  File "/home/pi/.local/lib/python2.7/site-packages/yapf/__init__.py", line 259, in _FormatFile
    logger=logging.warning)
  File "/home/pi/.local/lib/python2.7/site-packages/yapf/yapflib/yapf_api.py", line 91, in FormatFile
    verify=verify)
  File "/home/pi/.local/lib/python2.7/site-packages/yapf/yapflib/yapf_api.py", line 129, in FormatCode
    tree = pytree_utils.ParseCodeToTree(unformatted_source)
  File "/home/pi/.local/lib/python2.7/site-packages/yapf/yapflib/pytree_utils.py", line 102, in ParseCodeToTree
    tree = parser_driver.parse_string(code, debug=False)
  File "/usr/lib/python2.7/lib2to3/pgen2/driver.py", line 106, in parse_string
    return self.parse_tokens(tokens, debug)
  File "/usr/lib/python2.7/lib2to3/pgen2/driver.py", line 47, in parse_tokens
    for quintuple in tokens:
  File "/usr/lib/python2.7/lib2to3/pgen2/tokenize.py", line 429, in generate_tokens
    ("<tokenize>", lnum, pos, line))
  File "sample.py", line 11
    def g(self, x,y=42):
    ^
IndentationError: unindent does not match any outer indentation level

動かない…エラーメッセージから察するに、Webページのものをコピペして使うとpythonの文法まで崩れてしまっているようです。

Teratermのウインドウにドラッグ・アンド・ドロップすることで実行できる、scpなどでファイル転送をすると…

$ cat sample.py
x = {  'a':37,'b':42,

'c':927}

y = 'hello ''world'
z = 'hello '+'world'
a = 'hello {}'.format('world')
class foo  (     object  ):
  def f    (self   ):
    return       37*-+2
  def g(self, x,y=42):
      return y
def f  (   a ) :
  return      37+-+a[42-x :  y**3]

$ ~/.local/bin/yapf sample.py
x = {'a': 37, 'b': 42, 'c': 927}

y = 'hello ' 'world'
z = 'hello ' + 'world'
a = 'hello {}'.format('world')


class foo(object):
    def f(self):
        return 37 * -+2

    def g(self, x, y=42):
        return y


def f(a):
    return 37 + -+a[42 - x:y**3]

うまく実行できたようです。ファイルを変換したものに上書きするには-iをつければいいようです。他にも処理後との差分(diff)を表示する-d再帰的に実行する-rのスイッチがあります。

$ ~/.local/bin/yapf -i sample.py
pi@raspberrypi:~/yapf_sample $ cat sample.py
x = {'a': 37, 'b': 42, 'c': 927}

y = 'hello ' 'world'
z = 'hello ' + 'world'
a = 'hello {}'.format('world')


class foo(object):
    def f(self):
        return 37 * -+2

    def g(self, x, y=42):
        return y


def f(a):
    return 37 + -+a[42 - x:y**3]

REPL環境での実行でモジュールとして使用することもできる様です。 githubのページでは以下のようになると書いてあるのですが、

>>> from yapf.yapflib.yapf_api import FormatCode  # reformat a string of code

>>> FormatCode("f ( a = 1, b = 2 )")
'f(a=1, b=2)\n'

自分の環境では

$ python
Python 2.7.13 (default, Jan 19 2017, 14:48:08)
[GCC 6.3.0 20170124] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from yapf.yapflib.yapf_api import FormatCode  # reformat a string of code
>>> FormatCode("f ( a = 1, b = 2 )")
(u'f(a=1, b=2)\n', True)
>>>

となりました。python2だったからかな?念のためpython3でも実行してみました。

$ pip3 install yapf
Collecting yapf
  Using cached yapf-0.17.0-py2.py3-none-any.whl
Installing collected packages: yapf
Successfully installed yapf-0.17.0

$ python3
Python 3.5.3 (default, Jan 19 2017, 14:11:04)
[GCC 6.3.0 20170124] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from yapf.yapflib.yapf_api import FormatCode
>>> FormatCode("f ( a = 1, b = 2 )")
('f(a=1, b=2)\n', True)
>>> FormatCode("def g( ):\n    a=1\n    b = 2\n    return a==b", lines=[(1, 1), (2, 3)])
('def g():\n    a = 1\n    b = 2\n    return a==b\n', True)

ユニコード文字列を指すのuのプリフィックス以外は変わったところが無いみたい、あれ?

終わりに

コピペしてフォーマットが少し崩れるぐらいであれば、なんとかできるかなと思いましたが、派手に崩れる(文法的に崩れるレベル)とちょっとむずかしい様です。

RaspberryPiだけでなくWindowsのanaconda環境にもインストールしましたがこっちで使ったほうがいいのかもしれません。(コピペする前にこっちで整形をするという感じ)

/* -----codeの行番号----- */