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環境にもインストールしましたがこっちで使ったほうがいいのかもしれません。(コピペする前にこっちで整形をするという感じ)

Node.jsでRSSのデータを取得する【Windows10、RaspberryPi、DragonBoard】

Node.jsでRSSのデータを取得する【Windows10、RaspberryPi、DragonBoard】

以前からちょっと欲しいなと思っていたのですが、現在のテレビ番組を一覧的に表示できなかなと。テレビをつけてEPGを見ればいいじゃないとというのはもちろんわかっているんですが、もう自分はAmazon Primeなしには生きられない特撮バカですので入力切り替えとかそんなことはもうやりたくありません。(きっぱり)

どうせ、PCつけてコードとか書いているんだしPC上で見れればそれでいいかなと思っていたんです。

テレビを見なければいいという話もあるのかもしれませんが…そういうわけにもいかないので…。

テレビの放送状況を確認するにはテレビ王国というサイトがあります。

f:id:ueponx:20170903163256p:plain

SONYさんが運営しているサービスでHDDレコーダやiEPGなんかでも使われているので比較的確度の高い情報でしょう。

tv.so-net.ne.jp

このサイトは全国のエリアをカバーしているので、自分のような地方の人間にも優しいです。また、現在の放送番組のRSSも公開されているので最高です!

RSSはこんな感じ。

Gガイド.テレビ王国 - 放送中のRSS配信 - 地上波(東京)

あとはこのRSSを読み込んでしまえば…ということになります。

今回は他のシステムとの連携もしたかったのNode.jsコーディングしました。

Node.jsでRSSを読み込む

ネットで検索したところWebのスクレイピングではcheerio-httpcliを使用すると便利そうでした。

qiita.com

cheerio-httpcli

特徴としては以下の様です。

  1. 取得先WEBページの文字コードを自動で判定してHTMLをUTF-8に変換
  2. UTF-8に変換したHTMLをjQueryのように操作可能
  3. フォームの送信やリンクのクリックをエミュレート
  4. Node.jsお馴染みのコールバック形式と最近の流行であるプロミス形式どちらにも対応
  5. 同期リクエスト対応
  6. $(‘img’)要素画像のダウンロード(LazyLoad対応)
  7. $(‘a,img,script,link’)要素のURLを絶対パスで取得可能
  8. ブラウザ指定による簡単User-Agent切り替え機能
  9. 現在のクッキーの内容を簡単に取得(読み取り専用)
  10. XMLドキュメントを自動判別してパース処理を切り替え

半分ぐらいは今回とはあまり関係はないのですが、今後使えそうな機能が多いのでこちらを採用しました。

cheerio-httpcliのインストール

今回の実行環境(Windows10)は以下の通りです。

PS C:\Users\xxx\Documents\node> node -v
v6.11.2
PS C:\Users\xxx\Documents\node> npm -v
3.10.10

インストール作業はnpmを使えば簡単です。(LinuxでもWindowsでも違いはありません。プロンプトが違うぐらいです。)

PS C:\Users\xxx\Documents\node> npm install cheerio-httpcli

問題なくインストールできました。

コーディング

先程のRSSのURLを.fetch()の引数に与えてあとは、コールバック形式かプロミス形式で処理を行えば問題ありません。プロミスってなんだろうと思ったんですが、「非同期の動作を扱う際にコールバックからメソッドチェーンにすることができコールバック地獄を回避することができる」ってことのようです。非同期処理では結構苦労するので、こういう改良が必要なんですね。

今回は規模的にはどちらを選択しても良かったのですが、せっかくなので

【sample.js】

var client = require('cheerio-httpcli');
// かんとーちほーの現在の放送番組
var RSS = "https://tv.so-net.ne.jp/rss/schedulesByCurrentTime.action?group=10&stationAreaId=23";

client.fetch(RSS, {})
.then(function (result){
    if (result.error) { console.log("error"); return; }
    result.$("item").each(function (idx) {
        programName = result.$(this).find("title").text();
        programFrame = result.$(this).find("description").text();
        console.log(programName);        
        console.log(programFrame);
        console.log("---");
    });
    console.log("\n" + "放送中番組のタイトルを取得しました。");
});

これを実行してみます。

PS C:\Users\xxx\Documents\node> node .\sample.js
Hulu傑作シアター[]
9/3 1:302:30 [日テレ(Ch.4)]
---
名棋士が読み解く 藤井聡太 “強さ”の秘密[][]
9/3 1:452:30 [NHK総合・東京(Ch.1)]
---
ワールドプロレスリング
9/3 2:002:30 [テレビ朝日(Ch.5)]
---
お買い物情報
9/3 2:002:30 [TOKYO MX1(Ch.9)]
---
HERO'S[デ]
9/3 2:05~2:45 [フジテレビ(Ch.8)]
---
ランク王国 秋のサキドリSP!
9/3 2:08~3:08 [TBS(Ch.6)]
---
深夜に発見!新shock感~一度おためしください~
9/3 2:10~2:35 [テレビ東京(Ch.7)]
---
放送休止
9/3 2:12~5:00 [NHKEテレ1・東京(Ch.2)]
---

放送中番組のタイトルを取得しました。

うまく行ったようです。これで取得できるのはタイトル、放送枠時間、放送局だけなので内容がほしい場合には RSS上のlinkタグにあるURLを更にスクレイピングする必要があるので少し面倒ですね。

終わりに

あとはうまい感じで表示できればOKですね。

少し欲を出して、番組情報も出せるようにしました。1回目のRSSスクレイピングで詳細情報のリンク先を取得し、 2回目のスクレイピングで取得するという感じなのですが、JavaScriptは非同期実行なので、データにタイミングがコードの想定とはズレてしまうようでした。 Objectに格納しようとしたら格納時にはデータがなく、どこでデータ来るのかを判別するのがちょっと難しかったでの悩んでいました。

最初に書いた

  1. 同期リクエスト対応

こちらのことを思い出して、ググってみました。 以下のリンクに同期的実行に関して情報が乗っていたのでそれを試して見ました。

【参考】 qiita.com

【sample2.js】

var client = require('cheerio-httpcli');
var RSS = "https://tv.so-net.ne.jp/rss/schedulesByCurrentTime.action?group=10&stationAreaId=23";
var programsList = [];

var result1 = client.fetchSync(RSS);
result1.$("item").each(function (idx) {
    var program = new Object();
    var programName = result1.$(this).find("title").text();
    var programFrame = result1.$(this).find("description").text();
    var programLink = result1.$(this).find("link").text();
    var result2 = client.fetchSync(programLink);
    programInfo = result2.$(".subUtileSetting").eq(0).find("p").text();

    program["Name"] = programName;
    program["Frame"] = programFrame;
    program["Link"] = programLink;
    program["Info"] = programInfo; 
    programsList.push(program);
});

programsList.forEach(function(element) {
    console.log("\n--->\n");
    console.log("番組名  :" + element["Name"]);        
    console.log("番組時間 :" + element["Frame"]);        
    console.log("番組リンク:" + element["Link"]);            
    console.log("番組詳細 :" + element["Info"]);
}); 

これを実行させると…

PS C:\Users\xxx\Documents\> node .\sample2.js

--->

番組名  :サンバリュ 「外国人観光客に思い切って注意してみました ニッポンのマナー」[]
番組時間 :9/3 13:1514:15 [日テレ(Ch.4)]
番組リンク:http://tv.so-net.ne.jp/schedule/101040201709031315.action?from=rss
番組詳細 :満開の桜の枝をポッキリ折る!市場で生魚を手でベタベタ触る!世界遺産で環境破壊!目に余るマナー違反の外国人観光客を、勇気を出して注意!すると逆ギレ&驚きの言い訳が

--->

番組名  :45th フジサンケイクラシック 最終日[][]
番組時間 :9/3 13:3014:55 [フジテレビ(Ch.8)]
番組リンク:http://tv.so-net.ne.jp/schedule/101056201709031330.action?from=rss
番組詳細 :世界基準コース富士桜に挑む!
初優勝思い出の舞台で復活V目指す岩田寛!
22歳の若武者が初の頂点へ稲森佑貴!

--->

番組名  :舞台芸術の魅力 第8回「世界の現代演劇-演劇における「20世紀」の意味-」
番組時間 :9/3 13:4514:30 [放送大学1(Ch.12)]
番組リンク:http://tv.so-net.ne.jp/schedule/101088201709031345.action?from=rss
番組詳細 :京都造形芸術大学教授 森山 直人

--->

番組名  :海外出張オトモシマス!「フランス 激レア&絶品!チョコ・ハンター」[][][]
番組時間 :9/3 13:5014:20 [NHK総合・東京(Ch.1)]
番組リンク:http://tv.so-net.ne.jp/schedule/101024201709031350.action?from=rss
番組詳細 :海外出張のプロにオトモして、世界の流行の最前線に潜入する番組。今回の出張は、フランス!激レア絶品チョコの大捜索!パリからブルターニュ地方へ。日本初上陸のチョコ!

--->

番組名  :科捜研の女16 #11[][][]
番組時間 :9/3 13:5514:50 [テレビ朝日(Ch.5)]
番組リンク:http://tv.so-net.ne.jp/schedule/101064201709031355.action?from=rss
番組詳細 :「グルメ格付け殺人!死を呼ぶ裏メニューで人間発火!?」沢口靖子()▽身元不明の外国人男性が殺害された。マリコは 被害者が事件の夜に食事したフレンチ店に鍵があると考え…

--->

番組名  :日本の話芸 林家正雀 落語「怪談牡丹灯籠から お札はがし」[][]
番組時間 :9/3 14:0014:30 [NHKEテレ1・東京(Ch.2)]
番組リンク:http://tv.so-net.ne.jp/schedule/101032201709031400.action?from=rss
番組詳細 :第698回東京落語会から林家正雀さんの「怪談牡丹灯籠から お札はがし」をお送りします(平成29818日(金) 東京・虎ノ門 ニッショーホールで収録)

--->

番組名  :今夜!最大の悲劇がー日曜劇場「ごめん、愛してる」スペシャルダイジェスト[]
番組時間 :9/3 14:0014:50 [TBS(Ch.6)]
番組リンク:http://tv.so-net.ne.jp/schedule/101048201709031400.action?from=rss
番組詳細 :クライマックスに向け大きく動き出す長瀬智也主演の胸に迫る切ない物語「ごめん、愛してる」。そこで後編のストーリー&今夜の見所をご紹介!

--->

番組名  :日曜ミステリー「嫌われ監察官 音無一六3」[] 小日向文世主演[][]
番組時間 :9/3 14:0016:00 [テレビ東京(Ch.7)]
番組リンク:http://tv.so-net.ne.jp/schedule/101072201709031400.action?from=rss
番組詳細 :連続殺人事件の現場に残されたのは、恋愛を詠った百人一首の札。怨恨か?それとも…?監察官・音無一六が“深読み力”で真相を暴く! ≪解説放送あり≫

--->

番組名  :MXショッピング
番組時間 :9/3 14:0014:30 [TOKYO MX1(Ch.9)]
番組リンク:http://tv.so-net.ne.jp/schedule/123608201709031400.action?from=rss
番組詳細 :H2OスチームFX 【スチームクリーナー】

個人的には同期的にやったほうがスッキリしますねw。コードも短いし…。

同じソースコードでRaspberryPiとDragonBoard410Cでも実行してみます。

$ node sample2.js

--->

番組名  :決定!こども囲碁名人~第38回少年少女囲碁大会~
番組時間 :9/3 14:3016:30 [NHKEテレ1・東京(Ch.2)]
番組リンク:http://tv.so-net.ne.jp/schedule/101032201709031430.action?from=rss
番組詳細 :囲碁を志す全国の小・中学生にとって日本一の栄冠をかけたあこがれの舞台「少年少女囲碁大会・全国大会」今年も、未来の井山七冠を夢見るこどもたちが熱い戦いを繰り広げた

--->

番組名  :路線バスで寄り道の旅[]
番組時間 :9/3 15:4016:30 [テレビ朝日(Ch.5)]
番組リンク:http://tv.so-net.ne.jp/schedule/101064201709031540.action?from=rss
番組詳細 :日頃秒刻みスケジュールに疲れ気味の徳さん御一行。のんび~り旅を味わうために選んだのは“路線バス"。今回、目指すのは桜木町~馬車道~山下公園で横浜の裏 通りを散策旅

--->

番組名  :大捜索ドキュメント! 屋久島“伝説の超巨大杉”[字][再]
番組時間 :9/3 16:00~16:50 [NHK総合・東京(Ch.1)]
番組リンク:http://tv.so-net.ne.jp/schedule/101024201709031600.action?from=rss
番組詳細 :世界遺産・屋久島で語られる『超巨大杉伝説』。縄文杉を超える杉の大捜索に挑んだ。最新航空調査で森をスキャンし、秘境を大冒険。ついに捜索隊は驚きの巨大杉と遭遇する!

--->

番組名  :グラチャンバレー1番は誰?開幕直前SP[字]
番組時間 :9/3 16:00~16:55 [日テレ(Ch.4)]
番組リンク:http://tv.so-net.ne.jp/schedule/101040201709031600.action?from=rss
番組詳細 :9月5日(火)開幕、グラチャンバレー!徳井義実&佐藤栞里がバレー全日本女子を直撃!気になる噂の真相を選手が激白!

--->

番組名  :なるほど!今うなぎが食べたくなるテレビ【旬は秋!画面から香ばしい香りが?!】[解][字]
番組時間 :9/3 16:00~17:00 [TBS(Ch.6)]
番組リンク:http://tv.so-net.ne.jp/schedule/101048201709031600.action?from=rss
番組詳細 :芸能界のグルメ王・渡部建が名店の秘密に迫る!★芸能界屈指の料理人・梅沢富美男が安くて美味しいうなぎレシピに挑戦!★幻の「青うなぎ」を求めて漁に出るのはみやぞん!

--->

番組名  :YOUは何しに日本へ?予習復習▽日本茶&歌声に熱狂YOUマジ惚れ来日大騒ぎ[字]
番組時間 :9/3 16:00~17:05 [テレビ東京(Ch.7)]
番組リンク:http://tv.so-net.ne.jp/schedule/101072201709031600.action?from=rss
番組詳細 :本気で日本茶学びたい!…20歳青年YOUが本場静岡でガチ住み込み修行…超高級茶葉に昇天!?■遠い異国で人生変えた…坂本九匹敵神歌にYOUが全力マジ惚れ旅■ベビ ーメタル

--->

番組名  :いただきハイジャンプ 土曜お昼にお引っ越しSP[字]
番組時間 :9/3 16:00~17:25 [フジテレビ(Ch.8)]
番組リンク:http://tv.so-net.ne.jp/schedule/101056201709031600.action?from=rss
番組詳細 :(予定変更の場合あり)
様々な一大事にJUMPが立ち向かう▽番組引っ越し記念で人気企画を厳選!6歳少年が苦手克服に挑戦…涙のワケとは?

--->

番組名  :お買い物情報
番組時間 :9/3 16:00~16:30 [TOKYO MX1(Ch.9)]
番組リンク:http://tv.so-net.ne.jp/schedule/123608201709031600.action?from=rss
番組詳細 :トゥルースリーパープレミアケア 【低反発マットレス】

--->

番組名  :障害を知り共生社会を生きる 第8回「難病を知る」
番組時間 :9/3 16:00~16:45 [放送大学1(Ch.12)]
番組リンク:http://tv.so-net.ne.jp/schedule/101088201709031600.action?from=rss
番組詳細 :愛知県立大学教授 吉川 雅博 フリーアナウンサー 岡 知沙登

問題なく動作します!まあNode.jsなんで違いは無いはずですけどね。

うまくいったようです! これで快適なAmazonPrimeの特撮ライフがおくれそうです。

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