うちIoT

バス到着情報表示板

なんで作ったの?

毎朝、バスでいくか、それとも電車にするか、バスの待ち時間と混雑具合で決めたい、という娘のために作りました。そんなのケータイアプリがあるじゃん、と思われる方もおられるでしょうが、朝の忙しい時間に、常に表示されているのが便利なのです。また、バス情報がいらない時間には、ほかの情報も表示して遊びたいと思ったのです。

なにをするもの?

指定したバス停と、その前後二つのバス停(すぐ隣でなくても指定できる)のバス到着時間を表示します。色でバスの混み具合を表し、同じバスを表す数字は、線でつなぐようにしました。

コード意味意味
SEASEats Available座れるよ
黄色SDAStanDing Available立ってなら乗れる
LSDLimited StanDing立つ場所もないかも
Bus LED表示例1
バス待ち時間表示例1:3つのバス停でのステータスを表示する
バス待ち時間表示例2:色による混雑度の表現

どういう仕組み?

バスの到着時刻のデータをインターネットから取ってきます。シンガポールの場合、Land Transport DataMallというところからデータを取得することができます。(簡単なユーザー登録が必要。無料。どんどんデータを活用して便利なアプリなどを作ってね、というノリで提供されているので、やる気もでます)データは、httpリクエスト(GET)のレスポンスとしてjson形式で得られます。あとはその中から必要な部分を抜き出して、少々加工して、LED matrixに表示させるという仕組みです。

Raspberry pi zero w(二号機)がデータを取得して加工し、シリアル通信に表示に関するデータを流します。Arduino(Adafruit METRO M0 Express)がシリアル通信の内容を受けてLED matrixに表示します。ってわけです。

raspberry pi -> http request -> arduino -> LED matrix

使っている技(わざ)

興味のある方は、ここに出てくる文や単語をGoogle検索してさらに調べてみてください。
  • Raspberry piで
    • LEDをon-offする
    • ボタンスイッチ(tactile switch button)が押されたことを検出する
  • Raspberry piからArduinoにシリアル通信で情報を送る
  • Python3でhttp request (GET)をしてjsonレスポンスを受け取る
  • Python3, Arduinoでのjsonデータの取り扱い

作り方

材料

  • Raspberry pi zero w
    じゃなくてもいいですけど、これが安い。
  • Adafruit METRO M0 Express (Arduino)
    64×32 matrix(32×32より大きいもの)をコントロールするにはRAMが大きめじゃないとだめですよ、とあって、これにした。(Adafruitのページ
  • Adafruit RGB Matrix Shield for Arduino
    ArduinoとLED matrixをつなぐためのシールド。なくてもいいのですが、やたらケーブルが必要で、まとまりが悪い(最初のテストでそうしたのでわかります。下の写真参照)
  • Adafruit 64×32 RGB LED Matrix – 4mm pitch
    表示板がないとはじまりません。
  • 9V, 1A 電源
  • あとは、LED330オームぐらいの抵抗、ボタンスイッチ(tactile switch button)ジャンパーケーブルブレッドボードです。
  • できあがって面白がっている自分を想像する力と、途中でやめられないあきらめの悪さ(粘り強さともいいます)が必須です。
cable connection to LED matrix
ジャンパーワイヤーだけでLEDパネルを接続するとこうなって引き回しがしにくい。

コンピューター環境

  • Raspberry pi zero wはいわゆるheadless、モニタもマウスもキーボードもなし、という状態で使うので、ssh接続できるように準備しておく必要があります。
    myToDo: Raspberry piのセットアップメモを記事にする
  • python codeを書いたりRaspberry piに接続してコードファイルを転送するためのPC
  • python3
  • Arduino IDE

接続

  • Raspberry piの電源は5V USB電源から。
  • METRO M0には9V電源をつなぎました。のせているシールドに5Vの出力があり、そこからLED matrixへの電源を取っています
  • Raspberry piのUSB (mini)と、METRO M0のUSB (mini)とをUSBケーブルでつなぎます。ここをシリアル通信が流れます。
  • LED、 ボタンスイッチ、 抵抗とRaspberry pi GPIOへの接続は以下の通りです。
  • ボタン押しの検出は、GPIOをpulled-upにしておき、High to Low (falling)をとるようにします。
Raspberry pi -> Arduino serial communication

code

難しかったところや、あとで見たときに役に立ちそうなところだけについて書きます。

以下のコードはすべて断片で、実行するためのものではありません。何をしているか思い出すためのメモ書きです。

Raspberry pi側

まずはGPIOを使うための部分。selfのついた変数があるのは、CallBackクラスの変数として使っているから。

import RPi.GPIO as GPIO

### 初期設定たち ###
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)  # GPIO pin numberで指定するため
## ボタン読み取りたち
# pull upしておいて、ボタンでground touchさせる。
GPIO.setup(self.gray_btn_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
# fallingを検出する
GPIO.add_event_detect(self.gray_btn_pin, GPIO.FALLING, bouncetime=1000)
# callbackを指定する
GPIO.add_event_callback(self.gray_btn_pin, self.setProgramStatus)
# setProgramStatusの中で、どのプログラムがactiveなのかを設定している

## LED点灯用
GPIO.setup(self.gray_LED_pin, GPIO.OUT, initial=GPIO.LOW)

次にループ。ここが難しかった。手こずった。機械系のひとなら特になんでもない話かもしれないのだが、やりたいことをなんと表現したらいいのやらよくわからず、検索しても、 ちがうんだよ、そうじゃなくて、というページばかりにあたってしまい苦労した。最終的にlambdaで変数をcallableなものとして内側のループに持ち込むことで解決できた。

外側無限ループ:ボタンが押されるのを監視している。押されたボタンに応じてプログラムを呼び出す。呼び出されたプログラムも無限ループをもつ。(内側ループ)

内側無限ループ:今のところbus表示プログラム(ねずみ色ボタン)のみ。1分おきにbus statusをとりにいく。ほかの色のボタンが押されたとき、このループを止めなければならない。また、このループを回しつつ、外側ループにも制御が戻らなければならない。どうやる?

# 外側ループ。ボタンが押されることを監視している。
# time.sleep(1)で回している。
# 基本骨格はいかのとおり
while True:
    if self.gray_active == 1:  # bus表示用のボタン(薄いねずみ色)
        # が押されたことをキャッチ。
        # さて、bus表示用プログラムを起動したい。起動はできる。
        # 呼べばいい。しかし、
        # このプログラムも無限ループを持つ。
        # - どうやって止めに入るか?
        # - このループが回っている間、いかにボタン監視をつづけるか。
        #   普通に呼ぶと、向こうのループが回っている間、制御が帰ってこない。
        #   一人管理人状態。
        # これらをうまくやるにはどうしたらいいか、それが問題だった。

        # 以下がいまのところの解決策。もっときれいな方法があるかもしれない。

        # bus表示プログラム用のthreadを作る。
        stop_th_gray = False  # stop flagをFalseにする
        th_gray = threading.Thread(target=self.programGray,
                                args=(lambda: stop_th_gray,))
        # ここで、programGray()が内側プログラムなのだが、
        # argとしてstop_th_grayを渡す。
        # が、だがしかし、そのまま渡してはいけない。
        # lambdaの返り値として渡す。
        # これで内側ループが回っている間にほかの色のボタンが押されたとき、
        # stop_th_grayをTrueにすると、それがprogramGray()のループにわたる。
        # なるほど、なのだが、なかなかこの答えにたどり着かなかった。
        # thread開始
        th_gray.start()

    # == truncated: blue, green btn captures ===

    elif self.red_active == 1:
        # 赤ボタンプログラム。
        # threadは立てない。立てる必要がない。
        # programRed()が終了してここに制御がもどったら
        # breakでぬける。
        self.programRed()  # 終了メッセージをだして、赤LED点滅させる
        break  # この無限ループから抜ける
    time.sleep(1)
sys.exit()

Bus表示プログラム。あまり難しいことはしていない。bytesの取り扱いがpython3とpython(version2)とで違う。webで検索して試すと思いがけないエラーがでて混乱します。python3で動くコードなのか、python2じゃなきゃだめなのかをよく確認して採用しましょう。

webでpythonコードを検索するとき、python3なのかpython2なのかわからないことが多い。そもそもどちらでもうまく動くものも多い。コード自体には違いがなくても、python3で走らせた時と、python2で走らせた時とでエラーが出たり、問題なくうまくいったりする。しかも、pythonとだけ打った時、使用している環境によりそれはpython3を意味していたり、python2のことだったりする。確認するには

python -V

と打ってみます。バージョンが表示されますので、自分の打つpythonがどういう意味なのか(2なのか3なのか)がわかります。

# 別ファイルとして書いている。
# 主なimportたちは
import urllib.request
import json
import serial

# http リクエストを送る部分:getBusInfo()
# url構築は以下のように
# Auth params
headers = {'AccountKey': 'xxxxxxxxxxxxxxxxxxxxxx',  # 自分のものを使う
           'accept': 'application/json'}
# API  parameters
url = 'http://datamall2.mytransport.sg'
url += '/ltaodataservice/BusArrivalv2?'
url += 'BusStopCode=' + bus_stop
url += '&ServiceNo=' + bus_service

# これで一発。
req = urllib.request.Request(url, headers=headers, method='GET')

# jsonの内容をdictにとる。with構文で
with urllib.request.urlopen(req) as res:
    body1 = res.read()
    # print(type(body1))  # bytesで返ってきますので
    decoded_body1 = body1.decode('utf8')
    # print(type(decoded_body1))  # strにして
    res_dict = json.loads(decoded_body1)  # python dictに格納します
# python dictに入ってしまえばこっちのものです。

# シリアルに送り出す方法:sendToSerial()
# Raspberry piでのUSBがどういう名前になっているのかを確認しておく必要があります。
# ここではttyACM0だったので、それを使っています。
ser = serial.Serial('/dev/ttyACM0', 9600, timeout=None)  # this is on raspi
time.sleep(2)
json_string = json.dumps(info_dict) + '\n'
ser.write(json_string.encode())
ser.close()

Arduino側

// Arduino IDEのFile > Examples > RGB matrix Panel > testshapes_32x64
// を参考にしました。
// 大事そうな行のみコピーしておく
#include <ArduinoJson.h>
#include <RGBmatrixPanel.h>

#define CLK  8   // USE THIS ON ADAFRUIT METRO M0, etc.
#define OE   9
#define LAT 10
#define A   A0
#define B   A1
#define C   A2
#define D   A3
RGBmatrixPanel matrix(A, B, C, D, CLK, LAT, OE, false, 64);

void setup() {
  // Serial
  Serial.begin(9600);
  // LED matrix
  matrix.begin();
  // draw a pixel in solid white
  matrix.drawPixel(0, 0, matrix.Color333(7, 7, 7));
  delay(500);
  // fill the screen with 'black' 全部消す
  matrix.fillScreen(matrix.Color333(0, 0, 0));
  // size, wrap
  matrix.setTextSize(1);     // size 1 == 8 pixels high
  matrix.setTextWrap(false); // Don't wrap at end of line - will do ourselves
}

// カーソル位置セット
matrix.setCursor(3, 12);
// テキスト色を指定
// Color333(r, g, b)で、0-7の値で指定する。
matrix.setTextColor(matrix.Color333(1, 1, 1));
// テキストを書く
matrix.println("something...");
// 線を引く
matrix.drawLine(19, 27, 23, 11, matrix.Color333(1, 1, 1));
// 1LEDだけ
matrix.drawPixel(19, 27, matrix.Color333(1, 0, 0));

使ってみて

なんでもない表示といってしまえばそれまでなのですが、よくよく注意してみていると、次のバス停までの時間が伸びたり縮んだり、道路の込み具合が反映されるようですし、どの時間帯が最も混みやすいのかは、おおよそ把握できました。

電源が少し不安定で、同じコンセントから特にモーター系(コーヒーミル)などを使うと、突然プログラムが停止したりします。(コンセントの取り方を少し変えたらよくなりましたが、観察中)

このあとの予定

まだ、青と緑のボタンからの関数を作っていません。青ボタンは、はやぶさ2の地球までの距離(このページからweb scrapingできた)を表示させるために使おうと思ったのですが、Raspberry pi zero wではjava scriptの実行が遅いのかなんなのか、web scrapingがうまくいかず (Raspberry pi 3Bではできるんです) 、どうしようかと思案中です。別のRaspberry pi (3B)に取らせてpi zero wに送り込むか、あるいはpi zeroを3Bに置き換えるか。

家族からは

  • ニュースの見出しを流す(新幹線みたいに)
  • 覚えられない単語を繰り返し表示する(入力用webアプリかケータイアプリもつくってほしい)
  • 雨、雷情報(そういうのがSMSでくるんです)をアラート音とともに表示
  • パパの帰宅時、パパの家までの距離を表示(味噌汁温め開始タイミングをみはからう、ホント?)

などの意見が出ていますが(自分のも入ってます)、まだ決心がついていません。

うらばなし

実はこのバージョンの前に、Raspberry pi 3Bの上にLED matrix用のシールドをのせて試していました。(バス停表示はひとつのみ)LED matrixはpythonで動かしていたのですが、どうもノイズっぽい表示が消えず、またcrontabで起動直後に走るべきpythonスクリプトがうまくいったりエラーを出したり、再現性のあるエラー原因がつかめず、結局しばらく休業状態がつづいていました。やっぱり、こういう表示なんかはArduinoにまかせたほうが得意なんじゃない?データの取得と加工はRaspberry piでやろう、と思い立って作ったのが今回のバージョンです。ノイズも消え、ボタンもつけて、いろいろ表示できる(まだ作ってませんが)ようにもできたし、気に入ってます。

ABOUT ME
misson
ものづくりが趣味。頭の中でモヤモヤと渦巻くガスを形として沈殿させるのが趣味。座右の銘は:こたえは常に自分の中にある


follow us in feedly

COMMENT

メールアドレスが公開されることはありません。