obnizでLチカしてみた

ようやくobnizが手に入ったのでLチカをやってみました。 Lチカだけで終わらないようにしたい…

詳しくはこちらの動画をみていただくといいかなと思います。

youtu.be

obniz.io

いざ開封

自分はamazonさんから購入しました。

届くと箱に入ってこんな感じ。フリスクケースより一回り小さい感じかなと思います。

f:id:ueponx:20190114190130j:plain

箱開けるとこんな感じになっていました。

f:id:ueponx:20190114190140j:plain

表側(?)にはESP-WROOM32とOLEDが乗っています。

f:id:ueponx:20190114190148j:plain

裏にはメスのピンヘッダーとUSBコネクターがついています。 どのpinからも1A出力できるって本当なんでしょうか?動画ではモータードライバーなしで動いていました。 また、obniz Cloudを経由すればREST経由でもWebSocket経由でもGPIOへアクセスできるようです。 同一LAN内になくてもGPIOへアクセスできるのでわりと敷居が低くなっているのではないかなと思います。 WioNodeとなんとなく似ている感じですが、価格が5倍ぐらい差があるのでそのあたりは作りたいものと時間とのトレードオフかなと思います。

では設定をしていこうと思います。

obnizを設定する

obnizを使うためには、事前にobniz cloudに登録(無料)する必要があります。

まずは以下のサイトにアクセスします。

obniz.io

ウインドウの上の方にある【登録/ログイン】をクリックします。

f:id:ueponx:20190114193410p:plain

すると、認証画面に遷移します。SNSのアカウント経由で認証してもいいですし、メールアドレスでも登録が可能になっています。 今回はGoogleアカウントで登録することにしました。ボタンの中で一番上にある【Sign in with Google】をクリックします。

f:id:ueponx:20190114193847p:plain

複数アカウントを持っている場合には、アカウントを選択するダイアログが表示されるので、使用したいアカウントを選択します。 アカウントをクリックすれば登録完了になり、次の画面に遷移します。

f:id:ueponx:20190114193913p:plain

アカウント登録が終わると今度はobnizの登録を行うことになります。登録されたobnizにはURLが発行されるのでこの処理が必要になってきます。

f:id:ueponx:20190114194316p:plain

画面に表示されている【obnizを追加】ボタンをクリックします。すると次の画面に遷移します。

f:id:ueponx:20190114194638p:plain

obnizに電源を入れてWiFiの設定をするように指示されています。 では、obnizに電源を入れましょう。すると以下のような画面になると思います。

f:id:ueponx:20190114194832j:plain

画面に指示に従ってobnizのハードボタンを押してください。このハードボタンは押し込みと左右の動きができるようになっています。次の画面で認識されたアクセスポイントの一覧が見えていると思います。(アクセスポイントのSSIDは一部しか見えないのですが、選択して時間が立つとスクロールするので十分に確認をしてください) この中からハードボタンで適切なアクセスポイントを選択します。

f:id:ueponx:20190114195457j:plain

アクセスポイントのパスワードを入力します。(ハードボタンを左右に動かし、押し込むことで入力ができます)

f:id:ueponx:20190114195510j:plain

入力が完了したら【END】でハードボタンをプッシュします。 すると以下のように画面が接続処理を行う画面になります。接続が正常に行われると以下のようにQRコードが表示された画面になります。

f:id:ueponx:20190114195954j:plain

QRコードが表示されたらWeb画面に戻り

f:id:ueponx:20190114194638p:plain

【I'm ready】ボタンをクリックします。入力ボックスが表示された画面に遷移するので、

f:id:ueponx:20190114201151p:plain

obnizのOLEDに表示された数字を入力します。入力したら【Next】ボタンをクリックします。 すると以下のような画面に遷移します。

f:id:ueponx:20190114201559p:plain

obnizのOLEDもQRコードの画面から以下のように変化しています。

f:id:ueponx:20190114201735j:plain

OLEDに6桁の数値が表示されているのでWebの入力ボックスにタイプしていきます。

f:id:ueponx:20190114201624p:plain

入力が終わったら【Validate】ボタンをクリックします。

f:id:ueponx:20190114201850p:plain

以下のような画面になればアカウントにobnizが追加されています。

f:id:ueponx:20190114202132p:plain

以下にような画面になれば、事前設定は完了となります。

f:id:ueponx:20190114203851p:plain

Let's Lチカ

では、Lチカのコードを書こうと思ったのですが、初期テンプレートがWeb画面からON/OFFボタンを押すことでLEDの店頭とOLEDの表示を変化させるコードが出てきます…なにも書かなくてもできるとは。 説明は不要かもしれませんが、念の為に新規作成させてみます。

まず、ホーム画面から【プログラムをする】ボタンをクリックします。

f:id:ueponx:20190114211900p:plain

クリックするとデプロイ先の登録済みのobnizのIDの入力が求められるのでQRコードの隣に表示された数字を入力します。

f:id:ueponx:20190114211921p:plain

入力するとチュートリアルをするか尋ねられますが、そのまま【キャンセル】ボタンをクリックします。

f:id:ueponx:20190114210525p:plain

するとコードが開かれます。HTMLにJavaScriptが含まれたものになりますね。 この画面になったら画面上部にある【保存&開く】をクリックすると実行されます。

f:id:ueponx:20190114221802p:plain

実行すると以下のような画面がポップアップします。Web画面に【ON】ボタンと【OFF】ボタンがあります。

f:id:ueponx:20190114210556p:plain

obnizのOLEDにはHello Worldと表示されています。

f:id:ueponx:20190114214953j:plain

【ON】ボタンをクリックするとOLEDにはONの表示が

f:id:ueponx:20190114215030j:plain

【OFF】ボタンをクリックするとOLEDにはOFFの表示が

f:id:ueponx:20190114215057j:plain

GPIOにLEDを指しておけば【ON】ボタンでLチカできます。 (pinpin0がアノード、pin1がカソードになっているので注意してください。)

f:id:ueponx:20190114215121j:plain

ちなみに画面左側のリストにあるblockprogram.xmlのファイルをクリックすると以下の様な画面で

f:id:ueponx:20190114220555p:plain

ブロックプログラムもできるようになっています。

おわりに

ようやくobnizに手を出すことができました。JS使いの人であれば割といい選択肢かなとは思います。 python経由でのIOアクセスに関してもやってみようと思います。

M5Stackもそろそろ触らないとなあ…積み基板が多すぎる。

Twitterのツイートをwordcloudで可視化したい【後編】

前回はTwitterのタイムラインを取得するところで終了していましたが、実際に使用してみるうちに改良などをしていきました。

【前回のエントリ】 uepon.hatenadiary.com

修正のポイントを記載してから、wordcloudの処理に進めていこうと思います。(ソース内の*Twitterアプリのキーのため伏せています。)

Twitterのタイムラインを取得に使ったソース完成版】

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

import json
from requests_oauthlib import OAuth1Session
from pytz import timezone
from dateutil import parser
import time
import re
import codecs
import emoji

CONSUMER_KEY = '************************'
CONSUMER_SECRET = '************************'
ACCESS_TOKEN = '************************'
ACCESS_TOKEN_SECRET = '************************'
sinceid = -1
total_count = 0
hash_pattern = r'[##]([\w一-龠ぁ-んァ-ヴーa-z]+)'

def removeEmoji(src):
    return ''.join(c for c in src if c not in emoji.UNICODE_EMOJI)

twitter = OAuth1Session(CONSUMER_KEY,
                        CONSUMER_SECRET,
                        ACCESS_TOKEN,
                        ACCESS_TOKEN_SECRET)

url = "https://api.twitter.com/1.1/search/tweets.json"
# query = '#nhk exclude:retweets'
query = '#紅白歌合戦 exclude:retweets'
params = {
    'q': query,
    'count': 200,
    'tweet_mode': 'extended',
    'since_id': sinceid
}

print('----------------------------------------------------')

while True:
    logData = ''
    req = twitter.get(url, params=params)
    if req.status_code == 200:
        search_timeline = json.loads(req.text)
        metadata = search_timeline['search_metadata']
        metasid = metadata['since_id']
        metamid = metadata['max_id']
        total_count += len(search_timeline['statuses'])
        limit = req.headers.get('x-rate-limit-remaining', 0)
        for tweet in search_timeline['statuses']:
            text = re.sub(r'https?://[\w/:%#\$&\?\(\)~\.=\+\-…]+', "", tweet['full_text'])
            text = re.sub('\r', '', text)
            text = re.sub('\n', '', text)
            for hashtag in ((re.findall(hash_pattern, text))):
                r = r'[##]%s' % hashtag
                text = re.sub(r, '', text)
            jst_time = parser.parse(tweet['created_at']).astimezone(timezone('Asia/Tokyo'))

            logData += '"' + str(jst_time)+ '","' + str(tweet['id']) + '","' + \
                      tweet['user']['name'] + '","@' + tweet['user']['screen_name']  + '","' + \
                      text + '"\n'
            # ----------------------------------------------------
    else:
        print("ERROR: %d" % req.status_code)
    params['since_id'] = metamid + 1
    print('******')
    print('total_count::' + str(total_count))
    print('APIlimit::' + str(limit))
    # ----------------------------------------------------
    with codecs.open('./log/log.csv', mode='a', encoding='utf-8') as f:
        print(removeEmoji(logData), file=f, end='')
    # ----------------------------------------------------
    time.sleep(5)

Twitterでタイムラインを収集した際のポイント

先程の完成版で修正などを行ったところをメモしておきます。

Tweetの時刻がUTCとして記録されているので、JSTに変更したい

ツイートに含まれる生成時の時刻create_atUTCで表現されています。そのままだとわかりにくいのでJSTに変更するようにしました。 Pythonタイムゾーンや時刻を扱うにはpytzタイムゾーン処理)、python-dateutil(時刻処理)を使うことが多いようでしたので、 今回はそれを使用しています。

【参考】 qiita.com

【パッケージのインストール】

$ pip install pytz
$ pip install python-dateutil

UTCのDate文字列をJSTのDate文字列に変換する処理

【サンプル】

from pytz import timezone
from dateutil import parser

utc_string = "Sat Mar 19 06:17:57 +0000 2016"
jst_time = parser.parse(utc_string).astimezone(timezone('Asia/Tokyo'))
print(jst_time)
# 出力:2016-03-19 15:17:57+09:00

この処理を組み入れてツイート取得時に含まれる生成時刻create_atを以下のように記述して変換しています。

【プログラム抜粋】

while True:
    logData = ''
    req = twitter.get(url, params=params)
    if req.status_code == 200:
        search_timeline = json.loads(req.text)
【中略】
        for tweet in search_timeline['statuses']:
【中略】
            jst_time = parser.parse(tweet['created_at']).astimezone(timezone('Asia/Tokyo'))
【中略】

140文字以上のツイートを取得する

以前、twitterの書き込みが140文字以上になったというリリースがありましたが、現在はこのような長いツイートがあると全文が表示されず、途中省略されて全文へのリンクが表示されるようになっています。公式ドキュメントを確認すると以下のようになっています。

Tweet updates — Twitter Developers

ドキュメントの表内のExtendedの部分がそれに当たります。 APIアクセス時のパラメータにtweet_mode=extendedを追加し、戻ってきたJSONデータに関しては、これまでのツイート本文であったtextの代わりにfull_textを使えば良いと記載があります。これに合わせてソースを変更します。

【プログラム抜粋】

【中略】
query = '#nhk exclude:retweets'
params = {
    'q': query,
    'count': 200,
    'tweet_mode': 'extended',
    'since_id': sinceid
}
【中略】
while True:
    logData = ''
    req = twitter.get(url, params=params)
    if req.status_code == 200:
【中略】
        for tweet in search_timeline['statuses']:
            text = re.sub(r'https?://[\w/:%#\$&\?\(\)~\.=\+\-…]+', "", tweet['full_text'])

これで140文字以上のツイートも取得できます。

WindowsUTF-8文字列をファイルに書き込もうとするとコードエラーが発生する

取得したデータをcsvファイルに格納しようとするとエンコードエラーが発生してしまいました。 Python3であればUTF-8などの文字コードに関してはそれほど気にしなくてもいいのかなと思っていたのでちょっと驚きました。 更に開発に使用している画面表示に関してはPycharm側のコンソールを使っているのでUTF-8の表示も問題ありませんので、更に気がつくまでに時間がかかりました。

今回の問題はWindows側だけに発生しているようです。以下が参考になりました。

go-journey.club

簡単に言うとPython側で処理した文字列をファイル保存する際にうまく処理がおこわなれないのが原因のようです。 そこでcodecというモジュールを使用し、さらに書き込み時にはwrite()ではなくprint()を使用し、更に引数にfileを指定することで解決ができます。

【サンプル】

import codecs

# 最初に文字コードを指定して「追加」モードでログファイルを開く
f = codecs.open('/var/log/python/app.log', 'a', 'utf-8')
new_words = 'データ'
# print()で書き込みを行う。出力先の引数にfileを指定する
print(new_words, file=f)

これを使用しています。途中removeEmoji()とありますがユーザ定義の関数になりますので、単にここに文字列の変数を入れれば問題ありません。 これが入っている理由はemojiが入っているテキストでは最終目的であるwordcloudで処理ができないためです。

【プログラム抜粋】

【中略】
    with codecs.open('./log/log.csv', mode='a', encoding='utf-8') as f:
        print(removeEmoji(logData), file=f, end='')
【中略】

ちなみにemojiを扱うパッケージは、そのままemojiとなりますのでインストールもそのままでOKです。

【インストール】

$ pip install emoji

これを使用して以下のようにemoji削除の関数を作成しています。

def removeEmoji(src):
    return ''.join(c for c in src if c not in emoji.UNICODE_EMOJI)

長時間動作中にKeyErrorエラーが発生する

プログラムを長時間動作させていると極稀にKeyErrorが発生することがあります。 エラーに該当している部分、取得したJSONのHeaderの中にキーが含まれないことがあることに起因します。むしろそんなことがあるのか?と驚きましたがあるようです。

そこで取得する方法をget()を使うことでキーが存在しなければ、指定したデフォルト値を返すという方法に変更しました。

【変更前】

        limit = req.headers['x-rate-limit-remaining']

【変更後】

        limit = req.headers.get('x-rate-limit-remaining', 0)

これで長時間動作も可能になりました。(エラーは数時間やって一回程度のものです。)

そろそろWordCloudが使いたい…

ようやくデータが取得できたので目的であるWordCloudを使う準備をしてみたいと思います。

パッケージのGitHubのサイトは以下になります。

github.com

テストされているPythonのバージョンは、2.7、3.4、3.5、3.6、3.7のようです。

WordCloudのライブラリ

$ pip install wordcloud

依存するパッケージも同時にインストールされるので、素の状態であれば以下のパッケージがインストールされます。

  • pillow
  • numpy
  • wordcloud

このパッケージをインストールすると、CLI環境で使用できるwordcloud_cliというツールも同時にインストールされます。 こちらを使ってもテストができます。

【使用例】

$ wordcloud_cli --text mytext.txt --imagefile wordcloud.png

GitHubにあるサンプルを使用して、実験を行ってみます。

【alice.txt】 https://raw.githubusercontent.com/amueller/word_cloud/master/examples/alice.txt

【今回の実行例】

$ wordcloud_cli --text alice.txt --width 640 --height 480 --imagefile wordcloud.png

特に大きなエラーも発生せず、問題なく生成されています。

f:id:ueponx:20190101195212p:plain

では、このまま日本語のテキストもやってみようと思います。 以下のようなテキストを準備します。単語と単語の間にスペースを開ける必要があるようなのでこのようにしてみました。

【Jsamplet.txt】

今日 明日 明後日 晴れ 雨 しかし

以下のように実行してみると…

$ wordcloud_cli --text Jsample.txt --width 640 --height 480 --imagefile wordcloud.png

おや?文字が出ません。

f:id:ueponx:20190101201049p:plain

ググってみると日本語ではフォントファイルの設定をしないと表示できないということがかかれていたので --fontfileオプションでフォントファイルを設定して以下のように実行してみました。

$ wordcloud_cli --text Jsample.txt --fontfile C:\Windows\Fonts\meiryo.ttc --width 640 --height 480 --imagefile wordcloud.png

これでうまく日本語が表示されました。

f:id:ueponx:20190101202337p:plain

これからはPythonのプログラムと連携させて生成するようにしていきます。

(注)やっていてわかったのですが、wordcloudは画像を生成する際に?内部でmatplotlibモジュールを使用しているようなので、 もしインストールを行っていない場合には事前にインストールを行う必要があるようです。ほんと?

matplotlibモジュールのインストール】

$ pip install matplotlib

【wordcloudの画像生成】

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import os
from wordcloud import WordCloud

contents = open('Jsample.txt', encoding="utf-8").read()
# contents = '今日 明日 明後日 晴れ 雨 しかし'

fpath = 'C:\\Windows\\Fonts\\meiryo.ttc'
wordcloud = WordCloud(background_color="white", font_path=fpath, width=900, height=500).generate(contents)
wordcloud.to_file("./wordcloud_sample.png")

実行するとこんな感じに生成されます。今回は背景色を白に指定しています。

f:id:ueponx:20190101213721p:plain

あとはTwitterのタイムラインとこのプログラムを結合することになります。 ただ、wordcloudの入力に与えられるテキストデータは単語をスペースを開けた形にする必要があるのでデータの整形が必須となります。

入力データの整形を行う

今回収集しているデータはCSVなのでそのうちのツイートを取り出し、更にテキストの分かち書きを行うことになります。 分かち書きは簡単に言うと「文章を品詞分解を行い、単語ごとに取り出す」ことになります。この分かち書きには形態素解析エンジンを使用します。

以前のエントリではMecabという形態素解析エンジンを使用していましたが今回は違うアプローチをしたほうが面白いと思ったのでjanomeという 形態素解析エンジンを使用することにします。

Python形態素解析エンジンjanome

Janomeは、Mecabと比べると実行速度は劣りますが、Pythonのみで実装されていて辞書も内包されている点が特徴となります。 pipコマンドだけでインストールできる容易さも魅力です。詳細は以下をお読みください。

janomeドキュメント】

Welcome to janome's documentation! (Japanese) — Janome v0.3 documentation (ja)

インストールは以下でOKです。

$ pip install janome

まずは、形態素解析のサンプルを作ってみます。janome公式のドキュメントを見ながら作ってみると以下のようになります。

【サンプル】

from janome.tokenizer import Tokenizer

Sentence='ことしも熱い戦いをありがとう'

t = Tokenizer()
tokens = t.tokenize(Sentence)

for token in tokens:
    print("表層形:",token.surface,"\n"
          "品詞:",token.part_of_speech.split(',')[0],"\n"
          "品詞細分類1:",token.part_of_speech.split(',')[1],"\n"
          "品詞細分類2:",token.part_of_speech.split(',')[2],"\n"
          "品詞細分類3:",token.part_of_speech.split(',')[3],"\n"
          "活用型:",token.infl_type,"\n"
          "活用形:",token.infl_form,"\n"
          "原形:",token.base_form,"\n"
          "読み:",token.reading,"\n"
          "発音:",token.phonetic)
    print('-'*32)

【サンプルの実行結果】

$ python.exe janome_sample.py
表層形: ことし 
品詞: 名詞 
品詞細分類1: 副詞可能 
品詞細分類2: * 
品詞細分類3: * 
活用型: * 
活用形: * 
原形: ことし 
読み: コトシ 
発音: コトシ
--------------------------------
表層形: も 
品詞: 助詞 
品詞細分類1: 係助詞 
品詞細分類2: * 
品詞細分類3: * 
活用型: * 
活用形: * 
原形: も 
読み: モ 
発音: モ
--------------------------------
表層形: 熱い 
品詞: 形容詞 
品詞細分類1: 自立 
品詞細分類2: * 
品詞細分類3: * 
活用型: 形容詞・アウオ段 
活用形: 基本形 
原形: 熱い 
読み: アツイ 
発音: アツイ
--------------------------------
表層形: 戦い 
品詞: 名詞 
品詞細分類1: 一般 
品詞細分類2: * 
品詞細分類3: * 
活用型: * 
活用形: * 
原形: 戦い 
読み: タタカイ 
発音: タタカイ
--------------------------------
表層形: を 
品詞: 助詞 
品詞細分類1: 格助詞 
品詞細分類2: 一般 
品詞細分類3: * 
活用型: * 
活用形: * 
原形: を 
読み: ヲ 
発音: ヲ
--------------------------------
表層形: ありがとう 
品詞: 感動詞 
品詞細分類1: * 
品詞細分類2: * 
品詞細分類3: * 
活用型: * 
活用形: * 
原形: ありがとう 
読み: アリガトウ 
発音: アリガトー
--------------------------------

Process finished with exit code 0

形態素解析の結果でwordcloudで使用したいのは今回使用するのは表層系品詞ぐらいでしょうか。 助詞や接続詞などが多く出てきても面白くないですしね。(ツイッターでは比較的短文が多いので、それらの品詞あまり数はないと思いますが)

では次は取得したデータを使って分かち書きの出力を行おうと思います。

ツイートデータから分かち書きファイルを生成する

取得したツイートのデータから形態素解析を行って分かち書きを行ってみます。

取得したツイートデータの形式は

  • 書き込み時刻
  • TweetしたユーザID
  • Username(ユーザ名)
  • Screenname(@のついたユーザ名)
  • Tweetテキスト

となっているのでCSVの第4フィールドを取り出して形態素解析を行い、スペースで分かち書きした形式で出力を行います。

import csv
from janome.tokenizer import Tokenizer
import codecs

wakachi = ''

with codecs.open('./wakachi_log.txt', mode='w', encoding='utf-8') as fw:
    None

t = Tokenizer()
with open('log.csv', 'r', encoding="utf-8") as f:
    reader = csv.reader(f)
    for row in reader:
        tweet = row[4]

        word_list = []
        tokens = t.tokenize(tweet)
        for token in tokens:
            word = token.surface
            word_base = token.base_form
            partOfSpeech = token.part_of_speech.split(',')[0]
            if token.base_form in ["ある", "なる", "こと", "よう", "そう", "これ", "それ", "する", "いる", "いい"]:
                continue
            if partOfSpeech in ["形容詞", "動詞", "名詞", "代名詞", "副詞"]:
                if (partOfSpeech == "名詞"):
                    if (token.part_of_speech.split(',')[1] in ["数", "接尾", "助数詞", "非自立"]):
                        continue
                elif (partOfSpeech == "動詞"):
                    if (token.part_of_speech.split(',')[1] not in ["自立"]):
                        continue
                elif (partOfSpeech == "形容詞"):
                    if (token.part_of_speech.split(',')[1] not in ["自立"]):
                        continue
                elif (partOfSpeech == "副詞"):
                    if (token.part_of_speech.split(',')[1] in ["助詞類接続"]):
                        continue
                word_list.append(word_base)
                print("表層形:", token.surface, "\n"
                    "品詞:", token.part_of_speech.split(',')[0], "\n"
                    "品詞細分類1:", token.part_of_speech.split(',')[1], "\n"
                    "品詞細分類2:", token.part_of_speech.split(',')[2], "\n"
                    "品詞細分類3:", token.part_of_speech.split(',')[3], "\n"
                    "活用型:", token.infl_type, "\n"
                    "活用形:", token.infl_form, "\n"
                    "原形:", token.base_form, "\n"
                    "読み:", token.reading, "\n"
                    "発音:", token.phonetic)
            print('-' * 8)

        wakachi = " ".join(word_list)
        with codecs.open('./wakachi_log.txt', mode='a', encoding='utf-8') as fw:
            print(wakachi, file=fw)

いろいろノイズが多いので品詞で絞ったり、あってもよくわからないNGワードは抜いています。 出来上がったファイルは以下のようになりました。

【wakachi_log.txt】

f:id:ueponx:20190103163505p:plain

あとは出力されたファイルをwordcloudの処理にかければOKです。 前述のファイルを少し変えて以下のようにしました。

【wc.py】

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import os
from wordcloud import WordCloud

contents = open('wakachi_log.txt', encoding="utf-8").read()

fpath = 'C:\\Windows\\Fonts\\meiryo.ttc'
wordcloud = WordCloud(background_color="white", font_path=fpath, width=900, height=500).generate(contents)
wordcloud.to_file("./wordcloud.png")

箱根駅伝直後の数分のデータで生成すると以下のようになりました。

f:id:ueponx:20190103163941p:plain

割といい感じになっているような気がします。

ようやくお楽しみの時間へ

いくつかの番組をwordcloudの処理をかけてみました。

NHK紅白歌合戦 12/31 19時頃放送】

NGワード調整前

f:id:ueponx:20190103180840p:plain

いろいろ抽象的なものが多かったので、除外ワード追加調整後

f:id:ueponx:20190107080322p:plain

ぐるナイ おもしろ荘 12/31 24:30頃放送】

f:id:ueponx:20190103193948p:plain

フットンダ 12/31 26:00頃放送】

f:id:ueponx:20190103170050p:plain

【第95回箱根駅伝 往路 1/2 8:00頃放送】

f:id:ueponx:20190103200926p:plain

【第95回箱根駅伝 復路 1/3 8:00頃放送】

f:id:ueponx:20190103203314p:plain

おわりに

割と面白い結果がでてきますね。 それにしてもデータの取得さえしておくといろいろ楽しめます。

Twitterのツイートをwordcloudで可視化したい【前編】

まえまえから思っていたんですが、まったく手をつけられていませんでした。ただ、年末という非常にツイートデータが大量に手に入る時期なのでやるのは今だと思ってやります。ただいろいろやっている人もいるので、大した違いはないかなと思います。

Twitterアプリの作成

【注意】もしすでに作成済みのアプリがあるようであれば、そちらを使ったほうが時間がかなり短縮できるのでオススメです。

以前にもTwitterアプリを作成したのでそれが参考になるかなと思います。

【参考】 uepon.hatenadiary.com

ただ、UIがちょっと変わっているかなと思いますので久しぶりに作成してみます。

Twitterにログインしている状態で以下のURLへいくと新規アプリ作成(アプリ一覧)ページに遷移します。 過去エントリの参考のURLからでも行けるようになっていますが、URLは今後変わるので新しいものを記載しています。

https://developer.twitter.com/en/apps

ページにはこれまでに作成したTwitterアプリの一覧が表示されます。

f:id:ueponx:20181230121049p:plain

画面右上にある【Create an app】ボタンをクリックすると

f:id:ueponx:20181230122030p:plain

確認のダイアログが表示されるので【Apply】ボタンをクリックします。

f:id:ueponx:20181230122147p:plain

以前はアカウントに携帯電話番号の登録することなくアプリの作成ができましたが、現在ではできないようなので以下の画面が出た場合には登録する必要があります。 【Add a valid phone number】ボタンをクリックして登録を行います。

f:id:ueponx:20181230122746p:plain

クリックするとダイアログが表示されるので【Country/region】と【Phone/number】のプルダウンご自身の設定値を適切に入力して【Next】ボタンをクリックします。

f:id:ueponx:20181230123419p:plain

確認用コードの送信ダイアログになります。この時点で携帯電話のSMS経由で確認用コードが送信されるので、そこで表示された数字列を【Verification code】の入力ボックスに 入力して【Verify】ボタンをクリックします。

f:id:ueponx:20181230123432p:plain

確認が成功すると以下のような通知が表示されます。

f:id:ueponx:20181230123355p:plain

携帯での本人確認が終了したら画面表示がもとに戻るので【Continue】ボタンをクリックして作業を続けます。 ここからアプリの設定になります。

f:id:ueponx:20181230124619p:plain

Who are you requesting access for?の質問には【I am requesting access for my own personal use】のラジオボタンにチェックし、 残りの項目へ入力していきます。

f:id:ueponx:20181230124945p:plain

f:id:ueponx:20181230125726p:plain

f:id:ueponx:20181230125816p:plain

設定が完了したら、画面下の方にある【Continue】ボタンをクリックします。

f:id:ueponx:20181230125947p:plain

続くページでも作成の理由などを詳細に書く必要があります。 特に自由記述の文面を各部分もありハードルは上がっています。

f:id:ueponx:20181230130619p:plain

f:id:ueponx:20181230130739p:plain

設定が終わったら【Continue】ボタンをクリックします。

f:id:ueponx:20181230130908p:plain

画面が利用許諾のページになるので、文面を確認してチェックボックスにチェックを入れます。 ※許諾の文面をスクロールさせてすべて閲覧しないとチェックボックスが押せないので注意です。

f:id:ueponx:20181230131008p:plain

チェックができると【Submit application】ボタンがアクティブになるので、クリックします。

f:id:ueponx:20181230131218p:plain

これで設定が終わったので、確認用のメールが送信されるので【Confirm your email】をクリックしてください。

f:id:ueponx:20181230131520p:plain

ここからレビューして作成が可能になるとのことです。

f:id:ueponx:20181230131836p:plain

アプリ作成で手順がかなり増えたような… これでメールが返送されてきたらアプリの作成作業に入ります。

以下のURLをクリックして https://developer.twitter.com/en/apps

アプリ一覧の画面に遷移して画面上の方にある【Create an app】ボタンをクリックします。

f:id:ueponx:20181230180343p:plain

アプリ名などを入力する詳細設定の画面に遷移するのでデータを入力していきます。

f:id:ueponx:20181230181059p:plain

f:id:ueponx:20181230181305p:plain

必要な項目に入力をしたら、画面下方にある【Create】ボタンをクリックします。

f:id:ueponx:20181230181428p:plain

クリックするとダイアログが表示されるかもしれませんが、内容に問題がなければ【Create】ボタンをクリックします。

f:id:ueponx:20181230181544p:plain

うまく処理が行われるとブラウザに通知が行われます。

f:id:ueponx:20181230181714p:plain

アプリができたら

以下のURLからアプリ一覧を選択して

https://developer.twitter.com/en/apps

該当のアプリの【Details】ボタンをクリックします。

f:id:ueponx:20181230182024p:plain

するとアプリの詳細設定画面に移るので【Keys and tokens】をクリックします。

f:id:ueponx:20181230182521p:plain

f:id:ueponx:20181230182219p:plain

すると、APIのアクセスに必要な

4つのキーを表示する画面になります。Access tokenAccess token secretの値が表示されていない場合には【Create】ボタンをクリックして生成をおこなってください。

f:id:ueponx:20181230142708p:plain

この4つの値がコーディングに必要になるのでこれをメモしておきます。

コーディング

ようやくコーディングになります。長かった…

ツイッターでキーワードサーチをしてデータを収集するプログラムを作成します。

これまではTweepyを使用していましたが、今回は新しい取り組みとしてrequests_oauthlibを使用してみます。(Tweepyでも処理はあんまり変わらないのですが新しいものを使ってみたかったのでこちらにしています。)詳細は以下のページを御覧ください。

TweepyではTwitterに特化した処理ですが、requests_oauthlibは他のOAuth認証を使った処理でも使用可能なので、これを使えるほうがいいことがありそうです。

【参考】 pypi.org

というかもう結構まとまっている記事がありましたので、そちらを参考にしてコードを組みました。

【参考】 qiita.com

先程メモしておいた - API key - API secret key - Access token - Access token secret の値に関しては*で伏せています。

下準備

パッケージのインストール

$ pip install requests_oauthlib
$ pip install pytz
$ pip install python-dateutil

サンプルのコーディング

このサンプルではハッシュタグ#nhkリツイートではないものを最大20件取得するものを作っています。

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

import json
from requests_oauthlib import OAuth1Session
from pytz import timezone
from dateutil import parser

CONSUMER_KEY = '****************'
CONSUMER_SECRET = '****************'
ACCESS_TOKEN = '****************'
ACCESS_TOKEN_SECRET = '****************'

twitter = OAuth1Session(CONSUMER_KEY,
                        CONSUMER_SECRET,
                        ACCESS_TOKEN,
                        ACCESS_TOKEN_SECRET)

url = "https://api.twitter.com/1.1/search/tweets.json"
query = '#nhk exclude:retweets'
params = {
    'q': query,
    'count': 20
}

print('----------------------------------------------------')

req = twitter.get(url, params = params)

if req.status_code == 200:
    search_timeline = json.loads(req.text)
    for tweet in search_timeline['statuses']:
        print('name::\n' + tweet['user']['name'])
        print('text::\n' + tweet['text'])
        jst_time = parser.parse(tweet['created_at']).astimezone(timezone('Asia/Tokyo'))
        print('date::')
        print(jst_time)
        print('----------------------------------------------------')
else:
    print("ERROR: %d" % req.status_code)

このコードはそんなに難しくないのですが、返されるJSONに含まれる値のcreate_at(ツイートがされた時刻)がタイムゾーンUTCで扱われているので、 ローカルタイムゾーンに合わせるためpytzdateutilのパッケージを使用しています。

終わりに

2018年の年末まで1日しかありませんが、出来上がるのでしょうか。 すくなくともデータの取得までは完成させてwordcloudへの出力が遅れるのもやむなしかなと思ってがんばります。

環境変数の設定に関するメモ

いくつか実行環境をつかっていると環境変数の設定方法をいつも間違えるのでまとめておきます。

f:id:ueponx:20181229190252p:plain

Windows系cmd環境

Windows系のCLI環境で代表的なのはcmd.exeですね。

【設定方法】

> set MYENV=value

【表示方法】

> set

以下の例では MYENVという環境変数valueという値を格納して、設定された環境変数を格納しています。

C:\>set MYENV=value

C:\>set
ALLUSERSPROFILE=C:\ProgramData
APPDATA=C:\Users\uepon\AppData\Roaming
CommonProgramFiles=C:\Program Files\Common Files
CommonProgramFiles(x86)=C:\Program Files (x86)\Common Files
CommonProgramW6432=C:\Program Files\Common Files
【中略】
ProgramData=C:\ProgramData
ProgramFiles=C:\Program Files
ProgramFiles(x86)=C:\Program Files (x86)
ProgramW6432=C:\Program Files
PROMPT=$P$G
【中略】
MYENV=value
windir=C:\WINDOWS

WindowsPowerShell環境

Windows系のもう一つのCLI環境であるPowerShellになります。

環境変数に値を設定するには、「$env:変数名」に「=」で値を設定します。設定時には"(ダブルクオーテーション)が必要になるので注意です。

【設定方法】

PS C:\> $env:MYENV = "value"

【表示方法】

PS C:\> Get-ChildItem env:

または

PS C:\> dir env:

dirはコマンドのように見えていますが、PowerShell上ではaliasで

Alias           dir -> Get-ChildItem

と設定されているので実際は同じものを実行しているだけです。

【実行してみる】

PS C:\> $env:MYENV="value"

PS C:\> dir env:

Name                           Value
----                           -----
ALLUSERSPROFILE                C:\ProgramData
APPDATA                        C:\Users\uepon\AppData\Roaming
CommonProgramFiles             C:\Program Files\Common Files
CommonProgramFiles(x86)        C:\Program Files (x86)\Common Files
CommonProgramW6432             C:\Program Files\Common Files
【中略】
MYENV                          value
【中略】
windir                         C:\WINDOWS

また、環境変数はドライブのように扱われているのでenv:という形でアクセスしています。 そのため、こんなふうに表示することもできます。

PS C:\> cd env:
PS Env:\> ls

多分使わないと思いますが…

RaspberryPi系Bash環境とOSX

シェルの環境にはよりますが、デフォルトはbashなのでそちらを。

【設定方法】

$ export MYENV=value

【表示方法】

$ env

【実行結果】

pi@raspberrypi:~$ export MYENV=value
pi@raspberrypi:~$ env
LC_ALL=ja_JP.UTF-8
【中略】
MYENV=value
【中略】
_=/usr/bin/printenv

おわりに

内容がチラシの裏レベルになってきている…でも、最近はドキュメントやワークショップの内容が処理系に偏ることも多いのでこれを使って読み替えて勉強します。

WioNodeの非同期(websocket)処理をPythonで書いてみる

いつかやろうと思っていたんだけどできなかったものの消化です。

はじめに

WioNodeを使用していると単純にcurlコマンドなどを使ってセンサーの状態の取得ができるのですが、センサーによっては常時通信を行いながら、 そのイベントのトリガーをJSON形式で受信するというタイプのものもあります。

例えば、ButtonやGestureなどがそれにあたります。

WioアプリのAPIページにも使い方の記載されているので大きく問題はないのですが、WebSocketのサンプルはJavaScriptのみなので、JSがそんなに得意ではない自分には できればPythonから使えないかなと考えてしまいます。

f:id:ueponx:20181228154259p:plain

今回のエントリーでは、そのPythonで書いてみようというものです。 PythonのWebSocketのライブラリは調べてみると2つほどあったので、今回はその両方でチャレンジしました。

前処理

WioNodeの設定に関しては、過去のエントリーなどを参照いただければと思います。

【参考】 uepon.hatenadiary.com

今回は、ButtonのGroveモジュールをWioNodeに接続してテストを行います。

f:id:ueponx:20181228163141p:plain

Buttonの設定を行ってFirmwareの書き換えをし、画面右上のハンバーガーメニューからViewAPIのページを表示します。APIページの一覧の中にJavaScriptで書かれたWebSocket経由でのイベントの取得サンプルがあります。 ソース内の*はアクセスキーになりますので伏せてあります。

f:id:ueponx:20181228203243p:plain

f:id:ueponx:20181228203501p:plain

<script>
var ws = new WebSocket('wss://us.wio.seeed.io/v1/node/event');
ws.onopen = function() {
    ws.send("*****************************");
};
ws.onmessage = function (evt) {
    alert(evt.data);
};
</script>

これと同じ処理を行うコードをPythonで書いていくことになります。

PythonのWebSocketのライブラリモジュールを調べてみると

WebSocketのライブラリモジュールをググってみると2つほどヒットします。

  • websocket-client
  • websockets

個人的には、websocket-clientをオススメしますが、非同期的なwebsocketsも今っぽい感じで悪くないかなという印象です。 ただ、websocket-clientがオススメなのはJSなどとの類似性があるのでソースの可読性が高い点が理由かなと思います。(移植がらく)

websocket-clientを使用してコードを書いてみる

インストールはpipでOKです。Python 2.7とPython 3.4以上に対応しています。

【Windows10の場合】

$ pip install websocket-client

【RaspberryPiの場合】

$ pip3 install websocket-client

インストール時には依存性がある別のパッケージがインストールされます。詳細は以下のページを御覧ください。

pypi.org

このページにあるサンプルを改良していきます。*はキーの部分なので伏せてあります。

import websocket
try:
    import thread
except ImportError:
    import _thread as thread
import time

def on_message(ws, message):
    print(message)

def on_error(ws, error):
    print(error)

def on_close(ws):
    print("### closed ###")

def on_open(ws):
    ws.send('*******************')

if __name__ == "__main__":
    websocket.enableTrace(True)
    ws = websocket.WebSocketApp('wss://us.wio.seeed.io/v1/node/event',
                              on_message = on_message,
                              on_error = on_error,
                              on_close = on_close)
    ws.on_open = on_open
    ws.run_forever()

変更点としては - WebSocketのURL - WebSocketの接続後に接続キーを送信する(Openイベントが発生したタイミング)

あたりかなと思います。

実行すると以下のように動作します。WioNodeにつけたButtonを押すとJSONデータが受信されます。 JSONデータにはイベント名(今回は"button_pressed")接続されたpin番号が受信されます。

>> WioNode WEB Socket server connected.
< {"msg": {"button_pressed": "3"}}
< {"msg": {"button_pressed": "3"}}
< {"msg": {"button_pressed": "3"}}

Windows10のAnaconda環境でも、RaspberryPiでもうまく動いているようです。

f:id:ueponx:20181228175843p:plain

f:id:ueponx:20181228182235p:plain

JavaScriptと同じようにonほげのような感じでイベントハンドラの登録ができるような感じです。かなり分かりやすい印象です。

websocketsを使用してコードを書いてみる

インストールはpipでOKです。Python 3.4以上に対応していますが、それ以下でも書き方次第で動くかも?

【Windows10の場合】

$ pip install websockets

【RaspberryPiの場合】

$ pip3 install websockets

詳細は以下のページを御覧ください。

pypi.org

このページのサンプルに改良していきます。*はキーの部分なので伏せてあります。

#!/usr/bin/env python
import asyncio
import websockets

async def loop():
    async with websockets.connect('wss://us.wio.seeed.io/v1/node/event') as websocket:
        await websocket.send('*******************')
        print(">> WioNode WEB Socket server connected.")
        while True:
            receiveData = await websocket.recv()
            print("{}".format(receiveData))

asyncio.get_event_loop().run_until_complete(loop())

python3で使用可能となったasync/awaitasyncioを使用しています。 asyncioの持つイベントループにWebSocketの接続をいれてデータの送受信をする形になります。 非同期処理なんですが、同期的っぽくかけます。 他のライブラリなどとは記載の仕方が違う点は特殊ですが、分かりやすいといえば分かりやすいかもって感じです。 ただ、Pythonのバージョンが限定されるので、用途が限られるかなという印象はあります。

f:id:ueponx:20181228184520p:plain

f:id:ueponx:20181228185224p:plain

こちらもWindows10のAnaconda環境でもRaspberryPiでも動作していましたが、RaspberryPiではCtrl+CのInterruptでエラーメッセージが大量に表示されるので微妙なのかも。 RaspberryPiのPython3のバージョンは3.5.3でした。

おわりに

ようやくやれてなかったことをまとめられました。JavaScriptもう少しかけるように慣れればいいんでしょうけど個人的にはPythonも捨てがたいです。