🔆

ラズパイ - FastAPIとシリアルLEDで電光掲示板をつくったよ

2021/10/24に公開

FastAPIとシリアルLEDを使って、電光掲示板を作りました。
きっかけ、作り方について紹介します。

https://twitter.com/tw_kotatu/status/1452088857915052036

💡きっかけ

参加している勉強会のLTのネタとして作りました。

⚙概要

動作の流れ

以下の流れです。

  1. PC/スマホより、描画したい文字列を送信する
  2. [ラズベリーパイ]文字列を x * 32の画像に変換する(xは、文字数により変わる)
    例 :
  3. [ラズベリーパイ]作成された画像を読み出し、シリアルLEDに送信する
    文字列が表示される

🔧パーツ一覧

no 部品名 個数 備考
1 ラズベリーパイ 1 今回は4Bで確認
2 シリアルLED(16x16) 4 BTF-LIGHTING WS2812B ECO RGB合金ワイヤー 16X16cm 265ピクセル LED
3 ACアダプター 5V 4A 1 ACアダプター 5V 4A出力プラグ外径5.5mm 3A以上は必須
4 DCジャック変換プラグ 1 DCジャック変換プラグ
5 ジャンパー線 適量 -

接続

各モジュールの接続

image

シリアルLEDの接続

  • 正面から見たシリアルLED
    • image
  • 背面から見たシリアルLED(接続)
    • image

接続表

ws281x 接続先 備考
[A]①3pin - 5V - つながない
[A]①3pin - GND ラズベリーパイ-GND -
[A]①3pin - DIN [pi]① ラズベリーパイ-[PHY]12([BCM]18) [PHY]は物理ピン, [BCM]はプログラムで指定
[A]②2pin - 5V 外部電源-5V DCジャック変換プラグを介して、ACアダプタに接続
[A]②2pin - GND 外部電源-GND DCジャック変換プラグを介して、ACアダプタに接続
[A]③3pin - 5V ws281x - [B]③3pin - 5V 専用のコネクタで接続する
[A]③3pin - GND ws281x - [B]③3pin - GND 専用のコネクタで接続する
[A]③3pin - DOUT ws281x - [B]③3pin - DIN 専用のコネクタで接続する
[B]④3pin - 5V ws281x - [C]④3pin - 5V 専用のコネクタで接続する
[B]④3pin - GND ws281x - [C]④3pin - GND 専用のコネクタで接続する
[B]④3pin - DOUT ws281x - [C]④3pin - DIN 専用のコネクタで接続する
[C]④3pin - 5V ws281x - [D]④3pin - 5V 専用のコネクタで接続する
[C]④3pin - GND ws281x - [D]④3pin - GND 専用のコネクタで接続する
[C]④3pin - DOUT ws281x - [D]④3pin - DIN 専用のコネクタで接続する
  • 専用コネクタは、シリアルLEDに付属されている

ラズベリーパイの接続部分

💻環境

開発環境

  • ラズベリーパイ
    • Linux rpi 5.10.17-v7l+ #1403 SMP Mon Feb 22 11:33:35 GMT 2021 armv7l GNU/Linux
  • Python
    • Python 3.7.3 (default, Jan 22 2021, 20:04:44)

apt

日本語を表示するためにフォントをインストールします。

$ sudo apt-get install fonts-ipafont

pip

adafruit-circuitpython-neopixelというをライブラリを使用することで、シリアルLEDを制御することができます。
本モジュールを使用する場合は、sudo権限が必要です。

インストールが紹介されている公式サイトは、下記となります。

https://learn.adafruit.com/adafruit-neopixel-uberguide/python-circuitpython

adafruit-circuitpython-neopixelをインストールします。

$ python3 -m venv env
$ source env/bin/activate
(env) $ pip install adafruit-circuitpython-neopixel

また、fastapi, pillow, opencv, webcolorsをインストールします。

(env) $ pip install fastapi, uvicorn
(env) $ pip install pillow
(env) $ pip install opencv-python==4.5.1.48
(env) $ pip install webcolors

📝手順

3つのファイルに分けて作成します。
各ファイルの役割は以下となります。

ファイル名 役割 備考
main.py fastapiの実行 -
schemes.py スキーマ PC <-> ラズベリーパイ間のリクエスト定義 -
utl.py 画像の作成, 表示処理 -

全コードは、下記に配置してあります。

https://github.com/kotaproj/pi_electric_bulletin_board

schemes.py : スキーマ

以下の仕様になっています。

POST /regist

項目 説明
job_id string (Job Id) 識別子(ファイル名に使用)
message string (Message) 表示する文字列
color string (Color) 色の指定(red, green, ...)
fontsize integer (Fontsize) 文字サイズ

POST /show

項目 説明
job_id string (Job Id) 識別子(ファイル名に使用)
bright number (Bright) 明るさ
interval number (Interval) 更新間隔

POST /do

項目 説明
job_id string (Job Id) 識別子(ファイル名に使用)
message string (Message) 表示する文字列
color string (Color) 色の指定(red, green, ...)
fontsize integer (Fontsize) 文字サイズ
bright number (Bright) 明るさ
interval number (Interval) 更新間隔

main.py : API処理

表示時間がかかるため、各処理をbackground_tasksに渡しています。

@app.post("/do")
async def do_item(  item: ItemDo,
                        background_tasks: BackgroundTasks = None):
    background_tasks.add_task(do_job, job_id=item.job_id, msg=item.message, color=item.color,\
                                    fontsize=item.fontsize, bright=item.bright, interval=item.interval
    )
    return item

utl.py : 画像の作成、画像の表示

画像の作成 - regist_job

pillowを使用しています。背景を黒にして文字を描画します。
colorにて、"primary"(or "rainbow")を指定した場合、カラフルな文字になります。

import PIL.Image
import PIL.ImageDraw
import PIL.ImageFont
import webcolors


# フォント
ttfontname = "/usr/share/fonts/truetype/fonts-japanese-gothic.ttf"
# 表示位置
top_shift = 0
# 色種別
rain_colors = ["red", "green", "blue", "cyan", "white", "magenta", "yellow"]

def regist_job(job_id: str, msg: str, color: str, fontsize: int) -> None:
    # 画像サイズ,背景色を設定
    canvasSize = (32*(len(msg)+2), 32)
    backgroundRGB = webcolors.name_to_rgb("black")

    # 文字を描く画像の作成
    img  = PIL.Image.new('RGB', canvasSize, backgroundRGB)
    draw = PIL.ImageDraw.Draw(img)

    # 用意した画像に文字列を描く
    x_off, y_off = fontsize, 0

    font = PIL.ImageFont.truetype(ttfontname, fontsize)

    for cnt, moji in enumerate(msg):
        textWidth, textHeight = draw.textsize(moji, font=font)
        textTopLeft = (x_off, top_shift)
        if color in ["rainbow", "primary"]:
            rgb = webcolors.name_to_rgb(rain_colors[cnt%len(rain_colors)])
        else:
            rgb = webcolors.name_to_rgb(color)
        draw.text(textTopLeft, moji, fill=rgb, font=font)
        x_off += textWidth

    # crop
    img = img.crop((0, 0, x_off+32, 32))
    img.save((job_id + ".png"))

画像の表示 - show_job

pngファイルを開いて、スクロールします。
gifを指定すると、アニメーションできるようにもなっています。

def show_job(job_id: str, bright: float, interval: float) -> None:
    global stop_flag_pixel
    global is_running_pixel

    if is_running_pixel:
        stop_flag_pixel = True
        time.sleep(1)
    is_running_pixel = True

    # 消灯
    pixels.fill((0, 0, 0))
    pixels.show()
    
    # 明るさの指定
    pixels.brightness = 0.200 * bright

    img_path = job_id + ".png"

    cap = cv2.VideoCapture(img_path)
    while True:
        ret, frame = cap.read()
        if ret == False:
            break

        h, w, _ = frame.shape
        # スクロール表示
        for srt in range(w-32):
            # 中断
            if stop_flag_pixel:
                stop_flag_pixel = False
                return
            show32x32_pixel(cv2.cvtColor(frame[:,srt:srt+32,:], cv2.COLOR_BGR2RGB))
            if 0.0 < interval:
                time.sleep(interval)
    time.sleep(0.1)

    # LED消灯
    pixels.fill((0, 0, 0))
    pixels.show()
    stop_flag_pixel = False
    is_running_pixel = False

LEDの細かな仕様は、前記事 - ラズパイを使って、シリアルLEDを制御するを参照してください。

実行

ラズベリーパイ側

以下を指定して実行します。

$ sudo ~/mitomoku/env_mito/bin/uvicorn main:app --reload --host 0.0.0.0 --port 8000

PC側

ブラウザ等で、ラズベリーパイIPアドレス:8000にアクセスします。

Image from Gyazo

POST /doで確認にをします。

Image from Gyazo

"Execute"を実行すると、文字列が流れます👏。

さいごに

モノを動かすのは楽しいです。

この他にも、ラズパイの活用方法を

https://zenn.dev/kotaproj/books/raspberrypi-tips

としてまとめ中です。

GitHubで編集を提案

Discussion