お父にゃんの電子工作

お父にゃんの電子工作

暇なおじさんが、電子工作(主にラジオ製作)をして勝手な感想を書く

前前回に引き続きCYDで何か表示するものを作ってみたい。

 

今回は、気象情報(天気予報)を表示させてみることにする。

 

GoogleのAIモードでどうすれば良いか質問。

(ちょっと質問するだけならGoogleのAIモードが的確で使いやすいと思っている。)

すると、OpenWeatherMapOpen-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) +
             "&current=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 -> ------------------------------
 
(緯度と経度を、お住いのところに修正すると同じように表示できるので お試しを)
 
しかし見た目は良いのだが、風速は11m/sも吹いてないし、 北西の風でもない。気象庁のデータと乖離しているのである。予報の気温もかなりでたらめな感じなのである。
ChatGPTに「そこんとこ、どうなの?」と聞いたら 
「Open-Meteo は 数値予報モデルの値(予測値・平均化された値) であって気象庁アメダスとズレることは普通に起こります」
とのこと。これはOpenWeatherMapを使っても傾向は同じらしいのである。
 

むむむ・・・。良く考えれば無料のサービスに過度な期待をしてはいけないのである。

まぁ、天気の傾向だけ分かれば良いということで、天気予報のみ採用して表示することにする。

 

で、こんな感じで表示させてみた。

 

う~ん、なんか見た目がいまいちな感じがするが、とりあえず良しとする。

いずれまた修正すると思う。

 

今回のスケッチは以下に置いておきますので、興味のある方はどうぞ。

 

CYD_terminal_v02

 

なお、コンパイル時は「ツール」→「Partition Scheme」をHuge APPに設定。

 

 

「見た目は大まかな感じで大丈夫なのニャ」

あんたは、大まかすぎる。