余ったラズパイで時計と天気を表示するデジタルサイネージ作成の備忘録


余ったラズパイで時計と天気を表示するデジタルサイネージっぽいものを作った。

使用したもの
 - Raspberry Pi 3 ModelB+
 - LEDディスプレイ
 - HDMI
 - マウス
 - キーボード

## 時計と天気を表示するPythonアプリの作成
時計と天気はTkinterで表示する。天気はOpenWetherAPIで特定の場所の天気を取得する。

https://openweathermap.org/api

Tkinterをインストール
```
pip3 install pytk
```

下記時計と天気情報を表示するプログラム
```python
import tkinter as tk
from tkinter import font
import time
import requests
import math

# OpenWeatherMap API
api_key = '**************************'  # ここにAPIキーを入力してください
city_name = 'Tokyo'  # 表示したい都市名

def get_weather():
    url = f"http://api.openweathermap.org/data/2.5/weather?q={city_name}&appid={api_key}&units=metric"
    response = requests.get(url)
    weather_data = response.json()
    
    if weather_data['cod'] == 200:
        temp = weather_data['main']['temp']
        weather_desc = weather_data['weather'][0]['description']
        return f"{city_name}: {temp}°C, {weather_desc.capitalize()}"
    else:
        return "Weather data not available"

def update_clock():
    current_time = time.strftime('%H:%M:%S')
    time_label.config(text=current_time)
    time_label.after(1000, update_clock)  # 1秒ごとに更新

def update_weather():
    weather = get_weather()
    weather_label.config(text=weather)
    weather_label.after(600000, update_weather)  # 10分ごとに更新

def draw_hand(canvas, length, angle, width, color, tag):
    angle = math.radians(angle)
    x = center_x + length * math.sin(angle)
    y = center_y - length * math.cos(angle)
    canvas.create_line(center_x, center_y, x, y, width=width, fill=color, tags=tag)

def update_analog_clock():
    # 既存の針を削除
    canvas.delete('hands')
    
    # 現在時刻を取得
    now = time.localtime()
    hour = now.tm_hour % 12
    minute = now.tm_min
    second = now.tm_sec
    
    # 角度計算
    hour_angle = (hour + minute / 60) * 30  # 1時間=30度
    minute_angle = (minute + second / 60) * 6  # 1分=6度
    second_angle = second * 6  # 1秒=6度
    
    # 時計の針を描画（タグ付き）
    draw_hand(canvas, clock_radius * 0.5, hour_angle, 8, 'white', 'hands')  # 時針
    draw_hand(canvas, clock_radius * 0.7, minute_angle, 6, 'white', 'hands')  # 分針
    draw_hand(canvas, clock_radius * 0.9, second_angle, 2, 'red', 'hands')  # 秒針
    
    canvas.after(1000, update_analog_clock)

# GUI Setup
root = tk.Tk()
root.title("Digital Signage")
root.attributes("-fullscreen", True)  # フルスクリーンモード
root.configure(background='black')
root.attributes('-fullscreen', True)

# フォント設定
time_font = font.Font(family='Helvetica', size=30, weight='bold')
weather_font = font.Font(family='Helvetica', size=20, weight='bold')

# 時計の中心と半径
center_x, center_y = 200, 180
clock_radius = 150

# キャンバス（アナログ時計表示用）
canvas = tk.Canvas(root, width=400, height=400, bg='black', highlightthickness=0)
canvas.pack(anchor='center', pady=0)

# デジタル時計表示ラベル
time_label = tk.Label(root, font=time_font, bg='black', fg='white')
time_label.pack(anchor='n', pady=0)

# 天気表示ラベル
weather_label = tk.Label(root, font=weather_font, bg='black', fg='white')
weather_label.pack(anchor='s', pady=20)

# アナログ時計の盤面を描画
for i in range(12):
    angle = math.radians(i * 30)
    x = center_x + clock_radius * 0.9 * math.sin(angle)
    y = center_y - clock_radius * 0.9 * math.cos(angle)
    canvas.create_text(x, y, text=str(i if i > 0 else 12), fill="white", font=("Helvetica", 24))

# 時刻と天気情報の更新
update_clock()
update_weather()
update_analog_clock()

# 終了するにはEscキーを押す
root.bind("<Escape>", lambda e: root.destroy())

root.mainloop()
```

実行すると下記のような画面が表示される。
![2024-08-21-103117_1920x1080_scrot-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483556/6782168b-22f5-19e9-0318-86bd950c76d3.png)

## ラズパイ起動時に自動実行
意外とここに時間がかかった。

:::note info
サービスデーモンを作成するときに気を付けること
1. コマンドはフルパスで記載
1. 実行ユーザーを指定する
1. 失敗したときの再実行を定義する

```
sudo emacs /etc/systemd/system/run_uvicorn.service
sudo systemctl daemon-reload
sudo systemctl stop run_uvicorn.service
sudo systemctl start run_uvicorn.service
```

```
[Unit]
Description = run uvicorn daemon
After=network.target

[Service]
User=ec2-user
Group=ec2-user
ExecStart = /home/ec2-user/run_uvicorn.sh
WorkingDirectory=/var/www/html/myapp
RemainAfterExit=yes
Restart=always
RestartSec=20

 [Install]
WantedBy = multi-user.target
```

```run_uvicorn.sh
#!/bin/sh 
PATH=$PATH:$HOME/.local/bin:$HOME/bin
export PATH

echo `date` >> /home/ec2-user/run_uvicorn.log

# Fast API Server Auto Boot 
alias mycommand='/usr/bin/nohup uvicorn main:app --host localhost --port 8000  --workers 1  --timeout-keep-alive 30 &'
mycommand

#find /tmp/ -type f -mtime +7 -exec rm -f {} \; 
```

:::

起動時の自動実行は、systemctl serviceを使用する。

1. 上記Pythonコードを実行するシェルスクリプトを作成する。
```bash:/home/pi/autoexec.sh
#!/bin/sh

export DISPLAY=:0.0
exec python3 /home/pi/auroexec.py
```
:::note info
DISPLAY=:**の数字はecho $DISPLAYで確認してください
:::

1. serviceファイルを作成する
   vi /etc/systemdl/system/autoexec.service
2. 下記を記載する
   ```/etc/systemdl/system/autoexec.service
   [Unit]
   Desription=My Tkinter Script
   After=network-online.target
   #Wants=graphical.target
   
   [Service]
   User=pi
   Group=pi
   Environment=DISPLAY=:0.0
   #Environment=XAUTHOITY=/home/pi/.Xauthority
   ExecStart=/bin/bash /home/pi/autoexec.sh
   Restart=always
   RestartSec=5
   Type=forking
   RemainAfterExit=yes
   RuntimeMaxSec=7d
   
   [Install]
   WantedBy=multi-user.target
   ```
3. systemctl daemon-reload
4. systemctl start autoexec.service
  これで正常にPythonプログラムが実行されるか確認する。
5. systemctl enable autoexec.service
6. sudo reboot   

## 参考リンク

https://raspberrypi.stackexchange.com/questions/117006/help-launching-tkinter-on-boot-pi-3