Raspberry pi Zero Wを使って、初代カローラクロスのディスプレイオーディオの無線化を行いました。
この製作手順とGithubのプログラムを紹介します。
無線Android Auto環境をラズパイで作ってみました。2,000円台で簡単に作れちゃうおススメのDIYです。もう高いXXXXCastなんていらないかも!?ぜひ挑戦してみてくださいね。
■今回登場している機器
・ラズパイ Zero 2 W (2,099円、税込み)
こちらのラズパイのほうが安くてスペックいいです。必要な機材付
https://amzn.to/42bwjCu
・ラズパイ4 欲しい方はこちらおススメ(14,980円 税込)
https://amzn.to/4iecVtd
・マスカット一推しのケーブル
https://amzn.to/4ij1NeC
よく行く釣り場のお天気を30時間先まで確認できるアプリを、
ラズパイとGoogle GEMINIで生成したPythonプログラムで実現してみました。
この製作手順やソースコードを共有します。
ラズパイのセットアップまで解説しています。続々と関連動画続きます!
■ラズパイ始めるならこの辺り買ってください
・おすすめラズパイ4セット
https://amzn.to/3XEtwPA
・おすすめラズパイ5セット
https://amzn.to/3FH6dyj
・Waveshare LCD3.5(A)
https://amzn.to/4caFZ3v
・上記のラズパイとLCD組み合わせる場合ひつようかも
https://amzn.to/3E0dWHh
■マスカットが使っているラズパイ用謎アイテムみたいなの
(個人輸入で購入しましたのでドンピシャなモノはありませんが、似たようなもの紹介いたします。)
・謎のバッテリー キーボード付きモニター1
https://amzn.to/3E0dXej
・謎のバッテリー キーボード付きモニター2
https://amzn.to/3QSCDbJ
ラズパイガジェットマスカットのまとめ:
https://mf.d-herald.com/raspberrypi
『お天気ガジェット』の機能概要
5か所の拠点の情報をあらかじめボタンに設定し、切り替えて表示をすることが可能。
30時間分の天気予報をOpenWeatherMapより取得して表示(利用の場合はOpenWeatherMapへ登録を行い、API_KEYを取得してください。無料会員レベルでOK)
1時間おきに天気予報情報を更新。取得データはJSONデータとしてラズパイ内に保管。
日の出・日の入り時刻も現在時刻まわりに表示。
アプリ終了・ラズパイシャットダウンボタンを備える
■お天気ガジェット ver1.0.0 Pythonソースコード
import requests
from datetime import datetime, timedelta
import tkinter as tk
from tkinter import ttk
import calendar
import time
import subprocess
from PIL import Image, ImageTk
import urllib.request
import io
import os
import json
# APIキーをグローバル変数として設定
API_KEY = "YOUR_OpenWeatherMap_API_JEY" # ここにあなたのオープンウェザーマップで発行されたAPIキーを設定してください。
# 選択された都市名をグローバル変数として定義
selected_city = "Tokyo"
# アイコンを保存するローカルフォルダのパス
ICON_FOLDER = "icons"
# 天気データを保存するテキストファイルのパス
DATA_FILE = "weather_data.json"
day_name = ""
def get_weather_forecast(city_name, country_code):
"""OpenWeatherMap APIから天気予報を取得し、30時間分のデータを表示する"""
url = f"http://api.openweathermap.org/data/2.5/forecast?q={city_name},{country_code}&appid={API_KEY}&units=metric&lang=ja"
try:
response = requests.get(url)
response.raise_for_status() # エラーレスポンスを例外として処理
data = response.json()
forecast_list = data["list"]
weather_data = [] # 天気予報データを格納するリスト
now = datetime.now()
end_time = now + timedelta(hours=30) # 30時間後の日時
for forecast in forecast_list:
forecast_time = datetime.fromtimestamp(forecast["dt"])
if now <= forecast_time <= end_time:
day_name = calendar.day_abbr[forecast_time.weekday()] #曜日を日本語の漢字一文字で取得
weather_data.append({
"time": forecast_time.strftime(f'%d({day_name})%H:%M'), #日・曜日・時間
"description": forecast['weather'][0]['description'],
"icon": forecast['weather'][0]['icon'], # 天気アイコンのIDを追加
"temp": forecast['main']['temp'],
"humidity": forecast['main']['humidity'],
"wind_speed": forecast['wind']['speed'],
"pressure": forecast['main']['pressure'],
"rain": forecast.get("rain", {}).get("3h", 0)
})
#日の出、日の入りの時刻の追加
sunrise = datetime.fromtimestamp(data['city']['sunrise']).strftime('%H:%M')
sunset = datetime.fromtimestamp(data['city']['sunset']).strftime('%H:%M')
return data['city']['name'], weather_data, sunrise, sunset
except requests.exceptions.RequestException as e:
print(f"天気情報の取得に失敗しました: {e}")
return None, None, None, None
def save_weather_data(city_name, weather_data, sunrise, sunset):
"""天気データをテキストファイルに保存する"""
data = {
"city_name": city_name,
"weather_data": weather_data,
"sunrise": sunrise,
"sunset": sunset,
"timestamp": datetime.now().isoformat()
}
with open(DATA_FILE, "w") as f:
json.dump(data, f)
def load_weather_data():
"""テキストファイルから天気データを読み込む"""
if os.path.exists(DATA_FILE):
with open(DATA_FILE, "r") as f:
data = json.load(f)
return data["city_name"], data["weather_data"], data["sunrise"], data["sunset"], data["timestamp"]
else:
return None, None, None, None, None
def update_time(time_label):
"""現在時刻を更新する"""
now = datetime.now()
now_str = now.strftime("%Y年%m月%d日(%a) %H:%M:%S") # 年月日曜日 時分秒
time_label.config(text=now_str)
time_label.after(1000, lambda: update_time(time_label)) # 更新間隔を1秒に設定
def display_weather_forecast(city_name, weather_data, root, sunrise, sunset, timestamp=None):
"""天気予報をグラフィカルに表示する"""
# time_labelを除いてウィジェットを削除
for widget in root.winfo_children():
if widget.winfo_name() != "button_frame2" and widget.winfo_name() != "city_button_frame" and widget.winfo_name() != "data_time_label" and widget.winfo_name() != "info_frame" and widget.winfo_name() != "time_label":
widget.destroy()
root.title(f"{city_name}の天気予報")
# 現在時刻を表示するラベル
time_label = tk.Label(root, font=("", 22), name="time_label")
time_label.pack(side=tk.TOP, anchor=tk.CENTER, padx=1, pady=1)
update_time(time_label)
# 情報表示フレーム
info_frame = tk.Frame(root, name="info_frame")
info_frame.pack(side=tk.TOP, fill=tk.X, padx=1, pady=1)
# 都市名、日の出、日の入り、データ取得時刻
city_label = tk.Label(info_frame, text=f"{city_name}")
city_label.pack(side=tk.LEFT)
sunrise_label = tk.Label(info_frame, text=f"(日の出: {sunrise}、")
sunrise_label.pack(side=tk.LEFT)
sunset_label = tk.Label(info_frame, text=f"日の入り: {sunset})")
sunset_label.pack(side=tk.LEFT)
if timestamp:
data_time_label = tk.Label(info_frame, text=f"データ取得時刻: {timestamp}", name="data_time_label")
else:
data_time_label = tk.Label(info_frame, text=f"データ取得時刻: {datetime.now().strftime('%H:%M:%S')}", name="data_time_label")
data_time_label.pack(side=tk.RIGHT)
# Treeviewを作成して天気予報データを表示
style = ttk.Style()
style.configure("Treeview.Heading", font=("", 8)) #項目行の文字サイズ
#style.configure("Treeview", font=("", 15)) #データ行の文字サイズを14ポイントに変更
tree = ttk.Treeview(root, columns=(1, 2, 3, 4, 5, 6, 7), show='headings') # 列数を7に変更
tree.heading(1, text='日時')
tree.heading(2, text='天気')
tree.heading(3, text='気温(℃)')
tree.heading(4, text='湿度(%)')
tree.heading(5, text='風速(m/s)')
tree.heading(6, text='気圧(hPa)')
tree.heading(7, text='降水量(mm)')
# 列の幅を調整
tree.column(1, width=90, anchor='w') #日時列の幅と左寄せ
tree.column(2, width=45, anchor='c') # 天気列の幅を調整
tree.column(3, width=15, anchor='e') #右寄せ
tree.column(4, width=10, anchor='c')
tree.column(5, width=12, anchor='e')
tree.column(6, width=10, anchor='e')
tree.column(7, width=10, anchor='e')
#日時列だけ文字サイズを9ポイントにする
style.configure("Treeview", font=("", 12), column=1)
# 10行のみ表示
for i, data in enumerate(weather_data): #enumerateでインデックスを取得
if i >= 10:
break
#行ごとに背景色を変更
if i % 2 == 0:
tag = "even"
else:
tag = "odd"
tree.insert("", "end", values=(
data["time"],
data["description"],
data["temp"],
data["humidity"],
data["wind_speed"],
data["pressure"],
data["rain"]
), tags=(tag,))
#最初の行を選択状態にする
if weather_data:
tree.selection_set(tree.get_children()[0])
#タグの設定
tree.tag_configure("even", background="#f0f0f0") #偶数行の背景色
tree.tag_configure("odd", background="#ffffff") #奇数行の背景色
tree.pack(fill='x', expand=True, padx=0, pady=0) #余白をなくす
# 都市切り替えボタンと終了・シャットダウンボタンを横並びにするフレーム
button_frame = tk.Frame(root)
button_frame.pack(side=tk.BOTTOM, fill=tk.X)
# 都市切り替えのボタン
city_button_frame = tk.Frame(button_frame, name="city_button_frame") #ボタンをまとめるフレーム
city_button_frame.pack(side=tk.LEFT, padx=1, pady=1)
city_list = {"東京": "Tokyo", "富津": "Futtsu", "小浜": "Obama", "神戸": "Kobe", "岸和田": "Kishiwada"}
for city_jp, city_en in city_list.items():
city_button = tk.Button(city_button_frame, text=city_jp, command=lambda c=city_en: change_city(c, root))
city_button.pack(side=tk.LEFT, padx=1, pady=1)
# 終了ボタンとシャットダウンボタン
button_frame2 = tk.Frame(button_frame, name="button_frame2")
button_frame2.pack(side=tk.RIGHT, padx=1, pady=1)
exit_button = tk.Button(button_frame2, text="終了", command=root.destroy, bg="blue", fg="white")
exit_button.pack(side=tk.LEFT, padx=1, pady=1)
shutdown_button = tk.Button(button_frame2, text="Shutdown", command=shutdown, bg="red", fg="white")
shutdown_button.pack(side=tk.LEFT, padx=1, pady=1)
def change_city(city_name, root):
"""都市を切り替える"""
global selected_city
selected_city = city_name #グローバル変数を更新
country_code = "JP" #国コードは日本で固定
city, weather_data, sunrise, sunset = get_weather_forecast(city_name, country_code)
if city and weather_data:
save_weather_data(city, weather_data, sunrise, sunset) # 天気データを保存
display_weather_forecast(city, weather_data, root, sunrise, sunset)
#1時間ごとに都市情報を更新
root.after(3600000, lambda: change_city(selected_city, root))
else:
# インターネットからの情報更新に失敗した場合、保存されたデータを読み込んで表示
city, weather_data, sunrise, sunset, timestamp = load_weather_data()
if city and weather_data:
display_weather_forecast(city, weather_data, root, sunrise, sunset, timestamp)
def shutdown():
"""シャットダウンを実行する"""
try:
subprocess.run(["sudo", "shutdown", "-h", "now"], check=True)
except subprocess.CalledProcessError as e:
print(f"シャットダウンに失敗しました: {e}")
except FileNotFoundError:
print("sudoコマンドが見つかりませんでした。")
if __name__ == "__main__":
root = tk.Tk()
#root.attributes('-fullscreen', True) # 全画面表示
root.geometry("480x320") #ウィンドウサイズの設定をコメントアウト
# 保存された天気データを読み込み、表示
city, weather_data, sunrise, sunset, timestamp = load_weather_data()
if city and weather_data:
selected_city = city # 保存された都市名を使用
display_weather_forecast(city, weather_data, root, sunrise, sunset, timestamp)
# インターネットから天気情報を更新
city_name = selected_city # 初期都市
country_code = "JP" # 国コード
city, weather_data, sunrise, sunset = get_weather_forecast(city_name, country_code) #ここでcityとweather_dataを初期化
if city and weather_data:
save_weather_data(city, weather_data, sunrise, sunset) # 天気データを保存
display_weather_forecast(city, weather_data, root, sunrise, sunset)
#1時間ごとに都市情報を更新
root.after(3600000, lambda: change_city(selected_city, root))
else:
print("天気情報を取得できませんでした。")
root.mainloop()