なんで作ったの?
毎朝、バスでいくか、それとも電車にするか、バスの待ち時間と混雑具合で決めたい、という娘のために作りました。そんなのケータイアプリがあるじゃん、と思われる方もおられるでしょうが、朝の忙しい時間に、常に表示されているのが便利なのです。また、バス情報がいらない時間には、ほかの情報も表示して遊びたいと思ったのです。
なにをするもの?
指定したバス停と、その前後二つのバス停(すぐ隣でなくても指定できる)のバス到着時間を表示します。色でバスの混み具合を表し、同じバスを表す数字は、線でつなぐようにしました。
色 | コード | 意味 | 意味 |
緑 | SEA | SEats Available | 座れるよ |
黄色 | SDA | StanDing Available | 立ってなら乗れる |
赤 | LSD | Limited StanDing | 立つ場所もないかも |
どういう仕組み?
バスの到着時刻のデータをインターネットから取ってきます。シンガポールの場合、Land Transport DataMallというところからデータを取得することができます。(簡単なユーザー登録が必要。無料。どんどんデータを活用して便利なアプリなどを作ってね、というノリで提供されているので、やる気もでます)データは、httpリクエスト(GET)のレスポンスとしてjson形式で得られます。あとはその中から必要な部分を抜き出して、少々加工して、LED matrixに表示させるという仕組みです。
Raspberry pi zero w(二号機)がデータを取得して加工し、シリアル通信に表示に関するデータを流します。Arduino(Adafruit METRO M0 Express)がシリアル通信の内容を受けて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 電源
- あとは、LEDと330オームぐらいの抵抗、ボタンスイッチ(tactile switch button)、ジャンパーケーブルとブレッドボードです。
- できあがって面白がっている自分を想像する力と、途中でやめられないあきらめの悪さ(粘り強さともいいます)が必須です。
コンピューター環境
- 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)をとるようにします。
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じゃなきゃだめなのかをよく確認して採用しましょう。
# 別ファイルとして書いている。
# 主な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でやろう、と思い立って作ったのが今回のバージョンです。ノイズも消え、ボタンもつけて、いろいろ表示できる(まだ作ってませんが)ようにもできたし、気に入ってます。