Daily Hack

Raspberry Pi 5で天気を読み上げるスマート目覚ましを作ってみた

デジタル
#Raspberry Pi#自動化#IoT#朝活#スマートホーム

スマホのアラーム音で無理やり起こされる朝、つらいですよね。Raspberry Pi 5 を使えば、天気予報を声で教えてくれて、LEDの光で自然に目覚めるスマートアラームが作れます。

この記事では、音声合成による読み上げと LED テープライトの調光を組み合わせた、快適な朝を実現するシステムの作り方を解説します。

スマート目覚まし 動作フロー

完成イメージ

  • 設定時刻の30分前からLEDが徐々に明るくなる(疑似日の出)
  • 設定時刻に天気予報・気温・曜日を音声で読み上げ
  • スピーカーから心地よいチャイム音を再生

必要なもの

| パーツ | 目安価格 | |--------|----------| | Raspberry Pi 5(4GB 以上) | 約10,000円 | | microSD カード(32GB 以上) | 約1,000円 | | USB-C 電源アダプター | 約2,000円 | | スピーカー(3.5mm または Bluetooth) | 約1,000円〜 | | NeoPixel LED テープ(WS2812B)1m | 約800円 | | ロジックレベル変換モジュール | 約200円 | | 5V 電源アダプター(LED用・2A以上) | 約1,000円 |

LED テープライトなしで、音声読み上げだけのシンプル版も作れます。まずは音声だけで試して、あとから LED を追加するのもおすすめです。

STEP 1: 音声合成の環境を準備する

日本語の音声合成には Open JTalk を使います。

sudo apt update
sudo apt install -y open-jtalk open-jtalk-mecab-naist-jdic hts-voice-nitech-jp-atr503-m001

音声合成のテスト

echo "おはようございます。今日もいい天気ですね。" | \
  open_jtalk \
  -m /usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice \
  -x /var/lib/mecab/dic/open-jtalk/naist-jdic \
  -ow /tmp/test.wav && \
  aplay /tmp/test.wav

スピーカーから日本語が聞こえれば成功です。

音が出ない場合は sudo raspi-configSystem OptionsAudio でスピーカーの出力先を設定してください。

STEP 2: 天気予報 API の準備

OpenWeatherMap の無料アカウントを作成

  1. OpenWeatherMap にアクセス
  2. 無料アカウントを作成
  3. API Keys ページで API キーをコピー

無料プランで月60回/分、1,000回/日のリクエストが可能です。朝1回の取得なら十分です。

API の動作確認

curl "http://api.openweathermap.org/data/2.5/weather?q=Tokyo&appid=YOUR_API_KEY&units=metric&lang=ja"

JSON で天気情報が返ってくれば成功です。

STEP 3: Python 環境を構築する

python3 -m venv ~/smart-alarm
source ~/smart-alarm/bin/activate

pip install requests

LED テープライトも使う場合は追加で以下をインストールします。

pip install rpi_ws281x adafruit-circuitpython-neopixel

STEP 4: 音声読み上げスクリプトを作る

# smart_alarm.py
import requests
import subprocess
import os
from datetime import datetime

# === 設定 ===
API_KEY = "YOUR_API_KEY"  # OpenWeatherMap の API キー
CITY = "Tokyo"            # 都市名
VOICE_MODEL = "/usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice"
DICT_DIR = "/var/lib/mecab/dic/open-jtalk/naist-jdic"
WAV_FILE = "/tmp/alarm_message.wav"

WEEKDAYS = ["月", "火", "水", "木", "金", "土", "日"]

def get_weather():
    """天気予報を取得する"""
    url = (
        f"http://api.openweathermap.org/data/2.5/weather"
        f"?q={CITY}&appid={API_KEY}&units=metric&lang=ja"
    )
    try:
        res = requests.get(url, timeout=10)
        data = res.json()
        temp = round(data["main"]["temp"])
        temp_max = round(data["main"]["temp_max"])
        temp_min = round(data["main"]["temp_min"])
        desc = data["weather"][0]["description"]
        humidity = data["main"]["humidity"]
        return {
            "temp": temp,
            "temp_max": temp_max,
            "temp_min": temp_min,
            "desc": desc,
            "humidity": humidity,
        }
    except Exception as e:
        print(f"天気情報の取得に失敗: {e}")
        return None

def build_message(weather):
    """読み上げメッセージを組み立てる"""
    now = datetime.now()
    month = now.month
    day = now.day
    weekday = WEEKDAYS[now.weekday()]

    greeting = "おはようございます。"
    date_str = f"今日は{month}月{day}日、{weekday}曜日です。"

    if weather:
        weather_str = (
            f"現在の天気は{weather['desc']}、"
            f"気温は{weather['temp']}度です。"
            f"今日の最高気温は{weather['temp_max']}度、"
            f"最低気温は{weather['temp_min']}度の予報です。"
        )
        # 天気に応じたアドバイス
        advice = ""
        if "雨" in weather["desc"]:
            advice = "傘を忘れずにお持ちください。"
        elif weather["temp_max"] >= 30:
            advice = "暑くなりそうです。水分をこまめに取りましょう。"
        elif weather["temp_min"] <= 5:
            advice = "冷え込みそうです。暖かい服装でお出かけください。"
    else:
        weather_str = "天気情報を取得できませんでした。"
        advice = ""

    closing = "今日も良い一日をお過ごしください。"

    return f"{greeting}{date_str}{weather_str}{advice}{closing}"

def speak(text):
    """テキストを音声に変換して再生する"""
    # テキストをファイルに書き出し
    text_file = "/tmp/alarm_text.txt"
    with open(text_file, "w") as f:
        f.write(text)

    # Open JTalk で音声合成
    subprocess.run([
        "open_jtalk",
        "-m", VOICE_MODEL,
        "-x", DICT_DIR,
        "-ow", WAV_FILE,
        "-r", "0.9",   # 話速(少しゆっくり)
        "-fm", "2.0",  # 抑揚
        text_file,
    ], check=True)

    # 再生
    subprocess.run(["aplay", WAV_FILE], check=True)

    # 一時ファイル削除
    os.remove(text_file)
    os.remove(WAV_FILE)

# === メイン処理 ===
if __name__ == "__main__":
    print("スマートアラームを実行します")
    weather = get_weather()
    message = build_message(weather)
    print(f"読み上げ内容: {message}")
    speak(message)
    print("完了")

テスト実行

python smart_alarm.py

スピーカーから天気予報が読み上げられれば成功です。

STEP 5: LED 疑似日の出を追加する(任意)

NeoPixel LED テープを使って、アラーム時刻の30分前から徐々にライトを明るくします。

LED テープの配線

| LED テープ | 接続先 | |-----------|--------| | 5V | 5V 外部電源(Piの5Vピンでは電流不足) | | GND | Pi の GND と外部電源の GND を共有 | | DIN | ロジックレベル変換 → GPIO 18 (Pin 12) |

NeoPixel は 5V ロジックで動作しますが、Pi の GPIO は 3.3V です。ロジックレベル変換モジュールを挟むと確実に動作します。

疑似日の出スクリプト

# sunrise.py
import board
import neopixel
import time

# === 設定 ===
LED_PIN = board.D18
LED_COUNT = 30            # LED の数
SUNRISE_DURATION = 1800   # 30分(秒)

pixels = neopixel.NeoPixel(LED_PIN, LED_COUNT, brightness=0.0, auto_write=True)

def sunrise_effect(duration):
    """暖色で徐々に明るくなる疑似日の出"""
    steps = 100
    interval = duration / steps

    for i in range(steps + 1):
        progress = i / steps

        # 色の変化: 暗い赤 → オレンジ → 暖かい白
        if progress < 0.4:
            # 暗い赤からオレンジへ
            r = int(80 * (progress / 0.4))
            g = int(20 * (progress / 0.4))
            b = 0
        elif progress < 0.7:
            # オレンジから明るいオレンジへ
            p = (progress - 0.4) / 0.3
            r = 80 + int(120 * p)
            g = 20 + int(80 * p)
            b = int(20 * p)
        else:
            # 明るいオレンジから暖かい白へ
            p = (progress - 0.7) / 0.3
            r = 200 + int(55 * p)
            g = 100 + int(120 * p)
            b = 20 + int(100 * p)

        brightness = progress * 0.8  # 最大80%
        pixels.brightness = brightness
        pixels.fill((r, g, b))
        time.sleep(interval)

if __name__ == "__main__":
    print("疑似日の出を開始します(30分間)")
    sunrise_effect(SUNRISE_DURATION)
    print("完了 — LED をそのまま点灯しています")

STEP 6: 統合スクリプトを作る

LED の疑似日の出 → 音声アラームを一連の流れで実行します。

# alarm_main.py
import subprocess
import sys

print("=== スマートアラーム開始 ===")

# STEP 1: 疑似日の出(30分間)
print("LED 疑似日の出を開始...")
subprocess.Popen([sys.executable, "/home/pi/sunrise.py"])

# STEP 2: 30分後に音声読み上げ
import time
time.sleep(1800)

print("音声アラームを実行...")
subprocess.run([sys.executable, "/home/pi/smart_alarm.py"])

print("=== スマートアラーム完了 ===")

STEP 7: cron でスケジュール設定する

毎朝6:30にLED疑似日の出を開始し、7:00に音声アラームが鳴るようにします。

crontab -e

以下を追加します。

# 毎朝6:30にスマートアラームを開始(LED 30分前 + 7:00に音声)
30 6 * * * /home/pi/smart-alarm/bin/python /home/pi/alarm_main.py >> /home/pi/alarm.log 2>&1

LED テープなしのシンプル版は、音声スクリプトだけを cron に登録します。

# 毎朝7:00に天気読み上げ
0 7 * * * /home/pi/smart-alarm/bin/python /home/pi/smart_alarm.py >> /home/pi/alarm.log 2>&1

カスタマイズのヒント

  • 都市の変更: CITY = "Osaka" のように変更すれば、他の都市の天気を取得できます
  • 読み上げ速度: -r オプションの値を変更します(0.5=ゆっくり、1.5=速い)
  • BGM の追加: aplay の前にお気に入りの音楽ファイルを再生するコードを追加できます
  • 曜日ごとの変更: 平日と休日でアラーム時刻や内容を変えることも可能です

トラブルシューティング

| 症状 | 原因と対処 | |------|-----------| | 音が出ない | raspi-config → Audio で出力先を確認 | | 天気取得に失敗する | API キーの設定を確認。アカウント作成直後は有効化まで数時間かかる場合あり | | LED が点灯しない | 外部5V電源の接続を確認。GND がPiと共有されているか確認 | | LED の色がおかしい | ロジックレベル変換モジュールの接続を確認 |


毎朝「今日の天気は晴れ、最高気温は22度です」と教えてもらえるだけで、朝の準備がぐっとスムーズになります。まずは音声読み上げだけのシンプル版から始めてみてください。

PRスポンサーリンク
スポンサーリンク