前前回に引き続きCYDで何か表示するものを作ってみたい。
今回は、気象情報(天気予報)を表示させてみることにする。
GoogleのAIモードでどうすれば良いか質問。
(ちょっと質問するだけならGoogleのAIモードが的確で使いやすいと思っている。)
すると、OpenWeatherMap か Open-Meteoを使うと良い、というお答え。
今回は、登録不要で使える Open-Meteoを使ってみる。
ChatGPTと何度かやり取りして、以下のサンプルプログラムを書いてもらった。
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
const char* ssid = "your-ssid";
const char* password = "your-password";
// 松山市
const float LAT = 33.5011; //緯度
const float LON = 132.4614; //経度
// hourly+daily(最低/最高気温)
String url = "https://api.open-meteo.com/v1/forecast?"
"latitude=" + String(LAT, 4) +
"&longitude=" + String(LON, 4) +
"¤t=temperature_2m,relative_humidity_2m,wind_speed_10m,wind_direction_10m"
"&hourly=weathercode"
"&daily=temperature_2m_min,temperature_2m_max"
"&timezone=Asia/Tokyo";
// ---- 天気コード判定 ----
bool isSunny(int code) {
return (code == 0);
}
bool isCloudy(int code) {
return (code >= 1 && code <= 3) || (code >= 45 && code <= 48);
}
bool isRain(int code) {
return (code >= 51 && code <= 67) || (code >= 80 && code <= 82) || (code >= 95);
}
bool isSnow(int code) {
return (code >= 71 && code <= 77) || (code == 85 || code == 86);
}
// 24時間分を見て「雨のち晴れ」「くもりのち雪」などを作る
String makeForecastFromHourlyRange(JsonArray codes, int startIndex) {
bool morningRain = false;
bool morningSunny = false;
bool morningCloudy = false;
bool morningSnow = false;
bool afternoonRain = false;
bool afternoonSunny = false;
bool afternoonCloudy = false;
bool afternoonSnow = false;
// 午前(0~11時)
for (int i = startIndex; i < startIndex + 12; i++) {
int code = codes[i];
if (isRain(code)) morningRain = true;
if (isSunny(code)) morningSunny = true;
if (isCloudy(code)) morningCloudy = true;
if (isSnow(code)) morningSnow = true;
}
// 午後(12~23時)
for (int i = startIndex + 12; i < startIndex + 24; i++) {
int code = codes[i];
if (isRain(code)) afternoonRain = true;
if (isSunny(code)) afternoonSunny = true;
if (isCloudy(code)) afternoonCloudy = true;
if (isSnow(code)) afternoonSnow = true;
}
// 午前の代表天気(優先度:雪 → 雨 → 晴れ → くもり)
String morning;
if (morningSnow) morning = "雪";
else if (morningRain) morning = "雨";
else if (morningSunny) morning = "晴れ";
else if (morningCloudy) morning = "くもり";
else morning = "不明";
// 午後の代表天気
String afternoon;
if (afternoonSnow) afternoon = "雪";
else if (afternoonRain) afternoon = "雨";
else if (afternoonSunny) afternoon = "晴れ";
else if (afternoonCloudy) afternoon = "くもり";
else afternoon = "不明";
if (morning == afternoon) {
return morning;
} else {
return morning + "のち" + afternoon;
}
}
String windDirectionToText(int deg) {
static const char* dirTable[16] = {
"北", "北北東", "北東", "東北東",
"東", "東南東", "南東", "南南東",
"南", "南南西", "南西", "西南西",
"西", "西北西", "北西", "北北西"
};
// 360度を16分割(1区画 = 22.5度)
int index = (int)((deg + 11.25) / 22.5) % 16;
return String(dirTable[index]);
}
void setup() {
Serial.begin(115200);
delay(1000);
WiFi.begin(ssid, password);
Serial.print("WiFi接続中");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi接続完了");
}
void loop() {
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
http.begin(url);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
JsonDocument doc;
DeserializationError error = deserializeJson(doc, payload);
if (!error) {
// 現在の天気
float temp = doc["current"]["temperature_2m"];
int humidity = doc["current"]["relative_humidity_2m"];
float windSpeed = doc["current"]["wind_speed_10m"];
int windDir = doc["current"]["wind_direction_10m"];
// hourly 天気コード
JsonArray hourly = doc["hourly"]["weathercode"];
// daily 最低・最高気温
JsonArray tmin = doc["daily"]["temperature_2m_min"];
JsonArray tmax = doc["daily"]["temperature_2m_max"];
// 今日・明日の天気
String todayForecast = makeForecastFromHourlyRange(hourly, 0);
String tomorrowForecast = makeForecastFromHourlyRange(hourly, 24);
float todayMin = tmin[0];
float todayMax = tmax[0];
float tomorrowMin = tmin[1];
float tomorrowMax = tmax[1];
Serial.println("------ 松山市の気象情報 ------");
Serial.printf("現在の気温: %.1f ℃\n", temp);
Serial.printf("湿度: %d %%\n", humidity);
Serial.printf("風速: %.1f m/s\n", windSpeed);
String windDirText = windDirectionToText(windDir);
Serial.printf("風向: %s\n", windDirText.c_str());
Serial.println();
Serial.println("天気予報:");
Serial.printf("今日: %s (最低 %.1f ℃ / 最高 %.1f ℃)\n",
todayForecast.c_str(), todayMin, todayMax);
Serial.printf("明日: %s (最低 %.1f ℃ / 最高 %.1f ℃)\n",
tomorrowForecast.c_str(), tomorrowMin, tomorrowMax);
Serial.println("------------------------------\n");
} else {
Serial.println("JSON解析エラー");
}
} else {
Serial.printf("HTTPエラー: %d\n", httpCode);
}
http.end();
}
// 15分ごと更新
delay(15 * 60 * 1000);
}
ちなみにPCを新しくしたら、コンパイル時間が劇的に短くなった。
前のPCだと、WiFiを含めるとすごく時間がかかっていたが、とても快適。
シリアルモニタでこんな感じで表示された。
21:48:20.485 -> ------ 松山市の気象情報 ------
21:48:20.526 -> 現在の気温: 3.3 ℃
21:48:20.526 -> 湿度: 67 %
21:48:20.526 -> 風速: 11.6 m/s
21:48:20.526 -> 風向: 北西
21:48:20.526 ->
21:48:20.526 -> 天気予報:
21:48:20.526 -> 今日: 晴れ (最低 2.5 ℃ / 最高 5.7 ℃)
21:48:20.526 -> 明日: 晴れ (最低 2.0 ℃ / 最高 4.6 ℃)
21:48:20.526 -> ------------------------------
21:48:20.526 -> 現在の気温: 3.3 ℃
21:48:20.526 -> 湿度: 67 %
21:48:20.526 -> 風速: 11.6 m/s
21:48:20.526 -> 風向: 北西
21:48:20.526 ->
21:48:20.526 -> 天気予報:
21:48:20.526 -> 今日: 晴れ (最低 2.5 ℃ / 最高 5.7 ℃)
21:48:20.526 -> 明日: 晴れ (最低 2.0 ℃ / 最高 4.6 ℃)
21:48:20.526 -> ------------------------------
(緯度と経度を、お住いのところに修正すると同じように表示できるので お試しを)
しかし見た目は良いのだが、風速は11m/sも吹いてないし、 北西の風でもない。気象庁のデータと乖離しているのである。予報の気温もかなりでたらめな感じなのである。
ChatGPTに「そこんとこ、どうなの?」と聞いたら
「Open-Meteo は 数値予報モデルの値(予測値・平均化された値) であって気象庁アメダスとズレることは普通に起こります」
とのこと。これはOpenWeatherMapを使っても傾向は同じらしいのである。
むむむ・・・。良く考えれば無料のサービスに過度な期待をしてはいけないのである。
まぁ、天気の傾向だけ分かれば良いということで、天気予報のみ採用して表示することにする。
で、こんな感じで表示させてみた。
う~ん、なんか見た目がいまいちな感じがするが、とりあえず良しとする。
いずれまた修正すると思う。
今回のスケッチは以下に置いておきますので、興味のある方はどうぞ。
なお、コンパイル時は「ツール」→「Partition Scheme」をHuge APPに設定。
「見た目は大まかな感じで大丈夫なのニャ」
あんたは、大まかすぎる。


