Raspberry Piで電光掲示板を作る(発展)
前回の記事
では、指定の文字列を表示するところまで作成しました。
その続きとして、内容を自在に変えられる電光掲示板用のプログラムを作成します。
また、最終的にはより大きな電光掲示版として、縦2×横3の計6枚に表示するよう制作していきます。
目次
指定内容を表示させる
前回、作成した部分では
$ sudo /home/pi/rpi-rgb-led-matrix/examples-api-use/scrolling-text-example --led-cols=64 --led-rows=32 --led-chain=2 --led-no-hardware-pulse --led-slowdown-gpio=2 -f /home/pi/Downloads/font/sazanami-20040629/sazanami-mincho.bdf ウクライナ難民が400万人超える…UNHCRの最大想定人数、5週間で上回る
と、コマンド指定の結果、目的のテキストを表示できましたが、問題点が2つあります。
1.表示内容は全てコマンドと共に手打ちする必要がある。
2.表示内容はずっとスクロールし続けるので、Ctrl+Cで停止する必要がある。
これでは電光掲示板としてあまり役に立っているとは言えないので、改修します。
プログラムの概要
内容的にはこのようになり、表示したいテキストを用意してそのファイルを1行づつ読み、LEDマトリクスへの指示コマンドに成型して発行すれば良さそうです。
あいうえお
aiueo
かきくけこ
kakikukeko
さしすせそ
sashisuseso
たちつてと
tachitsuteto
なにぬねの
naninuneno
はひふへほ
hahifuheho
まみむめも
mamimumemo
や-ゆ-よ
ya-yu-yo
らりるれろ
rarirurero
#!/usr/bin/env python3
# -*- coding: utf8 -*-
import subprocess
def eprint(tline):
tline = tline.replace('\n','')
command = 'sudo'
command += ' /home/pi/rpi-rgb-led-matrix/examples-api-use/scrolling-text-example'
command += ' -l1'
command += ' --led-cols=64'
command += ' --led-rows=32'
command += ' --led-chain=2'
command += ' --led-no-hardware-pulse'
command += ' --led-slowdown-gpio=2'
command += ' -f /home/pi/Downloads/font/sazanami-20040629/sazanami-mincho.bdf '
command += tline
print(command)
try:
subprocess.run(command,shell=True)
except:
print('subprocess.check_call() failed')
def main():
#テキストファイルを読み込む
f = open('display.txt', 'r', encoding='UTF-8')
while True:
line = f.readline()
if line:
eprint(line)
else:
break
f.close()
if __name__ == '__main__':
main()
新しいパラメータとして、-l1が入っています。
デフォルトは-1のこの値は、入力値を何回表示するかを指定できます。
1を指定すると、1回表示して終了になります。
実行
これを実行すると、次の動画のようになります。
このように、表示する文字列を含んだLEDマトリックスへの指示コマンドが次々生成され、違う文字列が表示されます。
パラメーターを増やして表現を増やす
他にも指定していなかったパラメータがあり、それを指定すると、文字色や背景色、表示速度などが変えられます。
これらのパラメータを指定するため、最初の文字列のテキストに指定項目を入れる事にします。
あいうえお 255,255,255
aiueo 255,0,0
かきくけこ 0,255,0
kakikukeko 0,0,255
さしすせそ 0
sashisuseso
判りにくいかもしれませんが、表示文字の後ろにTABがセットしてあります。
さしすせそは、タブの後ろに指定の形でない0を入れました。
#!/usr/bin/env python3
# -*- coding: utf8 -*-
import subprocess
import re
def eprint(tline):
tline = tline.replace('\n','')
param = tline.split('\t')
try:
param[1]
if len(re.findall('[0-9]{1,3},[0-9]{1,3},[0-9]{1,3}', param[1])) == 0:
parac = ''
else:
parac = ' -C ' + param[1]
except IndexError:
parac = ''
command = 'sudo'
command += ' /home/pi/rpi-rgb-led-matrix/examples-api-use/scrolling-text-example'
command += ' -l1'
command += parac
command += ' --led-cols=64'
command += ' --led-rows=32'
command += ' --led-chain=2'
command += ' --led-no-hardware-pulse'
command += ' --led-slowdown-gpio=2'
command += ' -f /home/pi/Downloads/font/sazanami-20040629/sazanami-mincho.bdf '
command += param[0]
print(command)
try:
subprocess.run(command,shell=True)
except:
print('subprocess.check_call() failed')
def main():
#テキストファイルを読み込む
f = open('display.txt', 'r', encoding='UTF-8')
while True:
line = f.readline()
if line:
eprint(line)
else:
break
f.close()
if __name__ == '__main__':
main()
改造点は次の通りです。
- 表示したい文字列に0~255の数字を[,]で区切って付ける。区切りとしてタブを入れる。RBG値の指定になる。
- プログラム側は、splitで取得文字列をタブ位置で分解
- 文字列があり、指定の書式通りなら、-Cと共にパラメータに組み込む
これにより、表示したい文字列のカラー指定が行われます。
指定がない場合、あるいはTABだけ、指定の書式に合っていない場合は無視されます。
splitで分割した要素により、スクロールするスピードや、地の色も変える事ができます。
さらにタブで区切り要素を増やし、パラメータとして取り込めば、地色やスクロール速度もコントロールできます。
スクレイピングでWEBページのデータを取得する
以前、pythonからブラウザを自動運転してデータを取得する方法をご紹介しました。
今回は、Python 内部のHTMLアクセス機能を使用して、WEBページからテキストデータを取得し、これを電光掲示板に表示してみましょう。
#!/usr/bin/env python3
# -*- coding: utf8 -*-
import requests, bs4
def main():
res = requests.get('https://www3.nhk.or.jp/news/catnew.html')
res.raise_for_status()
soup = bs4.BeautifulSoup(res.content, "html.parser")
elems = soup.select('em.title')
l = 0
for elem in elems:
nwt = 'NHKニュース:'
nwt += elem.getText().replace('\n','') + '\t255,255,255\t4'
print(nwt)
l += 1
if l > 2:
break
if __name__ == '__main__':
main()
コードとしては簡単ですが、https://www3.nhk.or.jp/news/catnew.html から、<em> というタグに、title という css の Class が設定されている文章を抽出しています。
getText()でテキスト化すれば、このページの新着ニュース一覧のタイトルだけを集めてくる事が可能です。
実行すると、このような結果になります
全部は流石に多いので、上から3つのニュースを取得します。
前後に電光掲示板に必要な情報を加えて、先ほどのプログラムの表示用関数に送り込む様にすればOKです。
$ ./Untitled-4.py
NHKニュース:大谷翔平 開幕戦【速報】5回途中降板 引き続き指名打者で出場 255,255,255 4
NHKニュース:小林経済安保相「国民の命と暮らしを守り抜く体制整備を」 255,255,255 4
NHKニュース:大阪・関西万博“100の国と地域が参加表明 招請進める”万博相 255,255,255 4
その他にも、天気予報や、経済情報も取得しましょう。
取得に必要なデータが、cssクラスなどを含んで居ない場合は、上位部分を丸ごともってきて、必要部分を抽出する方法で対応します。
#!/usr/bin/env python3
# -*- coding: utf8 -*-
import re
import requests, bs4
def main():
res = requests.get('https://www.nikkei.com/markets/worldidx/')
res.raise_for_status()
soup = bs4.BeautifulSoup(res.content, "html.parser")
elems = soup.select('table.cmn-table_style1')
economic = ''
for elem in elems:
economic += elem.getText()
#整形
economic = economic.replace('\n',' ')
economic = economic.replace(u'\xa0', '')
economic = economic.replace('(','(')
economic =economic.replace(')',')')
economic = re.sub(' {2,4}', ' ', economic)
#必要箇所抽出
dt1 = re.search('日経平均.+?[0-9]{1,2}:[0-9]{1,2}', economic)
dt2 = re.search('TOPIX.+?[0-9]{1,2}:[0-9]{1,2}', economic)
dt3 = re.search('ドル・円.+?[0-9]{1,2}:[0-9]{1,2}', economic)
dt4 = re.search('ユーロ・円.+?[0-9]{1,2}:[0-9]{1,2}', economic)
economici = '経済情報 '
economici += dt1.group()
economici += ' '
economici += dt2.group()
economici += ' '
economici += dt3.group()
economici += ' '
economici += dt4.group()
economici += ' 日本経済新聞社提供'
economici += '\t255,255,255\t4'
print(economici)
if __name__ == '__main__':
main()
経済情報 日経平均(円) 26,985.80 +97.23(0.36%) 8日 15:15 TOPIX※ 1,896.79 +3.89(0.21%) 8日 15:00 ドル・円※ 124.04-124.05 +0.29(0.23%) 8日 18:34 ユーロ・円※ 134.84-134.86 -0.01(0.00%) 8日 18:34 日本経済新聞社 提供 255,255,255 4
日本経済新聞のWEBページの市況欄から、日経平均株価や、TOPIX、為替など必要情報だけ取り出して、表示内容にしています。
この他にも、天気予報やスポーツなど、表示したいページからデータを取りだしてくれば、いろいろなニュースを表示できます。
LEDマトリックスを縦に繋ぐ
今回使用しているLEDマトリクスは、縦方向に32個、横方向に64個のLEDが並んだものなので、32ドット以上の大きさの文字などを表示する事はできません。
室内などで使用するには問題ないと思われますが、より大きな表示が必要な場合もあるでしょう。
この場合、rpi-rgb-led-matrixには、縦方向にパネルを繋げる事が可能です。
例えば、2枚のLEDマトリックスパネルを縦に接続すると、縦方向に64個、横方向64個のほぼ正方形の表示ができるようになります。
ただ、この場合、2枚のパネルを縦方向に繋げて信号を制御するため、GPIOピンの接続が異なる治具が必要になります。
GNDや、A,B,C,Dなどは1枚目と同じですが、R1やG1などカラー関係のピンが異なっています。
やっかいな事に、1枚目と共通しているピンと、異なっているピンがあるため、配線はかなり大変です。
上記のGPIO配列にしたがって、Raspberry Pi用ユニバーサル基板、ラズパイ用スタッキングコネクタを用いて、もうひとつ治具を作成しました。
二段重ねですww とりあえず試作なので……
これを2枚のLEDパネルに繋ぎます。
今回作成した治具の方が、下段のパネル用という事になります。
接続したら、デモを実行してみます。
sudo ./rpi-rgb-led-matrix/examples-api-use/demo --led-no-hardware-pulse --led-rows=32 --led-cols=64 --led-parallel=2 --led-slowdown-gpio=2 -D 0
–led-parallel=2 で、LEDが縦に繋がっている事を指定しています。
中央あたりにうっすらと線が見えますが、これがLEDパネルの接合部分です。
このように、縦に大きな四角がくるくる回転します。
縦方向は、3枚まで繋ぐ事が可能なようです。もちろん、情報線の異なる結線が必要になるため、治具3段目が要りますので、2枚までしか実験していません。
64×192ドットのパネルにする
これで、–led-parallel=2′ で縦2枚に、–led-chain=3’で横3枚の計6枚を一つのパネルとして使用する準備ができました。
縦は32×2で64ドット、横は64×3で192ドットになります。
納品先を借りて試験した結果です。動画は撮り忘れましたww
フォントについては、前述のさざなみフォントをbdfフォントに変換して使用します。
縦サイズが64ドットになりましたので、
otf2bdf -p 58 -r 75 -o sazanami-mincho.bdf sazanami-mincho60.ttf
最初、上下2ドットずつ引いて60ドットにしたところ、漢字部分がうまく変換されず、58になりました。
依頼主のアイデアで、6枚のパネルの結合は、29.5センチx80センチのワイヤーネットを使用し、千鳥プレートと3Mのネジで固定する事にしました。
また。Raspberry Piなどを固定のため、縦2枚をつなぐ部分はホワイトウッドの板としました。
さらに、この電光掲示板は窓の外から見る事を前提にしていますが、窓にはブラインドがあるため、できるだけ薄くする必要が生じました。
先ほどのようにRaspberry Piに基板を2段重ねという訳にいかなくなり、やむをえず、Raspberry PiのGPIOを40ピンのリボンケーブルとフラットケーブルコネクタで別のユニバーサル基板に持ち出し、そこで2つの16品フラットケーブルコネクタに配線する方法で解決しました。
配線するまえと、実際に結線して、LEDパネルと繋いだところです。
Raspberry Piの配線とスイッチ
Raspberry Pi本体はパネルと一体化させるため、モニターやマウス、キーボードを接続させる訳にいかないため、当初は制御するパソコンにVNCを入れて、遠隔操作でシャットダウンしたりする予定でしたが、タクトスイッチを使うとスイッチの長押しでシャットダウンできるという記事があったため、そのスイッチを設置しました。
また、電源がLEDパネル用とRaspberry Piに別々に必要なため、マイクロUSBの端子部分を購入し、スイッチング電源から電源を確保しました。
プラグ部分だけが売られているので、コードをハンダ付けします。+はオレンジ、マイナスは白コードとしました。
完成
完成したものを車道を挟んだ反対側からみると、このようになります。
文字も判読可能で、お店の情報などが表示されるのが判ります。
使用しているプログラムは、このようになります。
mainを無限ループで呼び、mainでテキストファイルを読み、表示し、テキストファイルに指定のワードがあったら、NHKニュース、天気予報、経済情報は各WEBから情報を得つつ表示するようになっています。
#!/usr/bin/env python3
# -*- coding: utf8 -*-
import subprocess
import re
import requests, bs4
def eprint(tline):
tline = tline.replace('\n','')
param = tline.split('\t')
#文字色のパラメータ RGB値
try:
param[1]
if len(re.findall('[0-9]{1,3},[0-9]{1,3},[0-9]{1,3}', param[1])) == 0:
parac = ''
else:
parac = ' -C ' + param[1]
except IndexError:
parac = ''
#スクロール速度のパラメータ
try:
param[2]
if len(re.findall('[0-9]{1,2}', param[2])) == 0:
paras = ''
else:
paras = ' -s ' + param[2]
except IndexError:
paras = ''
#背景色のパラメータ
try:
param[3]
if len(re.findall('[0-9]{1,3},[0-9]{1,3},[0-9]{1,3}', param[3])) == 0:
parab = ''
else:
parab = ' -B ' + param[3]
except IndexError:
parab = ''
command = 'sudo'
command += ' /home/pi/rpi-rgb-led-matrix/examples-api-use/scrolling-text-example'
command += ' -l1'
command += parac
command += paras
command += parab
command += ' --led-cols=64'
command += ' --led-rows=32'
command += ' --led-parallel=2'
command += ' --led-chain=3'
command += ' --led-no-hardware-pulse'
command += ' --led-slowdown-gpio=4'
command += ' -f /home/pi/Downloads/font/sazanami-20040629/sazanami-mincho60.bdf '
command += param[0]
print(command)
try:
subprocess.run(command,shell=True)
except:
print('subprocess.check_call() failed')
def news():
res = requests.get('https://www3.nhk.or.jp/news/catnew.html')
res.raise_for_status()
soup = bs4.BeautifulSoup(res.content, "html.parser")
elems = soup.select('em.title')
l = 0
for elem in elems:
nwt = 'NHKニュース:'
nwt += elem.getText().replace('\n','') + '\t255,255,255\t2'
eprint(nwt)
l += 1
if l > 2:
break
def weather():
res = requests.get('https://tenki.jp/forecast/3/15/4520/12239/')
res.raise_for_status()
soup = bs4.BeautifulSoup(res.content, "html.parser")
elems = soup.select('div.forecast-days-wrap')
weather = '天気予報 '
for elem in elems:
weather += elem.getText()
#整形
weather = weather.replace(u'\xa0', '')
weather = weather.replace('\n',' ')
weather = weather.replace(' ',' ')
weather = re.sub(' {2,4}', ' ', weather)
weather = re.sub(r'降水確率 (.+?) (.+?) (.+?) (.+?) ', '降水確率 00-06(\\1) 06-12(\\2) 12-18(\\3) 18-24(\\4) ', weather)
weather = weather.replace('時間 00-06 06-12 12-18 18-24 ','')
weather = weather.replace('(','(')
weather = weather.replace(')',')')
weather += ' :tenki.jp提供'
weather += '\t255,255,255\t2'
eprint(weather)
def economic():
res = requests.get('https://www.nikkei.com/markets/worldidx/')
res.raise_for_status()
soup = bs4.BeautifulSoup(res.content, "html.parser")
elems = soup.select('table.cmn-table_style1')
economic = ''
for elem in elems:
economic += elem.getText()
#整形
economic = economic.replace('\n',' ')
economic = economic.replace(u'\xa0', '')
economic = economic.replace('(','(')
economic =economic.replace(')',')')
economic = re.sub(' {2,4}', ' ', economic)
#必要箇所抽出
dt1 = re.search('日経平均.+?[0-9]{1,2}:[0-9]{1,2}', economic)
dt2 = re.search('TOPIX.+?[0-9]{1,2}:[0-9]{1,2}', economic)
dt3 = re.search('ドル・円.+?[0-9]{1,2}:[0-9]{1,2}', economic)
dt4 = re.search('ユーロ・円.+?[0-9]{1,2}:[0-9]{1,2}', economic)
economici = '経済情報 '
economici += dt1.group()
economici += ' '
economici += dt2.group()
economici += ' '
economici += dt3.group()
economici += ' '
economici += dt4.group()
economici += ' 日本経済新聞社提供'
economici += '\t255,255,255\t2'
eprint(economici)
def main():
#テキストファイルを読み込む
f = open('/home/pi/Documents/display.txt', 'r', encoding='UTF-8')
while True:
line = f.readline()
if line:
if '[NEWS]' in line:
news()
elif '[WEATHER]' in line:
weather()
elif '[ECONOMIC]' in line:
economic()
else:
eprint(line)
else:
break
f.close()
if __name__ == '__main__':
while True:
main()
設置しているお店はこちらです。
コワーキングスペースなので、テレワークや勉強、集会など、様々に使用できます。
ご利用ついでに、実物をみていただけると幸いです。