04.Unifiedで気象庁3日予報表示


04.Unifiedで気象庁3日予報表示

気象庁の天気予報で3日予報を表示します。3日予報と7日予報が有りますが、webで表示されている項目すべてを表示し、明後日で3日予報の不足分は7日予報から追加します。
M5Unifiedを使用しましたが画面サイズは320x240が必要です。確認はM5Stack Basicのみです。
また、ArduinoJsonがVer7になっていました。使い方がVer6と少し違うみたいです。Arduino-IDEはVer2.3.3です。

5時発表

・3日予報

・7日予報

・今日

・明日

・明後日

11時発表

・3日予報

・7日予報

・今日

・明日

・明後日

17時発表

・3日予報

・7日予報

・今日

・明日

・明後日

気象庁の天気予報のweb表示

横浜市の場合
https://www.jma.go.jp/bosai/forecast/#area_type=class20s&area_code=1410000
です。

予報項目

発表時・発表気象台・天気マーク・天気文・風・波・降水確率・気温

発表時刻

05・11・17時
* これ以外の時刻で追加発表することもあります。

発表時刻による表示日数の違い

3日予報

・05時発表は明日までの2日表示
・11,17時は2日後までの3日表示

7日予報

・05時発表は6日後までの7日表示
・11,17時は 7日後までの8日表示

jsonの内容

https://www.jma.go.jp/bosai/forecast/data/forecast/140000.json
* json表記はweb表示内容と異なるところがあります。

3日予報の発表時刻

05:05時
11:11時
17:17時

3日予報の天気コード・天気文・風・波

05:2時刻 今日05時,明日00時,明後日無し *明後日のweb表示無し
11:3時刻 今日11時,明日00時,明後日00時 *明後日のweb表示有り
17:3時刻 今日17時,明日00時,明後日00時

3日予報の降水確率

05:7時刻 今日--,06,12,18,明日00,06,12,18 *明後日のweb表示無し
11:6時刻 今日--,--,12,18,明日00,06,12,18 *明後日のweb表示有り
17:5時刻 今日--,--,--,18,明日00,06,12,18
* 00~06,06~12,12~18,18~24時の4区分ですが、どれも00-06時の予報はありません。

3日予報の温度

05:4時刻 今日09,00,明日00,09 *明後日のweb表示無し
11:4時刻 今日09,00,明日00,09 *明後日のweb表示有り
17:2時刻 今日--,--,明日00,09
* 朝の最低気温,日中の最高気温です。05と11時の今日の順番が逆です。しかし、両方とも最高気温値で、webでは最低気温は"-"の表示です。

7日予報の発表時刻

05:前日の17時。web表示では05時です。
11:11時
17:17時

7日予報の天気マーク

05:7時刻 0-6日後
11:7時刻 1-7日後 webでは0日後(今日)も追加表示しています。
17:上と同じ

7日予報の降水確率

05:7時刻 0-6日後 0日後は"" (スケッチでは1日後はこの値を使用せず3日予報を使用)
11:7時刻 1-7日後 1日後は""ですがwebでは3日予報値を表示しています。
17:上と同じ

7日予報の信頼度

05:7時刻 0-6日後 *0と1日後は""となっています。
11:7時刻 1-7日後 *1と2日後は""となっています。
17:上と同じ

7日予報の温度

05:7時刻 0-6日後 0日後は"" (スケッチでは1日後はこの値を使用せず3日予報を使用)
11:7時刻 1-7日後 1日後は""となっています。
17:上と同じ
* 最高気温と下限と上限,最低気温と下限と上限範囲の6温度

7日予報で平年の最低気温,最高気温

05:各1点
11:同じ
17:同じ

7日予報の平年の7日間降水量の最低と最高mm

05:各1点
11:同じ
17:同じ
* 表示は小数点以下を四捨五入しています。

3日表示するには

・天気コード・天気文・風・波
 05時の明後日は7日予報から取得するが、風と波は無し。
 それ以外はあります。

・降水確率 (00-06,06-12,12-18,18-24)
 今日+明日で05時発表は7点、11時は6点、17時は5点なので今日の範囲時刻とずれないようにします。
 明後日は、7日予報から取得するが1日1点表記です。

・気温 (朝の最低,日中の最高)
 今日の05時と11時は朝の最低気温はありません。
 今日の17時は今日の温度はありません。
 明日の表示はあります。
 明後日は7日予報から取得します。

天気コードから天気文

7日予報は天気文が無く、天気コードだけなので、Chromeで
気象庁の天気予報 > F12(デベロッパーツール) > コンソールの ">" の後に Forecast.Const.TELOPS と手入力 > Enter
表示された文字を、""で文字列化、引用符を二重に、予報の英文を削除 等に修正してjsonを作成し、スケッチ内で天気コードから天気文を表示します。

スケッチ説明

・標準ではメモリ不足なので、"Large APP"に変更しました。
・天気予報のwifiは3入力から信号の一番強いものを自動選択(WiFiMulti)します。
・曜日は、年月日から計算します。(ツェラーの公式)
・発表時刻・天気文・風・波は文字変換して短く表示します。
・画面一番下に日付バーを表示します。(左:今日、中央:明日、右:明後日)
・Aキーで前日、Bキーで翌日を表示。端の日まで行くと表示はループします。
・天気と風は2行とし、その他はは1行とします。
・jsonで[があったら0からの数字が入ります。

・文字サイズは、標準で24を使用しました。
・画面サイズは、320x240のみです。

1行目 :24x0.8x1行=Δ19.2
2-10行目:24x1.0x9行=Δ216
日付表示: Δ3
y軸計Δ238

スケッチ

*****は自分のwifiのssidとパスワードを記入します。

// 気象庁の天気予報 3日表示
// M5Stack Basic(320x240), ツール > "Large APP"に変更
#include <M5Unified.h>    // M5Unifiedライブラリを使用
#include <ArduinoJson.h>  // jsonを使用 Ver7.2.0
#include <WiFi.h>        // wifiを使用
#include <WiFiMulti.h>   // 複数のwifiのssid入力から選択
WiFiMulti wifiMulti;     // 定義
#include <HTTPClient.h>  // Webにリクエストを送り応答を受取る
HTTPClient https;        // 定義
const char *weatherJson =
  "https://www.jma.go.jp/bosai/forecast/data/forecast/140000.json";  // 神奈川県の天気予報
// 神奈川県web表示 https://www.jma.go.jp/bosai/forecast/#area_type=offices&area_code=140000
// 横浜市のweb表示 https://www.jma.go.jp/bosai/forecast/#area_type=class20s&area_code=1410000
// 発表時刻 AM5, AM11, PM5の3回
// 神奈川県 東部 横浜市の天気 webを見て決めます

// 行末まで表示できないので英文削除
const char *weatherCodeJson = "{\"101\":[\"101.svg\",\"501.svg\",\"100\",\"晴時々曇\"],\"102\":[\"102.svg\",\"502.svg\",\"300\",\"晴一時雨\"],\"103\":[\"102.svg\",\"502.svg\",\"300\",\"晴時々雨\"],\"104\":[\"104.svg\",\"504.svg\",\"400\",\"晴一時雪\"],\"105\":[\"104.svg\",\"504.svg\",\"400\",\"晴時々雪\"],\"106\":[\"102.svg\",\"502.svg\",\"300\",\"晴一時雨か雪\"],\"107\":[\"102.svg\",\"502.svg\",\"300\",\"晴時々雨か雪\"],\"108\":[\"102.svg\",\"502.svg\",\"300\",\"晴一時雨か雷雨\"],\"110\":[\"110.svg\",\"510.svg\",\"100\",\"晴後時々曇\"],\"111\":[\"110.svg\",\"510.svg\",\"100\",\"晴後曇\"],\"112\":[\"112.svg\",\"512.svg\",\"300\",\"晴後一時雨\"],\"113\":[\"112.svg\",\"512.svg\",\"300\",\"晴後時々雨\"],\"114\":[\"112.svg\",\"512.svg\",\"300\",\"晴後雨\"],\"115\":[\"115.svg\",\"515.svg\",\"400\",\"晴後一時雪\"],\"116\":[\"115.svg\",\"515.svg\",\"400\",\"晴後時々雪\"],\"117\":[\"115.svg\",\"515.svg\",\"400\",\"晴後雪\"],\"118\":[\"112.svg\",\"512.svg\",\"300\",\"晴後雨か雪\"],\"119\":[\"112.svg\",\"512.svg\",\"300\",\"晴後雨か雷雨\"],\"120\":[\"102.svg\",\"502.svg\",\"300\",\"晴朝夕一時雨\"],\"121\":[\"102.svg\",\"502.svg\",\"300\",\"晴朝の内一時雨\"],\"122\":[\"112.svg\",\"512.svg\",\"300\",\"晴夕方一時雨\"],\"123\":[\"100.svg\",\"500.svg\",\"100\",\"晴山沿い雷雨\"],\"124\":[\"100.svg\",\"500.svg\",\"100\",\"晴山沿い雪\"],\"125\":[\"112.svg\",\"512.svg\",\"300\",\"晴午後は雷雨\"],\"126\":[\"112.svg\",\"512.svg\",\"300\",\"晴昼頃から雨\"],\"127\":[\"112.svg\",\"512.svg\",\"300\",\"晴夕方から雨\"],\"128\":[\"112.svg\",\"512.svg\",\"300\",\"晴夜は雨\"],\"130\":[\"100.svg\",\"500.svg\",\"100\",\"朝の内霧後晴\"],\"131\":[\"100.svg\",\"500.svg\",\"100\",\"晴明け方霧\"],\"132\":[\"101.svg\",\"501.svg\",\"100\",\"晴朝夕曇\"],\"140\":[\"102.svg\",\"502.svg\",\"300\",\"晴時々雨で雷を伴う\"],\"160\":[\"104.svg\",\"504.svg\",\"400\",\"晴一時雪か雨\"],\"170\":[\"104.svg\",\"504.svg\",\"400\",\"晴時々雪か雨\"],\"181\":[\"115.svg\",\"515.svg\",\"400\",\"晴後雪か雨\"],\"200\":[\"200.svg\",\"200.svg\",\"200\",\"曇\"],\"201\":[\"201.svg\",\"601.svg\",\"200\",\"曇時々晴\"],\"202\":[\"202.svg\",\"202.svg\",\"300\",\"曇一時雨\"],\"203\":[\"202.svg\",\"202.svg\",\"300\",\"曇時々雨\"],\"204\":[\"204.svg\",\"204.svg\",\"400\",\"曇一時雪\"],\"205\":[\"204.svg\",\"204.svg\",\"400\",\"曇時々雪\"],\"206\":[\"202.svg\",\"202.svg\",\"300\",\"曇一時雨か雪\"],\"207\":[\"202.svg\",\"202.svg\",\"300\",\"曇時々雨か雪\"],\"208\":[\"202.svg\",\"202.svg\",\"300\",\"曇一時雨か雷雨\"],\"209\":[\"200.svg\",\"200.svg\",\"200\",\"霧\"],\"210\":[\"210.svg\",\"610.svg\",\"200\",\"曇後時々晴\"],\"211\":[\"210.svg\",\"610.svg\",\"200\",\"曇後晴\"],\"212\":[\"212.svg\",\"212.svg\",\"300\",\"曇後一時雨\"],\"213\":[\"212.svg\",\"212.svg\",\"300\",\"曇後時々雨\"],\"214\":[\"212.svg\",\"212.svg\",\"300\",\"曇後雨\"],\"215\":[\"215.svg\",\"215.svg\",\"400\",\"曇後一時雪\"],\"216\":[\"215.svg\",\"215.svg\",\"400\",\"曇後時々雪\"],\"217\":[\"215.svg\",\"215.svg\",\"400\",\"曇後雪\"],\"218\":[\"212.svg\",\"212.svg\",\"300\",\"曇後雨か雪\"],\"219\":[\"212.svg\",\"212.svg\",\"300\",\"曇後雨か雷雨\"],\"220\":[\"202.svg\",\"202.svg\",\"300\",\"曇朝夕一時雨\"],\"221\":[\"202.svg\",\"202.svg\",\"300\",\"曇朝の内一時雨\"],\"222\":[\"212.svg\",\"212.svg\",\"300\",\"曇夕方一時雨\"],\"223\":[\"201.svg\",\"601.svg\",\"200\",\"曇日中時々晴\"],\"224\":[\"212.svg\",\"212.svg\",\"300\",\"曇昼頃から雨\"],\"225\":[\"212.svg\",\"212.svg\",\"300\",\"曇夕方から雨\"],\"226\":[\"212.svg\",\"212.svg\",\"300\",\"曇夜は雨\"],\"228\":[\"215.svg\",\"215.svg\",\"400\",\"曇昼頃から雪\"],\"229\":[\"215.svg\",\"215.svg\",\"400\",\"曇夕方から雪\"],\"230\":[\"215.svg\",\"215.svg\",\"400\",\"曇夜は雪\"],\"231\":[\"200.svg\",\"200.svg\",\"200\",\"曇海上海岸は霧か霧雨\"],\"240\":[\"202.svg\",\"202.svg\",\"300\",\"曇時々雨で雷を伴う\"],\"250\":[\"204.svg\",\"204.svg\",\"400\",\"曇時々雪で雷を伴う\"],\"260\":[\"204.svg\",\"204.svg\",\"400\",\"曇一時雪か雨\"],\"270\":[\"204.svg\",\"204.svg\",\"400\",\"曇時々雪か雨\"],\"281\":[\"215.svg\",\"215.svg\",\"400\",\"曇後雪か雨\"],\"300\":[\"300.svg\",\"300.svg\",\"300\",\"雨\"],\"301\":[\"301.svg\",\"701.svg\",\"300\",\"雨時々晴\"],\"302\":[\"302.svg\",\"302.svg\",\"300\",\"雨時々止む\"],\"303\":[\"303.svg\",\"303.svg\",\"400\",\"雨時々雪\"],\"304\":[\"300.svg\",\"300.svg\",\"300\",\"雨か雪\"],\"306\":[\"300.svg\",\"300.svg\",\"300\",\"大雨\"],\"308\":[\"308.svg\",\"308.svg\",\"300\",\"雨で暴風を伴う\"],\"309\":[\"303.svg\",\"303.svg\",\"400\",\"雨一時雪\"],\"311\":[\"311.svg\",\"711.svg\",\"300\",\"雨後晴\"],\"313\":[\"313.svg\",\"313.svg\",\"300\",\"雨後曇\"],\"314\":[\"314.svg\",\"314.svg\",\"400\",\"雨後時々雪\"],\"315\":[\"314.svg\",\"314.svg\",\"400\",\"雨後雪\"],\"316\":[\"311.svg\",\"711.svg\",\"300\",\"雨か雪後晴\"],\"317\":[\"313.svg\",\"313.svg\",\"300\",\"雨か雪後曇\"],\"320\":[\"311.svg\",\"711.svg\",\"300\",\"朝の内雨後晴\"],\"321\":[\"313.svg\",\"313.svg\",\"300\",\"朝の内雨後曇\"],\"322\":[\"303.svg\",\"303.svg\",\"400\",\"雨朝晩一時雪\"],\"323\":[\"311.svg\",\"711.svg\",\"300\",\"雨昼頃から晴\"],\"324\":[\"311.svg\",\"711.svg\",\"300\",\"雨夕方から晴\"],\"325\":[\"311.svg\",\"711.svg\",\"300\",\"雨夜は晴\"],\"326\":[\"314.svg\",\"314.svg\",\"400\",\"雨夕方から雪\"],\"327\":[\"314.svg\",\"314.svg\",\"400\",\"雨夜は雪\"],\"328\":[\"300.svg\",\"300.svg\",\"300\",\"雨一時強く降る\"],\"329\":[\"300.svg\",\"300.svg\",\"300\",\"雨一時みぞれ\"],\"340\":[\"400.svg\",\"400.svg\",\"400\",\"雪か雨\"],\"350\":[\"300.svg\",\"300.svg\",\"300\",\"雨で雷を伴う\"],\"361\":[\"411.svg\",\"811.svg\",\"400\",\"雪か雨後晴\"],\"371\":[\"413.svg\",\"413.svg\",\"400\",\"雪か雨後曇\"],\"400\":[\"400.svg\",\"400.svg\",\"400\",\"雪\"],\"401\":[\"401.svg\",\"801.svg\",\"400\",\"雪時々晴\"],\"402\":[\"402.svg\",\"402.svg\",\"400\",\"雪時々止む\"],\"403\":[\"403.svg\",\"403.svg\",\"400\",\"雪時々雨\"],\"405\":[\"400.svg\",\"400.svg\",\"400\",\"大雪\"],\"406\":[\"406.svg\",\"406.svg\",\"400\",\"風雪強い\"],\"407\":[\"406.svg\",\"406.svg\",\"400\",\"暴風雪\"],\"409\":[\"403.svg\",\"403.svg\",\"400\",\"雪一時雨\"],\"411\":[\"411.svg\",\"811.svg\",\"400\",\"雪後晴\"],\"413\":[\"413.svg\",\"413.svg\",\"400\",\"雪後曇\"],\"414\":[\"414.svg\",\"414.svg\",\"400\",\"雪後雨\"],\"420\":[\"411.svg\",\"811.svg\",\"400\",\"朝の内雪後晴\"],\"421\":[\"413.svg\",\"413.svg\",\"400\",\"朝の内雪後曇\"],\"422\":[\"414.svg\",\"414.svg\",\"400\",\"雪昼頃から雨\"],\"423\":[\"414.svg\",\"414.svg\",\"400\",\"雪夕方から雨\"],\"425\":[\"400.svg\",\"400.svg\",\"400\",\"雪一時強く降る\"],\"426\":[\"400.svg\",\"400.svg\",\"400\",\"雪後みぞれ\"],\"427\":[\"400.svg\",\"400.svg\",\"400\",\"雪一時みぞれ\"],\"450\":[\"400.svg\",\"400.svg\",\"400\",\"雪で雷を伴う\"]}";

const char *day3area = "東部";              // 3日予報の地方(webより)
const char *day3city = "横浜";              // 3日予報の都市(webより)
const char *day7area = "神奈川県";          // 7日予報の地方(webより)
const char *day7city = "横浜";              // 7日予報の都市(webより)
const uint8_t days3 = 0;                    // 3日予報のjsonインデックス[0]
const uint8_t days7 = 1;                    // 7日予報のjsonインデックス[1]
const char *ssid1 = "*****";                // 自分のwifiのssid1
const char *pass1 = "*****";                // そのPW
const char *ssid2 = "*****";                // 自分のwifiのssid2
const char *pass2 = "*****";                // そのPW
const char *ssid3 = "*****";                // 自分のwifiのssid2
const char *pass3 = "*****";                // そのPW
int8_t dMenu;                               // 0:今日,1:明日,2:明後日
char dayWeek[7][4] = { "土", "日", "月",
                       "火", "水", "木", "金" };  // 曜日
static JsonDocument doc;                          // jsonのメモリ領域を確保
static JsonDocument doc2;                         // 天気コード一覧
String todayStr;                                  // 表示の年月日 検索に使用

uint8_t setWeek(uint16_t yyear, uint8_t mmonth, uint8_t dday) {
  if (mmonth < 3) {  // ツェラーの公式 年月日から曜日weekを計算
    mmonth += 12;
    yyear--;
  }
  uint16_t year1 = yyear / 100;
  uint16_t year2 = yyear % 100;
  uint8_t week = (dday + (int)(26 * (mmonth + 1) / 10) + year2 + (int)(year2 / 4)
                  + (5 * year1) + (int)(year1 / 4))
                 % 7;
  return week;
}

void SetDisplay() {                  // 初期画面設定関数
  int32_t WW = M5.Display.width();   // 画面幅を取得
  int32_t HH = M5.Display.height();  // 画面高さを取得
  if (WW == HH) {                    // 画面を横長にする
    M5.Display.setRotation(0);       // 0度回転
  } else {
    M5.Display.setRotation(1);  // 90度回転
  }
  if (HH > WW) {  // 縦長なので横:WW,縦:HHにする
    int32_t HHWW = HH;
    HH = WW;
    WW = HHWW;
  }
  M5.Display.fillScreen(TFT_BLACK);                // 全画面黒
  M5.Display.setFont(&fonts::lgfxJapanGothic_24);  // ゴシック(8,12,16,20,24,28,32,36,40)
  M5.Display.setTextSize(1);                       // テキストサイズ(倍)
  M5.Display.setBrightness(100);                   // バックライト輝度(0-255)実際は数段階
  M5.Display.setTextColor(TFT_GREEN, TFT_BLACK);   // 色設定(文字色[,背景色])
  M5.Display.setTextWrap(true, false);             // 文字列の折返し(x軸,y軸)
}

String mojiHenkan(String moji) {  // 文字変換関数
  moji.replace(" ", "");         // 全角空白を削除
  moji.replace("晴れ", "晴");     // 天気で使用
  moji.replace("くもり", "曇");   // 天気で使用
  moji.replace("曇り", "曇");     // 天気で使用
  //moji.replace("のち", "/"); // 天気で使用
  //moji.replace("時々", "I"); // 天気で使用
  //moji.replace("一時", "II"); // 天気で使用
  moji.replace("メートル", "m");  // 波で使用
  moji.replace("0", "0");        // 半角変換 波で使用
  moji.replace("1", "1");        //
  moji.replace("2", "2");        //
  moji.replace("3", "3");        //
  moji.replace("4", "4");        //
  moji.replace("5", "5");        //
  moji.replace("6", "6");        //
  moji.replace("7", "7");        //
  moji.replace("8", "8");        //
  moji.replace("9", "9");        //
  moji.replace(".", ".");        //
  moji.replace("-", "/");         // "-" → "/" 時刻で使用
  moji.replace("T", " ");         // "T" → " " 時刻で使用
  moji.replace("\n", "");         // 予報文で使用 (今回は未使用)
  return moji;                    // 文字列mojiに変換後入れ戻る
}

// Areaの文字列が検索文字列と一致するIndexを求める
uint8_t FindSpot(JsonDocument doc, uint32_t days, uint32_t timeIndex, String spot) {
  for (uint8_t i = 0; i < doc[days]["timeSeries"][timeIndex]["areas"].size(); i++) {
    // 全体のjson[0:3日予報/1:7日予報]["timeSeries"][00:天気・風・波/01:降水確率/02:気温/
    // 10:天気コード・降水確率・信頼度/11:気温]["areas"] *数字の十の位は〇日予報の値
    // 場所の数だけ繰返し
    if (doc[days]["timeSeries"][timeIndex]["areas"][i]["area"]["name"].as() == spot) {
      // 表示項目の検索場所名と一致したら
      return i;  // 一致したIndex
    }
  }
  return 0;  // 一致しなかったらとりあえず0
}

JsonDocument getJson(const char *url) {                                      // wifi接続+json読込関数
  if (WiFi.status() == WL_CONNECTED) {                                       // wifiが接続されていたら
    https.begin(url);                                                        // アクセスするURLを登録
    if (https.GET() > 0) {                                                   // アクセス先があれば
      DeserializationError error = deserializeJson(doc, https.getString());  // 解析(JsonDocument,JSON入力)
      if (error) {                                                           // jsonがエラーなら
        M5.Display.print("json解析エラー");                                  // 表示
      }
    } else {                              // アクセス先が無ければ
      M5.Display.println("エラー HTTP");  // 表示
    }
    https.end();                        // https通信終了
  } else {                              // wifiが接続できないなら
    M5.Display.println("エラー wifi");  // 表示
  }
  return doc;  // 取得したjsonを返す
}

void printWeather(JsonDocument doc, int8_t dMenu) {  // 天気表示関数
  //if (doc == NULL) return;
  M5.Display.fillScreen(TFT_BLACK);  // 全画面黒
  M5.Display.setCursor(0, 0);        // カーソル位置

  // 1行目 発表時刻・気象台
  String s = doc[days3]["reportDatetime"];           // 発表日時を取得
  uint8_t reportTime = s.substring(11, 13).toInt();  // 発表時判定用
  String reportDateTime = s.substring(5, 7) + "月" + s.substring(8, 10) + "日"
                          + s.substring(11, 13) + "時 ";  // 発表月日時 年は略
  M5.Display.setTextSize(0.8);                            // 文字サイズ縮小
  M5.Display.print(mojiHenkan(reportDateTime));           // 文字変換後表示 発表日時

  String publishingOffice = doc[days3]["publishingOffice"];  // 気象台名を取得
  M5.Display.println(publishingOffice + "発表");             // 表示 気象台名
  M5.Display.setTextSize(1);                                 // 文字サイズ戻す

  // 2行目 表示の日付
  switch (dMenu) {  // 表示日が今日~明後日を表示
    case 0:
      s = doc[days3]["timeSeries"][0]["timeDefines"][0]
            .as();            // 3日予報天気項目の1番目
      todayStr = s.substring(0, 10);  // 今日の日付
      M5.Display.print("今日");       // 表示
      break;
    case 1:
      s = doc[days3]["timeSeries"][0]["timeDefines"][1]
            .as();            // 3日予報天気項目の2番目
      todayStr = s.substring(0, 10);  // 明日の日付
      M5.Display.print("明日");       // 表示
      break;
    case 2:
      if (reportTime == 5) {  // 5時発表なら
        s = doc[days7]["timeSeries"][0]["timeDefines"][2]
              .as();            // 7日予報降水確率の3番目
        todayStr = s.substring(0, 10);  // 明後日の日付
      } else {                          // 11・17時発表なら
        s = doc[days3]["timeSeries"][0]["timeDefines"][2]
              .as();            // 3日予報天気項目の3番目
        todayStr = s.substring(0, 10);  // 明後日の日付
      }
      M5.Display.print("明後日");  // 表示
      break;
  }
  JsonObject dayData = doc[days3]["timeSeries"][0];          // 条件取出し 3日予報 天気・風・波を取得
  if ((dayData["timeDefines"].size() == 2) && dMenu == 2) {  // 日付が2個and明後日なら (AM5発表時) 7日予報より
    JsonObject dayData = doc[days7]["timeSeries"][0];        // 条件取出し 7日予報 天気・風・波を取得
    String dateDay = dayData["timeDefines"][dMenu];          // 天気の項目の日付を取得
    uint16_t yyear = dateDay.substring(0, 4).toInt();        // 表示する年 曜日計算にのみ使用
    uint8_t mmonth = dateDay.substring(5, 7).toInt();        // 表示する月 曜日計算のみに使用
    uint8_t dday = dateDay.substring(8, 10).toInt();         // 表示する日
    int wweek = setWeek(yyear, mmonth, dday);                // 曜日計算
    M5.Display.printf(" %2d日(", dday);                      // 表示する日
    M5.Display.print(dayWeek[wweek]);                        // 表示する曜日
    M5.Display.println(") [気象庁]");                       // 表示

    // 3-4行目 天気コード 7日予報より (AM5発表時)
    JsonObject timeData = doc[days7]["timeSeries"][0];             // 7日予報の天気コード・降水確率・信頼度
    for (int8_t i = 0; i < timeData["timeDefines"].size(); i++) {  // 時刻の数繰返す
      if (timeData["timeDefines"][i].as().indexOf(todayStr) != -1) {
        // もし、年月日の中から、検索文字(表示日の年月日)が見つかれば
        String weatherCode = timeData["areas"][FindSpot(doc, days7, 0, day7area)]
                                     ["weatherCodes"][i]
                                       .as();      // 7日予報の天気コード
        M5.Display.print("天気: (" + weatherCode + ") ");  // 表示 天気コード

        DeserializationError error = deserializeJson(doc2, weatherCodeJson);  // 解析(JsonDocument,JSON入力)
        if (error) {
          Serial.print("エラー 天気コード");  // 解析エラー
        }
        s = doc2[weatherCode][3].as();  // 天気コードから変換した天気文
        M5.Display.println(s);                  // 表示 天気文
      }
    }

    // 5-6行目 風
    M5.Display.setCursor(0, 19 + 24 * 3);  // カーソル位置
    M5.Display.println("風: --");          // 表示

    // 7行目 波
    M5.Display.setCursor(0, 19 + 24 * 5);  // カーソル位置
    M5.Display.println("波: --");          // 表示

  } else {
    String dateDay = dayData["timeDefines"][dMenu];    // 天気の項目の日付を取得
    uint16_t yyear = dateDay.substring(0, 4).toInt();  // 表示する年 曜日計算にのみ使用
    uint8_t mmonth = dateDay.substring(5, 7).toInt();  // 表示する月 曜日計算のみに使用
    uint8_t dday = dateDay.substring(8, 10).toInt();   // 表示する日
    int wweek = setWeek(yyear, mmonth, dday);          // 曜日計算
    M5.Display.printf(" %2d日(", dday);                // 表示する日
    M5.Display.print(dayWeek[wweek]);                  // 表示する曜日
    M5.Display.println(") [気象庁]");                 // 表示

    // 3-4行目 天気文 3日予報より
    String weather = dayData["areas"][FindSpot(doc, days3, 0, day3area)]["weathers"][dMenu]
                       .as();                    // 天気予報文を取得 (json全体,0=3日予報,0=天気風波,検索文字列)
    M5.Display.println("天気: " + mojiHenkan(weather));  // 文字変換後表示 天気予報

    // 5-6行目 風 3日予報より
    M5.Display.setCursor(0, 19 + 24 * 3);  // カーソル位置
    String winds = dayData["areas"][FindSpot(doc, days3, 0, day3area)]["winds"][dMenu]
                     .as();                  // 風を文字列で取得
    M5.Display.println("風: " + mojiHenkan(winds));  // 文字変換後表示 風

    // 7行目 波 3日予報より
    M5.Display.setCursor(0, 19 + 24 * 5);  // カーソル位置
    String waves = dayData["areas"][FindSpot(doc, days3, 0, day3area)]["waves"][dMenu]
                     .as();                  // 波を文字列で取得
    M5.Display.println("波: " + mojiHenkan(waves));  // 文字変換後表示 波
  }

  // 8-9行目 降水確率 3日予報+7日予報
  M5.Display.setCursor(0, 19 + 24 * 6);                            // カーソル位置
  M5.Display.println("降水確率 00- 06- 12- 18-");                  // 表示
  if (dMenu < 2) {                                                 // 今日・明日は3日予報より
    char popArray[4][5] = { " -- ", " -- ", " -- ", " -- " };      // 降水確率初期表示内容
    JsonObject rainData = doc[days3]["timeSeries"][1];             // 条件取出し 3日予報 降水確率を取得
    for (int8_t i = 0; i < rainData["timeDefines"].size(); i++) {  // 時刻の数繰返す
      if (rainData["timeDefines"][i].as().indexOf(todayStr) != -1) {
        // もし、天気・風・波の年月日の中から、検索文字(表示日の年月日)が見つかれば (max 4)
        String pops = rainData["areas"][FindSpot(doc, days3, 0, day3area)]["pops"][i]
                        .as();                          // 降水確率を抽出
        s = rainData["timeDefines"][i].as();            //その降水確率の時間
        String rainTime = s.substring(11, 13);                  // 時を抽出
        String ss = " " + pops + " ";                           // 降水確率
        if (rainTime == "00") strcpy(popArray[0], ss.c_str());  // 00-06の範囲の降水確率と交換
        if (rainTime == "06") strcpy(popArray[1], ss.c_str());  // 06-12の範囲の降水確率と交換
        if (rainTime == "12") strcpy(popArray[2], ss.c_str());  // 12-18の範囲の降水確率と交換
        if (rainTime == "18") strcpy(popArray[3], ss.c_str());  // 18-24の範囲の降水確率と交換
      }
    }
    M5.Display.print("    ");     // 表示
    for (int8_t i = 0; i < 4; i++) {  // 4個表示
      M5.Display.print(popArray[i]);  // 各時間範囲の降水確率
    }
    M5.Display.println("%");                                                  // 表示
  } else {                                                                    // 明後日は7日予報より
    JsonObject rainData = doc[days7]["timeSeries"][0];                        // 7日予報の天気コード・降水確率・信頼度
    for (int8_t i = 0; i < rainData["timeDefines"].size(); i++) {             // 3日予報と同様
      if (rainData["timeDefines"][i].as().indexOf(todayStr) != -1) {  // 3日予報と同様
        String pops = rainData["areas"][FindSpot(doc, days7, 0, day7area)]["pops"][i]
                        .as();                            // 同様に3日予報の値を7日予報に変更
        M5.Display.printf("  %2s%%    --- --- --- ---\n", pops);  // 表示 1日の降水確率
      }
    }
  }

  // 10行目 最高最低気温 3日予報+7日予報より
  int8_t tmpMin = 99, tmpMax = -99;                                                    // 仮の最低・最高温度
  if (dMenu < 2) {                                                                     // 今日・明日は3日予報より
    JsonObject tmpData = doc[days3]["timeSeries"][2];                                  // 条件取り出し 3日予報 気温
    for (int i = 0; i < tmpData["timeDefines"].size(); i++) {                          // 時刻の数だけ繰返す
      if (tmpData["timeDefines"][i].as().indexOf(todayStr) != -1) {            // 検索日が有ったら
        int8_t val = tmpData["areas"][FindSpot(doc, days3, 2, day3city)]["temps"][i];  // 検索日の温度を抽出
        if (val < tmpMin) tmpMin = val;                                                // 同日に2値あるので小さい方を最低とする
        if (val > tmpMax) tmpMax = val;                                                // 同日に2値あるので大きい方を最高とする
      }
    }
  } else {                                                                            // 明後日は7日予報より
    JsonObject tmpData = doc[days7]["timeSeries"][1];                                 // 条件取り出し 7日予報 気温
    for (int i = 0; i < tmpData["timeDefines"].size(); i++) {                         // 3日予報と同じ
      if (tmpData["timeDefines"][i].as().indexOf(todayStr) != -1) {           // 3日予報と同じ
        tmpMin = tmpData["areas"][FindSpot(doc, days7, 1, day7city)]["tempsMin"][i];  // 最低気温
        tmpMax = tmpData["areas"][FindSpot(doc, days7, 1, day7city)]["tempsMax"][i];  // 最高気温
      }
    }
  }
  M5.Display.print("朝 ");                     // 表示
  M5.Display.setTextColor(TFT_CYAN);           // 文字色
  if ((tmpMin == tmpMax) || (tmpMin == 99)) {  // 最低が最高と同じ or 最低が無ければ
    M5.Display.print("--");                    // 表示
  } else {                                     // 最低があれば
    M5.Display.print(tmpMin);                  // 表示 最低気温
  }
  M5.Display.setTextColor(TFT_GREEN);  // 文字色
  M5.Display.print("℃min 日中 ");     // 表示
  M5.Display.setTextColor(TFT_RED);    // 文字色
  if (tmpMax == -99) {                 // 最高が無ければ
    M5.Display.print("--");            // 表示
  } else {                             // 最高があれば
    M5.Display.print(tmpMax);          // 表示 最高気温
  }
  M5.Display.setTextColor(TFT_GREEN);  // 文字色
  M5.Display.println("℃max");          // 表示

  M5.Display.fillRect(106 * dMenu, 237, 106, 3, TFT_LIGHTGREY);  // 表示中の日を表すバー (x,y,w,h,色)
  delay(500);                                                    // 0.5秒待つ
}

void setup() {
  auto cfg = M5.config();                                      // M5設定用の構造体を代入
  M5.begin(cfg);                                               // M5本体の初期化
  SetDisplay();                                                // 初期画面設定関数へ
  M5.Display.println("wifi接続開始");                          // 表示
  wifiMulti.addAP(ssid1, pass1);                               // ssid1を登録
  wifiMulti.addAP(ssid2, pass2);                               // ssid2を登録
  wifiMulti.addAP(ssid3, pass3);                               // ssid3を登録
  M5.Display.println("wifi接続中");                            // 表示
  for (int i = 20; i && WiFi.status() != WL_CONNECTED; --i) {  // 20回以内にwifi接続されなかったら
    wifiMulti.run();                                           // 一番強いアクセスポイントに接続
    M5.Display.print(".");                                     // 表示
    delay(1000);                                               // 1秒待つ
  }
  if (wifiMulti.run() == WL_CONNECTED) {      // wifiが接続されたら
    M5.Display.print("\nwifi接続済\nssid:");  // 表示
    M5.Display.println(WiFi.SSID());          // 接続したssidを表示
  } else {                                    // 接続されなかったら
    M5.Display.println("\nwifi未接続");       // 表示
  }
  doc = getJson(weatherJson);  // 関数へ
  dMenu = 0;                   // 表示は今日の予報
  printWeather(doc, dMenu);    // 表示関数へ
}

void loop() {
  M5.update();                 // ボタンに合わせて表示日を変更
  if (M5.BtnA.wasPressed()) {  // Aボタンが押されたら
    dMenu--;                   // 前日
    if (dMenu < 0) dMenu = 2;  // 今日の前は明後日
    printWeather(doc, dMenu);  // 表示関数へ
  }
  if (M5.BtnB.wasPressed()) {  // Bボタンが押されたら
    dMenu++;                   // 翌日
    if (dMenu > 2) dMenu = 0;  // 明後日の次は今日
    printWeather(doc, dMenu);  // 表示関数へ
  }
  delay(100);  // 0.1秒待つ
}
* flash memory(2.1Mbyte)のうち、スケッチが63%使用。RAM(328kbyte)のうち、global変数が15%使用、local変数で278kbyte使用可能。(1000byte=1kbyteで計算)