tameshitemita.blog
tameshitemita.blog
背景
- ゲーム利用チケットの構想と、Home Assisntantを入れてAlexaの通知を実験を経て運用に向けて準備する
やることまとめ

- 「iPadでアイコンタップ」はおなじみ、ショートカットアプリでsshでコマンドを叩きにいくのを想定
- 「終了予定時間を入力」は、こどもに手間がかかる為、今回は45分という固定にした
- チケットのフォーマットを決める+チケットのサーマルプリンタ出力を作る
試してみた
- 逆順で対応していく
- プリンタ出力+文言を決めて、Pythonで作成
from escpos.printer import Serial
from PIL import Image, ImageDraw, ImageFont
from datetime import datetime
import sys
import argparse
import locale
# 日本語曜日を使いたい場合(環境に合わせて必要なら有効化)
try:
locale.setlocale(locale.LC_TIME, "ja_JP.UTF-8")
except locale.Error:
# ロケールが無い環境では英語曜日になる
pass
def wrap_line_pixel(line, font, max_width, draw):
"""
1行のテキストを、pixel幅 max_width で折り返した行リストに変換する
"""
result = []
buf = ""
for ch in line:
trial = buf + ch
bbox = draw.textbbox((0, 0), trial, font=font)
w = bbox[2] - bbox[0]
if w <= max_width:
buf = trial
else:
if buf:
result.append(buf)
buf = ch
if buf:
result.append(buf)
return result
def print_ticket(end_time_str: str):
# フォント設定
font_path = "/usr/share/fonts/truetype/fonts-japanese-gothic.ttf"
font_title = ImageFont.truetype(font_path, 48) # タイトル用(大きめ)
font_item = ImageFont.truetype(font_path, 28)
font_date = ImageFont.truetype(font_path, 24)
# 用紙幅とレイアウト
WIDTH = 576
head_margin = 10
line_margin = 6
foot_margin = 20
# 一時イメージ(幅計測用)
tmp_img = Image.new("1", (WIDTH, 100), 1)
tmp_draw = ImageDraw.Draw(tmp_img)
left_margin = 7
right_margin = 7
max_text_width = WIDTH - (left_margin + right_margin)
# ---- ここから文面の定義 ----
now = datetime.now()
# YYYY/MM/DD(曜日)
# ロケールが有効なら日本語曜日、だめなら英語曜日になる
date_text = now.strftime("%Y/%m/%d(%a)")
start_time_text = now.strftime("開始時間:%H:%M:%S")
end_time_text = f"終了時間:{end_time_str}"
notice_lines = [
"注意事項:",
"・仲良くゲームをすること。",
"・仲良く出来なかった場合は、すぐに終了とする。",
]
# 行ごとに高さ計測
def text_height(font):
bbox = tmp_draw.textbbox((0, 0), "あ", font=font)
return (bbox[3] - bbox[1]) + line_margin
title_h = text_height(font_title)
date_h = text_height(font_date)
item_h = text_height(font_item)
# チケット全体の高さを計算
# タイトル1行 + 日付1行 + 開始/終了時間2行 + 注意事項4行
HEIGHT = (
head_margin
+ title_h
+ date_h
+ item_h * (2 + len(notice_lines))
+ foot_margin
)
# 本番イメージ
img = Image.new("1", (WIDTH, HEIGHT), 1)
draw = ImageDraw.Draw(img)
y = head_margin
# タイトル(中央寄せ・幅一杯イメージ)
title_text = "ゲームチケット"
title_bbox = draw.textbbox((0, 0), title_text, font=font_title)
title_w = title_bbox[2] - title_bbox[0]
title_x = (WIDTH - title_w) // 2
draw.text((title_x, y), title_text, font=font_title, fill=0)
y += title_h
# 2行目: 日付
date_bbox = draw.textbbox((0, 0), date_text, font=font_date)
date_w = date_bbox[2] - date_bbox[0]
date_x = (WIDTH - date_w) // 2 # 中央寄せ
draw.text((date_x, y), date_text, font=font_date, fill=0)
y += date_h
# 3行目: 開始時間
draw.text((left_margin, y), start_time_text, font=font_item, fill=0)
y += item_h
# 4行目: 終了時間(引数)
draw.text((left_margin, y), end_time_text, font=font_item, fill=0)
y += item_h
# 注意事項(折り返しあり)
for line in notice_lines:
wrapped = wrap_line_pixel(line, font_item, max_text_width, draw)
for wline in wrapped:
draw.text((left_margin, y), wline, font=font_item, fill=0)
y += item_h
# デバッグ用に画像保存
img.save("ticket_thermal.png")
# 印刷
p = Serial(devfile="/dev/ttyACM0", baudrate=9600, timeout=1, dsrdtr=True)
p._raw(b"\x1b@")
p.image("ticket_thermal.png")
p.cut()
def main():
parser = argparse.ArgumentParser(description="ゲームチケット印刷")
parser.add_argument(
"end_time",
help="終了時刻(例: 21:30)",
)
args = parser.parse_args()
print_ticket(args.end_time)
if __name__ == "__main__":
main()

youtu.be