完成品
気象庁から天気予報データを取得して
今日、明日の天気、最高気温、最低気温、降水確率
を表示します
古いラズパイを久々に起動したら...
今回使った Raspberry Pi の OS は jessie
古いバージョンなので、サポートが終了しています
$ sudo apt-get update
を実行したところ、aptの参照先が無くなっていてエラー発生
/etc/apt/sources.list
を書き換えて対応しました
pi@raspberrypi:~ $ sudo cat /etc/apt/sources.list
deb http://legacy.raspbian.org/raspbian/ jessie main contrib non-free rpi
LCDをつなげる
買ったもの
ラズパイから制御できる LCDキャラクタディスプレイモジュール を買いました
以下の条件で探してAmazonで購入
- 20文字 x 4行
- I2CシリアルIFモジュールつき
- バックライトつき
949円でした
結線
ジャンパワイヤーで、以下のように結線します
I2CシリアルIFモジュール | Raspberry Pi |
---|---|
GND | PIN6 (GND) |
VCC | PIN4 (5V) |
SDA | PIN3 (GPIO2) |
SCL | PIN5 (GPIO3) |
ドライバ、ライブラリ、テストコード
TheRaspberryPiGuy氏のLCD制御プログラムを利用しました
$ git clone https://github.com/the-raspberry-pi-gui/lcd.git
$ cd lcd/
$ sudo sh install.sh
$ python demo_clock.py
無事ディスプレイに時計が表示されました
I2CシリアルIFモジュール と TheRaspberryPiGuy氏 優秀すぎ
試行錯誤を覚悟していたのですが、電子工作もプログラミングも不要でした
カタカナの表示
Extended Strings を利用すると半角カナが表示できます
UTF-8のコードと比較して、簡単な変換関数を作りました
UTF-8 : 半角カナ
https://orange-factory.com/sample/utf8/code3/ef.html#HalfwidthandFullwidthForms
ヲ : 0xEF 0xBD 0xA6
タ : 0xEF 0xBE 0x80lcd : Extended string code
https://github.com/the-raspberry-pi-guy/lcd/blob/master/README.md#extended-strings
ヲ : 0xA6 ( + 0x00 )
タ : 0xC0 ( + 0x40 )
def convert_to_code(input_string):
char_codes = []
for char in input_string:
c = ord(char) & 0xFF
c += 0x40 if c < 0xA0 else 0
char_code = "{{0x{:02X}}}".format(c)
char_codes.append(char_code)
return "".join(char_codes[2::3])
ヒヤヒヤするコードですね
ロバスト性皆無なので、絶対に半角カナ以外入れちゃだめだぞ~
demo_clock.py
を改造して、こんな感じ (↓) でカタカナの表示に成功しました
天気予報の取得
気象庁のURLとjson
天気予報は気象庁が提供するjsonを利用します
このようなURLからjsonがとれます。素晴らしいです
https://www.jma.go.jp/bosai/forecast/data/forecast/130000.json
jsonの例です
[
{
"publishingOffice": "気象庁",
"reportDatetime": "2023-07-26T17:00:00+09:00",
"timeSeries": [
{
"timeDefines": [
"2023-07-26T17:00:00+09:00",
"2023-07-27T00:00:00+09:00",
"2023-07-28T00:00:00+09:00"
],
"areas": [
{
"area": {
"name": "東京地方",
"code": "130010"
},
"weatherCodes": [
"100",
"100",
"100"
],
"weathers": [
"晴れ",
"晴れ 多摩西部 では 昼過ぎ から 雨 で 雷を伴う",
"晴れ"
],
weatherCodes
と weathers
に天気情報が入っています
weathers
は日本語かつ長い(ときもある)ので、weatherCodes
を使います
気象庁のjsonについては以下のblogなどを参考にしました
天気コードと "読み" への変換
weatherCodes
は天気に対応していて、例えば 100 は 晴れ です
対応表は、 Forcast.Const.TELOPS
を気象庁の天気予報のページから入手して
手元でそれを変換して使いました
詳しくはこちらの記事を参照のこと
手順
-
Forcast.Const.TELOPS
をローカルにファイル保存 - 読みを付加
- 全角カタカナを半角カタカナに置換 (by 秀丸エディタ)
-
TELOPS_hankaku_kana.csv
というファイル名で保存
プログラム・コード
お天気ディスプレイのコードです
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# Weather information program.
# Demo program for the I2C 20x4 Display from Ryanteck.uk
# Created by https://github.com/Tomonobu3110
# Import necessary libraries for communication and display use
## for weather data
import requests
import json
import csv
import unicodedata
## for LCD display
import drivers
from time import sleep
from datetime import datetime
# Convart charactor code
# from Hankaku Kana (UTF-8) to extended string code
# utf-8 : https://orange-factory.com/sample/utf8/code3/ef.html#HalfwidthandFullwidthForms
# extended string : https://github.com/the-raspberry-pi-guy/lcd/blob/master/README.md#extended-strings
def convert_to_code(input_string):
char_codes = []
for char in input_string:
c = ord(char) & 0xFF
c += 0x40 if c < 0xA0 else 0
char_code = "{{0x{:02X}}}".format(c)
char_codes.append(char_code)
return "".join(char_codes[2::3]) ## Python syntax : array[start:stop:step]
# Get json from specified URL
def get_json_from_url(url):
try:
response = requests.get(url)
response.raise_for_status() # error check
return response.json()
except requests.exceptions.RequestException as e:
print("Error during request: {}".format(e))
return None
except json.JSONDecodeError as e:
print("Error parsing JSON: {}".format(e))
return None
# open TELOPS dictionary
def read_csv_dict(csv_file):
data_dict = {} # 空の辞書を作成
with open(csv_file, 'rb') as file:
csv_reader = csv.reader(file) # ファイルハンドルからreaderオブジェクトを作成
for row in csv_reader: # 各行に対してループ
key = int(row[0]) # 各行の最初の数をキーとして取得
data_dict[key] = row[1:] # キーと値を辞書に追加
return data_dict
def get_date_index(json, datetime):
if datetime[0:10] == json["today"]["date"][0:10]:
return "today"
if datetime[0:10] == json["tomorrow"]["date"][0:10]:
return "tomorrow"
return "undef"
def get_time_index(datetime):
return int(datetime[11:13]) / 6
def get_minmax_index(datetime):
if datetime[11:13] == "00":
return "min"
if datetime[11:13] == "09":
return "max"
return "undef"
def get_disp_str(value):
if -1 == value:
return "-"
return str(value)
# Load the driver and set it to "display"
# If you use something from the driver library use the "display." prefix first
display = drivers.Lcd()
yomi_dict = read_csv_dict("TELOPS_hankaku_kana.csv")
# display weather information
try:
while True:
# get weather information
url = "https://www.jma.go.jp/bosai/forecast/data/forecast/130000.json"
json_data = get_json_from_url(url)
if json_data:
try:
# (1) Weather
weather_times = json_data[0]["timeSeries"][0]["timeDefines"]
weather_areas = json_data[0]["timeSeries"][0]["areas"]
for area in weather_areas:
if int(area["area"]["code"]) == 130010: # 東京地方 130010
weather_codes = area["weatherCodes"]
dates = [s[8:10] for s in weather_times]
weather = [convert_to_code(yomi_dict[int(c)][1]) for c in weather_codes]
# (2) Rainy percent
rainy_times = json_data[0]["timeSeries"][1]["timeDefines"]
rainy_areas = json_data[0]["timeSeries"][1]["areas"]
for area in rainy_areas:
if int(area["area"]["code"]) == 130010: # 東京地方 130010
rainy_pops = area["pops"]
# (3) Temperature
temp_times = json_data[0]["timeSeries"][2]["timeDefines"]
temp_areas = json_data[0]["timeSeries"][2]["areas"]
for area in temp_areas:
if int(area["area"]["code"]) == 44132: # 東京 44132
temp_temps = area["temps"]
except KeyError:
print("The specified path does not exist in the JSON data.")
# template of weather data
weather_data = '''\
{
"today": {
"date": "2023-01-01",
"code": 0,
"pops": [
-1,
-1,
-1,
-1
],
"temps": {
"min": -1,
"max": -1
}
},
"tomorrow": {
"date": "2023-01-02",
"code": 0,
"pops": [
-1,
-1,
-1,
-1
],
"temps": {
"min": -1,
"max": -1
}
}
}
'''
# conver to intermidiate data format
weather_json = json.loads(weather_data)
weather_json["today"]["date"] = weather_times[0][0:10]
weather_json["today"]["code"] = int(weather_codes[0])
weather_json["tomorrow"]["date"] = weather_times[1][0:10]
weather_json["tomorrow"]["code"] = int(weather_codes[1])
for i, dt in enumerate(rainy_times):
weather_json[get_date_index(weather_json, dt)]["pops"][get_time_index(dt)] = int(rainy_pops[i])
for i, dt in enumerate(temp_times):
weather_json[get_date_index(weather_json, dt)]["temps"][get_minmax_index(dt)] = int(temp_temps[i])
if weather_json["today"]["temps"]["min"] == weather_json["today"]["temps"]["max"]:
weather_json["today"]["temps"]["min"] = -1
#print(weather_json)
print("Writing to display")
display.lcd_display_extended_string(weather_json["today"] ["date"][8:10] + convert_to_code("ニチ") + " " + convert_to_code(yomi_dict[weather_json["today"] ["code"]][1]), 1)
display.lcd_display_extended_string(weather_json["tomorrow"]["date"][8:10] + convert_to_code("ニチ") + " " + convert_to_code(yomi_dict[weather_json["tomorrow"]["code"]][1]), 2)
temp_str = convert_to_code("キオン") + " "
temp_str += get_disp_str(weather_json["today"]["temps"]["min"]) + " "
temp_str += get_disp_str(weather_json["today"]["temps"]["max"]) + " | "
temp_str += get_disp_str(weather_json["tomorrow"]["temps"]["min"]) + " "
temp_str += get_disp_str(weather_json["tomorrow"]["temps"]["max"]) + " "
display.lcd_display_extended_string(temp_str, 3)
pops_str = convert_to_code("アメ") + " "
pops_str += get_disp_str(max(weather_json["today"]["pops"])) + "% | "
pops_str += get_disp_str(max(weather_json["tomorrow"]["pops"])) + "%"
display.lcd_display_extended_string(pops_str, 4)
# wait 5 min
sleep(5 * 60)
display.lcd_clear()
except KeyboardInterrupt:
# If there is a KeyboardInterrupt (when you press ctrl+c), exit the program and cleanup
print("Cleaning up!")
display.lcd_clear()
中間形式のjsonのフォーマットをもうちょっと賢く作ればよかったかな...
ほかにも突っ込みどころ満載ですが、とりあえず動いているのでよしとします
自動起動の設定
ラズパイ起動時に自動的にスクリプトを動作させます
ほぼ、公式のREADME.md
通り
https://github.com/the-raspberry-pi-guy/lcd/blob/master/README.md#systemd
一点だけ WorkingDirectory
を足しました
serviceファイルの作成
$ sudo nano /lib/systemd/system/rpi-lcd.service
serviceファイルの内容
[Unit]
Description=RPi Python script for a 16x2 LCD
[Service]
Type=simple
## Edit the following according to the script permissions
User=pi
#Group=users
## Working directory
WorkingDirectory=/home/pi/lcd
## Edit the following with the full path to the compatible Python version and your script
ExecStart=/usr/bin/python /home/pi/lcd/demo_tenki.py
Restart=always
RestartSec=5
KillMode=process
KillSignal=SIGINT
[Install]
WantedBy=multi-user.target
サービスの有効化
$ sudo systemctl enable rpi-lcd.service
$ sudo systemctl start rpi-lcd.service
以上です