概要
Raspberry Pi Zero WHと64x32のLEDマトリクスで声で操作できる電光掲示板を作成しました。
声だけで気温&ニュース掲示板の起動
— すいばり@'22年5戦4勝⚾️ (@Suibari_cha) December 31, 2022
ただ起動まで1分近くかかるw
WAITINGを出してるのはそれが理由 pic.twitter.com/dI30bpcZtW
実現にあたって必要な情報は大方検索すれば出てくるので、本記事はキュレーション的なまとめ記事になると思っています。
ただしrpi-rgb-led-matrix(本記事で使用するLEDマトリクスパネル(以下、パネル)点灯用ライブラリ)の使い方関連の情報は自分の調べた限りでは無かったので、本記事の情報が参考になると思います。
目次
内容
必要なもの
以下が必要です。
商品 | 価格 | URL |
---|---|---|
Raspberry Pi Zero WH | 元々所持 | スイッチサイエンス |
Raspberry Pi Zero用ACアダプタ 5V1.2A | 元々所持 | 楽天 |
64x32 HUB75規格対応LEDマトリクスパネル | 2,970 | Amazon |
メス-メス ジャンパー線最低14本 | 790 | 楽天 |
パネル用ACアダプタ 5V4A | 1,400 | 楽天 |
DCジャック | 送料込270 | 楽天 |
計 | 5,430 |
パネルについて、2023/1/1現在、Amazonで最も安いRGB/HUB75規格対応/64x32のパネルなのでおすすめです。
電源について、ライブラリのREADMEによると64x32のパネル用電源は5V7Aが推奨のようです。
ただ、今回使用した5V4A電源でも輝度が落ちたり表示が消えるといったことはありませんでした。
判断は自己責任でお願いしたいですが、この電力でも問題ないと思います。
ラズパイのセッティング
たくさんの記事があるので以下などを参考にするといいと思います。
自分は記事同様ヘッドレスセットアップしました。
https://memo.appri.me/iot/rpi0-headless-setup
配線
画像のような配線となっています。
パネル配線写真
— すいばり@'22年5戦4勝⚾️ (@Suibari_cha) January 1, 2023
qiita投稿用 pic.twitter.com/tZ6XnpPjaD
以下ライブラリの配線を参考にすれば問題なくできると思います。
😄のマークのピン(E除く)を配線します。今回はrow32のパネルなのでDピンを使用し、Eピンは使用しません。
https://github.com/hzeller/rpi-rgb-led-matrix/blob/master/wiring.md/#connection
コーディング
コードは以下です。
#!/usr/bin/env python
# Display a runtext and a static text with double-buffering.
# sys.pathにsitepackagesがあるのにbs4を読み込んでくれない…強制的に読み込ませる
import sys
sys.path.append("/home/pi/.local/lib/python3.7/site-packages")
from rgbmatrix import RGBMatrix, RGBMatrixOptions
from graphics import graphics
import time
import requests
from bs4 import BeautifulSoup
import re
import threading
import datetime
from PIL import Image
atcl_arr = []
temp = ""
def getNews():
global atcl_arr
atcl_arr_new = []
# 接続
print(str(datetime.datetime.now())+": getting NEWS...")
#URL = "https://www.japantimes.co.jp/" # JT
URL = "https://www.nikkei.com/news/category/" # 日経
rest = requests.get(URL)
soup = BeautifulSoup(rest.text, "html.parser")
# HTMLパース
#atcl_list = soup.find_all(attrs={"class" : "article-title"}) #JT
atcl_list = soup.select('#CONTENTS_MAIN')[0].find_all(class_=re.compile("_titleL")) # 日経
# 格納
for atcl in atcl_list:
print(atcl.string)
atcl_arr_new.append(atcl.string)
atcl_arr = atcl_arr_new
return atcl_arr
def getTemperature():
global temp
print(str(datetime.datetime.now())+": getting current temperature...")
URL = "https://tenki.jp/amedas/3/17/46091.html" # 海老名のアメダス
rest = requests.get(URL)
soup = BeautifulSoup(rest.text, "html.parser")
temp_tag = soup.select('.amedas-current-list')[0].select('li')[0]
temp_tag.select('span')[0].decompose()
temp = temp_tag.text
#print(temp.text)
return temp
class createLED():
# パネル設定用関数
def __init__(self):
self.font = [] # フォント
self.textColor = [] # テキストカラー
self.flagPreparedToDisplay = False # メイン関数を表示する準備ができたかどうかのフラグ
# LEDマトリクス設定
options = RGBMatrixOptions()
options.rows = 32
options.cols = 64
options.gpio_slowdown = 0
self.matrix = RGBMatrix(options = options)
# キャンバス作成
self.offscreen_canvas = self.matrix.CreateFrameCanvas()
# 最低限のフォントだけ読み込み、WAITINGを表示させておく
self.f = threading.Thread(target=self.displayLEDTemp)
self.f.setDaemon(True)
self.f.start()
# 文字設定
print("setting LED matrix options...")
self.font.append(graphics.Font())
self.font[0].LoadFont("/home/pi/Downloads/font/sazanami-20040629/sazanami-gothic_14.bdf") # 上の行と分割しないとセグフォ
self.font.append(graphics.Font())
self.font[1].LoadFont("/home/pi/Downloads/font/misaki_bdf_2021-05-05/misaki_gothic_2nd.bdf")
self.textColor.append(graphics.Color(255, 255, 0))
# 画像設定
self.image = Image.open("/home/pi/ledmatrix/twitter_dot.png")
# パネル点灯する関数(一時的)
def displayLEDTemp(self):
print('display "WAITING..."')
font_simple = graphics.Font()
font_simple.LoadFont("/home/pi/rpi-rgb-led-matrix/fonts/5x8.bdf")
textColor_simple = graphics.Color(255, 255, 0)
#while (self.flagPreparedToDisplay == False):
timeout_start = time.time()
while time.time() < timeout_start + 10: # 10秒経過後にWAITING表示を消すはずだが機能しない。ただ上のwhileより処理が早いのでこちらを使ってる…
graphics.DrawText(self.offscreen_canvas, font_simple, 0, 31, textColor_simple, "WAITING...") # 静止文字
self.offscreen_canvas = self.matrix.SwapOnVSync(self.offscreen_canvas)
# パネル点灯する関数(メイン)
def displayLEDMain(self):
global atcl_arr
global temp
# Start loop
i = 0
pos = self.offscreen_canvas.width
self.flagPreparedToDisplay = True
print("display LED, Press CTRL-C to stop")
while True:
self.offscreen_canvas.Clear()
graphics.DrawText(self.offscreen_canvas, self.font[1], 0, 7, self.textColor[0], "外気温"+temp) # 静止文字
length = graphics.DrawText(self.offscreen_canvas, self.font[0], pos, 29, self.textColor[0], atcl_arr[i]) # 動く文字
self.matrix.SetImage(self.image.convert('RGB'), 0, 8, False) # 画像
# 動く文字の位置をずらす
pos = pos - 1
if (pos + length < 0): # 文字が左まで行って消えたら、posをリセット
pos = self.offscreen_canvas.width
# iをインクリメント、iがMAXなら0にする
if (i == len(atcl_arr)-1):
i = 0
else:
i += 1
time.sleep(0.05)
self.offscreen_canvas = self.matrix.SwapOnVSync(self.offscreen_canvas)
def worker():
global atcl_arr
global temp
atcl_arr = getNews()
temp = getTemperature()
def mainloop(time_interval, f, another):
f() # 最初に情報取得の完了まで待たないと描画時にoutofindex
now = time.time()
t0 = threading.Thread(target=another) # argsの,は必要。ないとエラー
t0.setDaemon(True)
t0.start() # 描画
while True: # 5分後、以後5分ごとに実行
wait_time = time_interval - ( (time.time() - now) % time_interval )
time.sleep(wait_time)
t = threading.Thread(target=f)
t.setDaemon(True)
t.start() # 情報取得を実行
if __name__ == "__main__":
LED = createLED()
mainloop(300, worker, LED.displayLEDMain)
処理の流れは以下です。
- 2の表示に必要な最低限のフォントなどを読み込み
- パネルに"WAITING..."を表示
- 最新ニュースおよび気温の情報をスクレイピングして取得
- 5の表示に必要なフォント、画像などを読み込み
- パネルに最新ニュースと気温を表示
- 以後、5分ごとに3を実行しパネルにも取得情報を反映
こだわったポイントとしては以下2点があります。
- ライブラリのデモにもサポートがない2行以上の表示に対応
- マルチスレッド(Threading Module)を採用。描画と表示で役割を分担し処理を高速化
一点注意として、パネルでの日本語の表示は不可能で一旦文字を画像に変換して表示するのが必要とかQiitaにも情報が掲載されていますが、不要だと思います。余計にオーバーヘッドが大きくなると思います。比較したわけではありませんが。。
普通にBDF形式の日本語フォントをダウンロードしてくるか、TTF形式のフォントをOTF2BDFで変換して読み込ませれば表示できると思います。
方法は以下参照。
https://yoshisyou.com/electric_bulletin_board_raspberry-pi/#%E6%97%A5%E6%9C%AC%E8%AA%9E%E3%81%8C%E6%B5%81%E3%82%8C%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B
パネル表示プログラムの最小構成
ライブラリのサンプルプログラムだとパネル表示するために必要なプログラムの最小構成が分かりにくかったので以下に載せます。
描画はwhileの無限ループで行い、毎ループでRGBMatrixクラスのSwapOnVSyncメソッドを実行することが必要です。
これを応用すればどんなに複雑な表示でも行えると思います。
# ライブラリインポート
from rgbmatrix import RGBMatrix, RGBMatrixOptions
from graphics import graphics
import time
# パネル設定
options = RGBMatrixOptions()
options.rows = 32
options.cols = 64
options.gpio_slowdown = 0
matrix = RGBMatrix(options = options)
# キャンバス作成
offscreen_canvas = matrix.CreateFrameCanvas()
# フォント設定
font_simple = graphics.Font()
font_simple.LoadFont("/home/pi/rpi-rgb-led-matrix/fonts/5x8.bdf")
textColor_simple = graphics.Color(255, 255, 255)
# 描画
while True:
graphics.DrawText(offscreen_canvas, font_simple, 0, 31, textColor_simple, "hello world")
offscreen_canvas = matrix.SwapOnVSync(offscreen_canvas) # 必要
スマートホーム化
Node-REDを導入し、アレクサのウェイクワードからラズパイのコマンドを起動できるようにします。
以下記事が参考になります。
https://qiita.com/g-iki/items/a5d4d4674a30de7ed124
Node-REDで入力するコマンドは以下の通りにしました。
OFF時のコマンドについて、これでPythonで動かしている全てのプロセスを停止するようにしています。
おわりに
ネットに情報が多く上がっていたため、デモまでは凄くすんなり行きましたが、2行以上の表示など応用をやろうとすると大変でした。
ジャンパー線を使った工作作業は趣味でやるのは初めてだったため楽しかったです。
電光掲示板に表示する情報のアイデア募集中です。