Raspberry Pi Zero 2 W を「電源入れるだけで動く」スマートグラス表示サーバにした
(Wi-Fi自動接続 / HTML送信 / ST7735表示 / systemd自動起動)
スマートグラス側(Raspberry Pi Zero 2 W)をモバイルバッテリーに繋いで電源ONすると、
- 勝手にWi-Fiに繋がる
- 勝手にHTMLサーバ(Flask)が立ち上がる
- スマホ/PCのブラウザで
http://<PiのIP>:8000/を開く - 文字を送信すると、メガネ側ディスプレイに即表示
までが ノー操作 で完了する構成です。
参考にしたもの
- Smart Glasses (Under $10!!!)
https://www.instructables.com/Smart-Glasses-4/
メガネに「ディスプレイ+反射鏡+レンズ+透明板(コンバイナ)」を収める光学レイアウトの発想を参考にしました。
本記事は、表示制御を Raspberry Pi Zero 2 W + Web送信(Flask) に置き換え、さらに “電源入れるだけ運用” まで詰めた版です。
仕組み(超ざっくり)
- Wi-Fi自動接続:Raspberry Pi OSの標準機能(事前にSSID/パスを登録)
- Webサーバ:Flask(フォーム → POST受信)
- 描画:Pillowで文字を画像化
- 表示:ST7735へSPIで転送
- 電源ONで自動起動:systemdサービスに登録
材料
メカ(ほぼ紙工作)
- 厚紙(筐体、遮光の壁)
- 黒テープ(遮光と補強)
- 両面テープ / ホットボンド(固定)
光学(代用品で成立させる)
- プレパラート(スライドガラス)
- 100円ショップの老眼鏡(レンズ代用:拡大&ピント調整)
- メイク用品の鏡(反射鏡として使用)
電子
- Raspberry Pi Zero 2 W
- ST7735系 小型SPIディスプレイ(例:128×160)
- モバイルバッテリー(5V)
- 配線(SPI)
配線(ST7735 / SPI)
※SPIの信号名は SCLK(SCK) / MOSI(DIN) 表記が一般的です。
(I2CのSCL/SDAと混同しやすいので、ここではSPI表記で統一します)
- SCLK(SCK) → GPIO11(物理 23)
- MOSI(DIN) → GPIO10(物理 19)
- CS → GPIO8(物理 24, CE0)
- DC → GPIO24(物理 18)
- RST → GPIO25(物理 22)
- VDD → 3.3V(物理 1)
- BKL → 3.3V(直結で点灯:後述の注意あり)
- GND → GND(物理 6)
全体構造(ざっくり)
光の流れはこの順番にすると調整がラクでした。
ディスプレイ →(反射鏡で折る)→ 老眼鏡レンズ → プレパラート(透明板) → 目

*参考図
- 反射鏡:光路を折って、厚紙箱の中で距離を稼ぐ
- 老眼鏡レンズ:表示を見やすい距離に“持ってくる”(ピント合わせ担当)
- プレパラート:外界は透過、HUD像は反射で目に入る(簡易コンバイナ)
完成までの流れ(手順)
1) 厚紙で「光学箱」を作る(ここが勝敗)
- メガネの片側に載るサイズで、厚紙で箱を作る
- 内側は黒テープで徹底的に遮光
- ここが甘いと、表示が薄くなる / 白っぽくなる / 見づらい
コツ:いきなり本固定しないで、最初はマステで“動く仮固定”が吉。
2) メイク鏡を反射鏡にする(光路を折る)
- 鏡部分を取り出して小片にする(※私はこの方法)
- 光学箱の中に 45°くらいで仮固定
- ディスプレイの光が「横→前」へ折れて、箱の中で距離を稼げます
3) ディスプレイを固定(まず“点く”が最優先)
- ST7735ディスプレイを箱の奥に設置
- ここも仮固定でOK(あとで必ず位置調整する)
4) 老眼鏡レンズを入れる(ピントが合う場所を探す)
- 100均老眼鏡をバラして、片側レンズだけ使う
- 反射鏡の先(目側)に入れて、前後に動かしてピントが合う位置を探す
メモ:度数で性格が変わります。
強いほど近距離で合うが歪みやすい / 弱いほど距離が必要。
5) プレパラートを“透明板”として配置(外界とHUDの合成)
- 目の前に来る位置にプレパラートを置く
- 角度を少しずつ変えると、外の景色と表示のバランスが変わる
調整ポイント
- 表示が薄い → 角度 / 遮光 / ディスプレイ輝度を見直す
- 二重に見える(ゴースト) → プレパラート角度を微調整(表裏反射が出やすい)
6) 光学が決まったら固定して“完成形”へ
- 位置が決まった部品から順に本固定
- 最後に厚紙箱の隙間を黒テープで埋め、遮光を仕上げる
Raspberry Pi Zero 2 W 側(電源入れるだけ運用)
7) Wi-Fiを自動接続にしておく(最初の一回だけ)
Raspberry Pi OSでSSID/パスを登録しておけば、次回以降は電源ONで勝手に繋がります。
(GUIでも、ヘッドレスなら設定ファイルでもOK)
8) SPIを有効化(忘れがち)
Raspberry PiでSPIを使うので、有効化しておきます。
-
sudo raspi-config→ Interface Options → SPI → Enable - 再起動:
sudo reboot
9) ソフト:インストール(日本語フォント込み)
sudo apt update
sudo apt install -y fonts-noto-cjk python3-venv
# 仮想環境(例)
python3 -m venv ~/st7735-venv
source ~/st7735-venv/bin/activate
pip install --upgrade pip
pip install flask pillow st7735
注意(ライブラリ差)
ST7735系は複数のPythonライブラリがあり、APIが微妙に違います。
この記事のコードはfrom st7735 import ST7735が通るst7735パッケージを前提にしています。もしimportエラーになる場合は、利用しているディスプレイ基板に合うライブラリ(Pimoroni系など)へ置き換えてください。
10) HTMLサーバ(Flask)で「文字を受けて表示」
やっていることはシンプルにこれだけです。
Flaskサーバコード全文(ST7735 128×160 / 日本語 / HTMLフォーム)
/home/pi/smartglass_server.py として保存します(中央寄せ表示)。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from flask import Flask, request
from PIL import Image, ImageDraw, ImageFont
from st7735 import ST7735
"""
Raspberry Pi Zero 2 W + ST7735(128x160)
ブラウザ(HTML)から文字を送って表示する
配線前提(SPI)
SCLK(SCK) -> GPIO11 (物理23)
MOSI(DIN) -> GPIO10 (物理19)
CS -> GPIO8 (物理24, CE0)
DC -> GPIO24 (物理18)
RST -> GPIO25 (物理22)
VDD -> 3.3V
BKL -> 3.3V 直結(基板によっては注意)
GND -> GND
"""
disp = ST7735(
port=0, # SPI0
cs=0, # CE0 (GPIO8)
dc=24, # GPIO24
rst=25, # GPIO25
backlight=None, # BKLは直結
width=128,
height=160,
rotation=0, # 0/90/180/270
invert=False,
spi_speed_hz=10_000_000,
bgr=False,
)
# ライブラリによって begin() が必要/不要な場合があるため吸収
if hasattr(disp, "begin"):
disp.begin()
# 日本語フォント
FONT_SIZE = 20
FONT_PATH = "/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc"
font = ImageFont.truetype(FONT_PATH, FONT_SIZE)
app = Flask(__name__)
HTML = """
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Smart Glass Control</title>
<style>
body { font-family: sans-serif; padding: 1rem; }
h1 { font-size: 1.4rem; }
input[type=text] { width: 80%; font-size: 1.2rem; padding: 0.2rem; }
button { font-size: 1.1rem; padding: 0.3rem 1rem; margin-top: 0.5rem; }
form { margin-bottom: 1rem; }
.note { color: #666; font-size: 0.9rem; }
</style>
</head>
<body>
<h1>スマートグラス表示コントロール</h1>
<p class="note">同一Wi-Fi上の端末から送信できます(認証なし)。必要なら後述の対策を入れてください。</p>
<form method="POST" action="/text">
<p>
表示したい文字:
<input type="text" name="msg" autocomplete="off" maxlength="80">
</p>
<button type="submit">送信して表示</button>
</form>
<form method="POST" action="/clear">
<button type="submit">画面クリア</button>
</form>
</body>
</html>
"""
def draw_text_on_lcd(msg: str) -> None:
# 空文字は何もしない(好みで変更OK)
msg = (msg or "").strip()
if not msg:
return
img = Image.new("RGB", (disp.width, disp.height), (0, 0, 0))
draw = ImageDraw.Draw(img)
# Pillowのバージョン差吸収
if hasattr(draw, "textbbox"):
bbox = draw.textbbox((0, 0), msg, font=font)
text_w = bbox[2] - bbox[0]
text_h = bbox[3] - bbox[1]
else:
text_w, text_h = draw.textsize(msg, font=font)
x = (disp.width - text_w) // 2
y = (disp.height - text_h) // 2
draw.text((x, y), msg, fill=(255, 255, 255), font=font)
disp.display(img)
def clear_lcd() -> None:
img = Image.new("RGB", (disp.width, disp.height), (0, 0, 0))
disp.display(img)
@app.route("/", methods=["GET"])
def index():
return HTML
@app.route("/text", methods=["POST"])
def set_text():
msg = request.form.get("msg", "")
draw_text_on_lcd(msg)
return HTML
@app.route("/clear", methods=["POST"])
def clear():
clear_lcd()
return HTML
if __name__ == "__main__":
# 同一LANから見えるよう 0.0.0.0
app.run(host="0.0.0.0", port=8000)
11) 「電源入れるだけ」にする:systemd自動起動
OS起動と同時にサーバを上げます。
A) いちばん安定(venv内pythonを直指定)
/etc/systemd/system/smartglass.service
[Unit]
Description=Smart Glass Flask Server
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=pi
WorkingDirectory=/home/pi
Environment=PYTHONUNBUFFERED=1
ExecStart=/home/pi/st7735-venv/bin/python /home/pi/smartglass_server.py
Restart=on-failure
RestartSec=2
[Install]
WantedBy=multi-user.target
反映:
sudo systemctl daemon-reload
sudo systemctl enable smartglass.service
sudo systemctl restart smartglass.service
sudo systemctl status smartglass.service
補足(Wi-Fi待ちが不安定なとき)
network-online.targetでも「Wi-Fiがまだ繋がってない」ケースが出ることがあります。
その場合は環境に応じて “wait-online” を使うと安定します(例:NetworkManager環境ならNetworkManager-wait-online.service)。
B) スクリプト経由で起動したい場合(任意)
/home/pi/start_smartglass.sh
#!/bin/bash
cd /home/pi
source /home/pi/st7735-venv/bin/activate
exec python /home/pi/smartglass_server.py
実行権限を付与:
chmod +x /home/pi/start_smartglass.sh
この場合、smartglass.service の ExecStart は /home/pi/start_smartglass.sh にします。
使い方(運用)
- Piをモバイルバッテリーに繋いで電源ON
- スマホ/PCを同じWi-Fiに繋ぐ
- PiのIPを確認(Pi側で見るなら
hostname -I
- ブラウザで http://:8000/ を開く
- 文字を送信 → スマートグラス側に表示
小ネタ:IPを探したくない場合
環境によってはhttp://raspberrypi.local:8000/のような mDNS で開けることがあります(名前はホスト名次第)。
ハマりポイント(先に潰す)
- 遮光不足:表示が薄くて“何も見えない”原因の9割
- プレパラートは反射率が低い:明るい屋外だと負けやすい(暗所でめちゃ見える)
- 二重像(ゴースト):ガラスの表裏反射。角度調整が効く
- 電源が弱いと不安定:Pi Zero 2 Wは電圧低下で再起動しやすい
追加:表示バリエーション(関数差し替え)
左寄せ版(関数差し替え)
def draw_text_on_lcd(msg: str) -> None:
msg = (msg or "").strip()
if not msg:
return
img = Image.new("RGB", (disp.width, disp.height), (0, 0, 0))
draw = ImageDraw.Draw(img)
if hasattr(draw, "textbbox"):
bbox = draw.textbbox((0, 0), msg, font=font)
text_h = bbox[3] - bbox[1]
else:
_, text_h = draw.textsize(msg, font=font)
x = 0
y = (disp.height - text_h) // 2
draw.text((x, y), msg, fill=(255, 255, 255), font=font)
disp.display(img)
45°回転表示(関数まるごと差し替え)
def draw_text_on_lcd(msg: str) -> None:
msg = (msg or "").strip()
if not msg:
return
w, h = disp.width, disp.height
base = Image.new("RGB", (w, h), (0, 0, 0))
canvas_w, canvas_h = w * 2, h * 2
txt_img = Image.new("RGBA", (canvas_w, canvas_h), (0, 0, 0, 0))
draw = ImageDraw.Draw(txt_img)
if hasattr(draw, "textbbox"):
bbox = draw.textbbox((0, 0), msg, font=font)
text_w = bbox[2] - bbox[0]
text_h = bbox[3] - bbox[1]
else:
text_w, text_h = draw.textsize(msg, font=font)
tx = (canvas_w - text_w) // 2
ty = (canvas_h - text_h) // 2
draw.text((tx, ty), msg, font=font, fill=(255, 255, 255, 255))
rotated = txt_img.rotate(45, expand=True, resample=Image.BICUBIC)
rw, rh = rotated.size
cx = (rw - w) // 2
cy = (rh - h) // 2
cropped = rotated.crop((cx, cy, cx + w, cy + h))
base.paste(cropped.convert("RGB"), (0, 0))
disp.display(base)
セキュリティ(最低限の注意)
この構成は 同一Wi-Fi上の誰でも送信できる可能性があります。
家庭内LANなど、用途が限定されている前提なら割り切れますが、気になる場合は対策を検討してください。
- ルータ側で端末を隔離(ゲストWi-Fi等)
- Pi側でファイアウォールで送信元を制限
- Flaskに簡易トークン(パスコード)を入れる(フォームにhiddenで付ける等)
安全面(重要)
- 鏡・プレパラート加工は破片が危険(目の近くなので特に注意)
- 顔面に近い配線はショートと発熱を最優先で潰す
- 歩行中・運転中の使用は避ける(注意が削れます)
-
BKLを3.3V直結は基板によって電流が大きい場合があります
- 発熱が気になる場合は、抵抗を入れる / トランジスタで駆動する等を検討してください


