13.CoreInkでOpenWeatherMap(4)


>13.CoreInkでOpenWeatherMap(4)

説明

無料のワンコールAPI(https://openweathermap.org/api/one-call-api)で
  • 現在の天気
  • 1Hの分毎の降水量予報
  • 48Hの時間予報
  • 7日間の毎日の天気予報
  • 全国気象警報
  • 過去5日間の過去の気象データ
が提供され、前回は1Hの分毎の降水量予報を表示しましたが、今回は48Hの時間予報を表示します。今回から、1画面で全部を表示できなくなったため、ボタンを使用して選択します。そして、今までの分も見れるように変更しました。

[1]現在の気象
ボタンを2秒間上に回したら何もしない
ボタンを上に回したら
ボタンを2秒間下に回したら[2]へ
ボタンを下に回したら
ボタンを押し込んだら
上面ボタンを押したら何もしない
詳しくは、「11.CoreInkでOpenWeatherMap(2)」を参照してください。

[2]1H後までの降水予報量
ボタンを2秒間上に回したら[1]へ
ボタンを上に回したら
ボタンを2秒間下に回したら[3]へ
ボタンを下に回したら
ボタンを押し込んだら
上面ボタンを押したら[1]へ
このところずっと晴れです。詳しくは、「12.CoreInkでOpenWeatherMap(3)」を参照してください。

[3]48H後までの気象予報のmenu
月/日は一番左の時刻の日付です。48データあります。
ボタンを2秒間上に回したら時刻を1行上へ
ボタンを上に回したら前の時刻へ
ボタンを2秒間下に回したら時刻を1行下へ
ボタンを下に回したら次の時刻へ
ボタンを押し込んだら[4]へ
上面ボタンを押したら[1]へ
4/23 10:00の時刻を選択しています。

[4]48H後までの気象予報
ボタンを2秒間上に回したら1H前の予報へ
ボタンを上に回したら
ボタンを2秒間下に回したら1H後の予報へ
ボタンを下に回したら
ボタンを押し込んだら[5]へ
上面ボタンを押したら[1]へ
4/23 10:00の予報です。


スケッチ

xxxxxは、自分のwifiのSSID、wifiのパスワード、APIキーを入力します。自分で作った天気のアイコン"icon48-9.h"も必要です。

// 天気情報 OpenWeatherMap
// 確認は、https://openweathermap.org/city/1848354
// 関数一覧
// pushSprite    LovyanGFXの1画面表示お決まり
// getJson       jsonを得る
// houi          wind_degからwind_houi算出
// drawCcurrent  現在の天気 1画面をセット
// drawMinutely  1H後までの降水量 1画面セット
// drawHourlymenu 48H後までの予報のmenu 1画面セット
// drawHourly    48H後までの予報 1画面セット
// drawDaily     7日後までの予報 1画面セット
// inkSet        coreInkを使う時お決まり
// wifiSet       wifiを使う時お決まり
// nihonSet      日本語を表示する時お決まり
// setup
// loop
#include "icon48-9.h"     // 天気マーク 同一ホルダに置く
#include <M5CoreInk.h>    // M5Stack CoreInk使用
#include <WiFi.h>         // wifi使用
#include <HTTPClient.h>   // httpと通信
#include <ArduinoJson.h>  // json型式で抽出
#include <efontEnableJaMini.h> // フォント 日本語ミニ
#include <efontFontData.h>     // efontのフォントデータ
#define LGFX_AUTODETECT        // init時に対応機種を自動認識
#include <LovyanGFX.hpp>       // 描画ライブラリ
#include <time.h>              // 時間表示
const char* ssid = "xxxxx";  // 自分のwifiのSSID
const char* password = "xxxxx";     // wifiのパスワード
const char* weatherURL = "https://api.openweathermap.org/data/2.5/onecall?lat=35.447781&lon=139.642502&units=metric&lang=ja&appid=xxxxx";
// 神奈川県 横浜 ワンコールAPI
DynamicJsonDocument weatherInfo(35000);  //
Ink_Sprite InkPageSprite(&M5.M5Ink);     // 定義
static LGFX_Sprite sprite;               // 定義
time_t t;  // システム時刻 time()関数によって得られる,<time.h>で定義,数値
struct tm *tm;  // 構造体の型を宣言,タグ名,構造体変数名
static const char *wd[7] = {"(日)", "(月)", "(火)", "(水)", "(木)", "(金)", "(土)"};
int dy = 17, upy;  // 行刻み,上の行
int sheet;  // 1:現在(current) 2:1H後までの降水量(minutely)
// 3:48H後までの予報のmenu(hourly) 4:48H後までの予報(hourly)
// 5:7日後までの予報(daily)
int hanten = 0;  // sheet=3にて選択時刻0-47

// LovyanGFXの1画面表示関数
void pushSprite(Ink_Sprite *coreinkSprite, LGFX_Sprite *lgfxSprite) {
  coreinkSprite->clear();
  for (int y = 0; y < 200; y++) {    // 縦方向0-199
    for (int x = 0; x < 200; x++) {  // 横方向0-199
      uint16_t c = lgfxSprite->readPixel(x, y);
      if (c == 0x0000) {
        coreinkSprite->drawPix(x, y, 0);
      }
    }
  }
  coreinkSprite->pushSprite(); // アロー演算子
}

DynamicJsonDocument getJson() {     //
  DynamicJsonDocument doc(35000);   //
  if ((WiFi.status() == WL_CONNECTED)) {  // wifiが接続されていたら
    HTTPClient http;             // 定義
    http.begin(weatherURL);      // アクセスするURLを登録
    Serial.println(weatherURL);  // シリアルモニタに表示
    int httpCode = http.GET();   //登録URLに、GETリクエスト送信
    if (httpCode > 0) {  //HTTPのリターンコード。エラーの場合は負数
      String jsonString = http.getString();  //
      Serial.print("HTTPコード="); Serial.println(httpCode);//シリアルモニタに表示
      Serial.println(jsonString);        // json用文字列 長い
      deserializeJson(doc, jsonString);
      // 直列化されたデータをDynamicJsonDocumentの形に変換
    } else {
      Serial.println("エラー HTTPリクエスト");  // シリアルモニタに表示
    }
    http.end();                // TCPコネクションを切断しHTTP通信を終了
  }
  return doc;
}

String houi(int wind_degInt) {      // wind_degIntからwind_houi算出
  String wind_houi;                 // 8方位
  if (wind_degInt < 22.5) {
    wind_houi = "北 ";
  } else if (wind_degInt < 67.5) {
    wind_houi = "北東";
  } else if (wind_degInt < 112.5) {
    wind_houi = "東 ";
  } else if (wind_degInt < 157.5) {
    wind_houi = "南東";
  } else if (wind_degInt < 202.5) {
    wind_houi = "南 ";
  } else if (wind_degInt < 247.5) {
    wind_houi = "南西";
  } else if (wind_degInt < 292.5) {
    wind_houi = "西 ";
  } else if (wind_degInt < 337.5) {
    wind_houi = "北西";
  } else {
    wind_houi = "北 ";
  }
  return wind_houi;
}



void drawCcurrent() {                    // 現在の気象 1画面セットする
  sheet = 1;                             // 画面1
  upy = 0;                               // 表示用y
  sprite.clear(TFT_WHITE);               // 全体の背景色を白
  String today = weatherInfo["current"]; // 現在(current)
  DynamicJsonDocument doc(35000);        //
  deserializeJson(doc, today);           //
  //
  String dt = doc["dt"];                          // 現在の時刻(Unix,UTC)
  t = dt.toInt() + 9 * 3600; tm = localtime(&t);  //
  sprite.setCursor(5, upy);                       // 表示位置
  sprite.printf("%04d/%02d/%02d%s %02d:%02d現在",  // 画面表示
                tm->tm_year + 1900, tm->tm_mon + 1,
                tm->tm_mday, wd[tm->tm_wday],
                tm->tm_hour, tm->tm_min);
  Serial.printf("現在%04d/%02d/%02d%s %02d:%02d", // シリアルモニタに表示
                tm->tm_year + 1900, tm->tm_mon + 1,
                tm->tm_mday, wd[tm->tm_wday],
                tm->tm_hour, tm->tm_min);
  //
  String sunrise = doc["sunrise"];                   // 日の出時刻(Unix,UTC)
  t = sunrise.toInt() + 9 * 3600; tm = localtime(&t);       //
  upy = upy + dy; sprite.setCursor(10, upy);                // 表示位置
  sprite.printf("日の出%01d:%02d", tm->tm_hour, tm->tm_min); // 画面表示
  Serial.printf(" 日の出%01d:%02d", tm->tm_hour, tm->tm_min);//シリアルモニタに表示
  //
  String sunset = doc["sunset"];                       // 日没時間(Unix,UTC)
  t = sunset.toInt() + 9 * 3600 ; tm = localtime(&t);       //
  sprite.printf("   日没%02d:%02d", tm->tm_hour, tm->tm_min);// 画面表示
  Serial.printf(" 日没%02d:%02d\n", tm->tm_hour, tm->tm_min);//シリアルモニタに表示
  //
  String icon = doc["weather"][0]["icon"];    // 天気アイコンID
  upy = upy + dy; sprite.setCursor(10, upy);  // 表示位置
  Serial.printf("天気(%s)", icon);             // シリアルモニタに表示
  int x = 70, y = 52, W = 48, H = 48;         // 天気マークの表示位置と大きさ
  if (icon == "01d" || icon == "01n") {       // or
    sprite.pushImage(x, y, W, H, img48hare);  // 晴天(晴れ)
  } else if (icon == "02d" || icon == "02n") {
    sprite.pushImage(x, y, W, H, img48hareKumori);// いくつかの雲:11-25%(晴曇り)
  } else if (icon == "03d" || icon == "03n") {
    sprite.pushImage(x, y, W, H, img48kumori);    // 散在する雲25-50%(曇り)
  } else if (icon == "04d" || icon == "04n") {
    sprite.pushImage(x, y, W, H, img48donyori);   // 壊れた雲51-100%(どんより)
  } else if (icon == "09d" || icon == "09n") {
    sprite.pushImage(x, y, W, H, img48kumoriAme); // にわか雨(曇雨)
  } else if (icon == "10d" || icon == "10n") {
    sprite.pushImage(x, y, W, H, img48ame);       // 雨(雨)
  } else if (icon == "11d" || icon == "11n") {
    sprite.pushImage(x, y, W, H, img48kaminari);  // 雷雨(雷)
  } else if (icon == "13d" || icon == "13n") {
    sprite.pushImage(x, y, W, H, img48yuki);      // 雪(雪)
  } else if (icon == "50d" || icon == "50n") {
    sprite.pushImage(x, y, W, H, img48moya);      // 靄(もや)
  }
  //
  String main = doc["weather"][0]["main"];  // 概略気象
  Serial.printf("%s\n", main);              // シリアルモニタに表示
  //
  String id = doc["weather"][0]["id"];      // 詳細気象ID
  Serial.printf("詳細(%s)", id);             // シリアルモニタに表示
  //
  char buf[100];                                         // char用buf
  String description = doc["weather"][0]["description"]; // 詳細気象 日本語
  int len = description.length();                        // 文字列長さ
  description.toCharArray(buf, len);                     // string → char
  for (int i = 0; i < len; i++) {
    sprite.printf("%c", description[i]);     // 日本語変数表示
  }
  Serial.printf("%s\n", description);        // シリアルモニタに表示
  //
  String temp = doc["temp"];                    // 温度
  upy = upy + dy; sprite.setCursor(130, upy);   // 表示位置
  sprite.printf("温度%2.0f℃", temp.toFloat());  // 文字列→単精度浮動小数点
  Serial.printf("温度%2.0f℃", temp.toFloat());  // シリアルモニタに表示
  //
  String feels_like = doc["feels_like"];              // 体感温度
  upy = upy + dy; sprite.setCursor(130, upy);         // 表示位置
  sprite.printf("体感%2.0f℃", feels_like.toFloat());  // 画面表示
  Serial.printf(" 体感%2.0f℃", feels_like.toFloat()); // シリアルモニタに表示
  //
  String dew_point = doc["dew_point"];               // 結露気温
  upy = upy + dy; sprite.setCursor(130, upy);        // 表示位置
  sprite.printf("結露%2.0f℃", dew_point.toFloat());  // 画面表示
  Serial.printf(" 結露%2.0f℃", dew_point.toFloat()); // シリアルモニタに表示
  //
  sprite.setCursor(10, upy);                         // 表示位置
  sprite.print("(横浜)");                             // 場所を画面表示
  //
  String humidity = doc["humidity"];                 // 湿度(%)
  upy = upy + dy; sprite.setCursor(10, upy);         // 表示位置
  sprite.printf("湿度%2.0f%%", humidity.toFloat());   // 画面表示
  Serial.printf(" 湿度%2.0f%%", humidity.toFloat());  // シリアルモニタに表示
  //
  String pressure = doc["pressure"];                     // 海面気圧(hPa)
  sprite.printf("    海面%4.0fhPa", pressure.toFloat());  // 画面表示
  Serial.printf(" 海面%4.0fhPa\n", pressure.toFloat());   // シリアルモニタに表示
  //
  String wind_speed = doc["wind_speed"];              // 風速(m/s)
  upy = upy + dy; sprite.setCursor(10, upy);          // 表示位置
  sprite.printf("風%2.0fm/s", wind_speed.toFloat());  // 画面表示
  Serial.printf("風%2.0fm/s", wind_speed.toFloat());  // シリアルモニタに表示
  //
  String wind_deg = doc["wind_deg"];     // 風向(方位角)
  int wind_degInt = wind_deg.toInt();    //
  String wind_houi = houi(wind_degInt);  // wind_degからwind_houi算出
  sprite.printf("(%s%3d°)", wind_houi, wind_degInt);  // 画面表示
  Serial.printf("(%s%3d°)", wind_houi, wind_degInt);  // シリアルモニタに表示
  //
  String clouds = doc["clouds"];                 // 曇量(%)
  sprite.setCursor(150, upy);                    // 表示位置
  sprite.printf("曇%2d%%", clouds.toInt());       // 画面表示
  Serial.printf(" 曇量%2d%%\n", clouds.toInt());  // シリアルモニタに表示
  //
  String uvi = doc["uvi"];                     // UV指数
  upy = upy + dy; sprite.setCursor(10, upy);   // 表示位置
  sprite.printf("UV指数%4.1f", uvi.toFloat());  // 画面表示
  Serial.printf("UV指数%4.1f", uvi.toFloat());  // シリアルモニタに表示
  //
  String visibility = doc["visibility"];                       // 可視性(m)
  sprite.printf("  可視%4.1fkm", visibility.toFloat() / 1000);  // 画面表示
  Serial.printf(" 可視性%4.1fkm", visibility.toFloat() / 1000); // シリアルモニタに表示
  //
  String rain_1h = doc["hourly"][0]["rain"][0]["1h"];
  //利用可能な場合、過去1Hの降水量(mm)
  upy = upy + dy; sprite.setCursor(10, upy);  // 表示位置
  if (rain_1h = "") {
    sprite.printf("降水なし");                // 画面表示
    Serial.printf("降水なし");                // シリアルモニタに表示
  } else {
    sprite.printf("1H雨%.0dmm", rain_1h.toInt());   // 画面表示
    Serial.printf("1H降水%.0dmm", rain_1h.toInt()); // シリアルモニタに表示
  }
  //
  String snow_1h = doc["hourly"][0]["snow"][0]["1h"];
  //利用可能な場合、1H前からの積雪量(mm)
  if (snow_1h = "") {
    sprite.printf(" 積雪なし");                      // 画面表示
    Serial.printf(" 積雪なし\n");                    // シリアルモニタに表示
  } else {
    sprite.printf(" 1H雪%.0dmm", snow_1h.toInt());  // 画面表示
    Serial.printf(" 1H前からの積雪%.0dmm\n", snow_1h.toInt()); // シリアルモニタに表示
  }
  //
  String wind_gust = doc["wind_gust"];        // 利用可能な場合、突風(m/s)
  upy = upy + dy; sprite.setCursor(10, upy);  // 表示位置
  if (wind_gust = "") {
    sprite.printf("突風なし");                 // 画面表示
    Serial.printf(" 突風なし\n");              // シリアルモニタに表示
  } else {
    sprite.printf("突風%2.0fm/s", wind_gust.toFloat());    // 画面表示
    Serial.printf(" 突風%2.0fm/s\n", wind_gust.toFloat()); // シリアルモニタに表示
  }
}



void drawMinutely() {  // 1H後までの降水量 1画面セットする
  sheet = 2;                                  // 画面2
  sprite.clear(TFT_WHITE);                    // 全体の背景色を白
  String minutely = weatherInfo["minutely"];  //
  DynamicJsonDocument doc(35000);             //
  deserializeJson(doc, minutely);             //
  //
  String dt = doc[0]["dt"];                   // 時刻(Unix,UTC)
  t = dt.toInt() + 9 * 3600; tm = localtime(&t);  //
  sprite.setCursor(0, 0);                     // 1行目
  sprite.printf("%02d:%02dから60分後までの",  // 画面表示
                tm->tm_hour, tm->tm_min);
  sprite.setCursor(70, dy); sprite.print("降水予測量(mm/H)");  // 2行目
  //
  int x = 20, y = 20;              // 表示位置
  for (int i = 0; i <= 60; i++) {  // 61データ
    String dt = doc[i]["dt"];      // 時刻(Unix,UTC)
    t = dt.toInt() + 9 * 3600;     //
    tm = localtime(&t);            //
    Serial.printf("%04d/%02d/%02d%s %02d:%02d", // シリアルモニタに表示
                  tm->tm_year + 1900, tm->tm_mon + 1,
                  tm->tm_mday, wd[tm->tm_wday],
                  tm->tm_hour, tm->tm_min);
    //
    String precipitation = doc[i]["precipitation"]; // 降水予測量(mm)
    sprite.setCursor(x, y);  // 表示位置
    y = y + (dy - 2);        // 下へ
    if (y > 180) {           // 次の列へ
      y = 35;                // yの最上位置へ
      x = x + 30;            // 右へ
    }
    sprite.printf("%3d", precipitation.toInt());    // 画面表示
    Serial.printf(" %dmm\n", precipitation.toInt());// シリアルモニタに表示
  }
  int i = 0;                                    // 分
  for (y = 21; y <= 180; y = y + dy - 2) {      // 11文字表示
    sprite.setTextColor(TFT_WHITE, TFT_BLACK);  // 黒地に白字
    sprite.setCursor(0, y);                     // 表示位置
    sprite.printf("%2d", i);                    // 最左列の縦軸値
    i = i + 1;                                  // 次の分
  }
  i = 1;                                // 分
  for (x = 20; x <= 180; x = x + 30) {  // 6文字表示
    sprite.setCursor(x, 185);           // 表示位置
    sprite.printf("%3d", i * 10);       // 最下行の横軸値
    i = i + 1;                          // 次の分
  }
  sprite.setTextColor(TFT_BLACK, TFT_WHITE);      // 白地に黒字
  for (x = 17; x <= 180; x = x + 30) {            // 6本引く
    sprite.drawFastVLine(x, 35, 165, TFT_BLACK);  // 縦線(x,y,L,色)
  }
  pushSprite(&InkPageSprite, &sprite);            // 1画面表示関数へ
}



void drawHourlymenu() {  // 48H後までの予報menu 1画面セットする
  sheet = 3;                              // 画面3
  sprite.clear(TFT_WHITE);                // 全体の背景色を白
  String hourly = weatherInfo["hourly"];  //
  DynamicJsonDocument doc(35000);         //
  deserializeJson(doc, hourly);           //

  upy = 5; sprite.setCursor(15, upy);   // 表示位置
  sprite.print("予報の時刻選択メニュー");   // 画面表示

  String dt = doc[0]["dt"];             // 予報時刻(Unix,UTC)
  t = dt.toInt() + 9 * 3600; tm = localtime(&t);  //
  upy = upy + dy + 5; sprite.setCursor(10, upy);  // 表示位置
  sprite.printf("%02d/%02d %02d時 ~ ",            // 画面表示
                tm->tm_mon + 1, tm->tm_mday, tm->tm_hour);

  String dt1 = doc[47]["dt"];              // 予報時刻(Unix,UTC)
  t = dt1.toInt() + 9 * 3600; tm = localtime(&t); //
  sprite.printf("%02d/%02d %02d時",               // 画面表示
                tm->tm_mon + 1, tm->tm_mday, tm->tm_hour);

  int i = 0;
  sprite.drawFastHLine(0, 49, 199, TFT_BLACK);     // 横線(x,y,L,色)
  for (int y = 50; y < 180; y = y + dy) {
    String dt = doc[i]["dt"];                      // 予報時刻(Unix,UTC)
    t = dt.toInt() + 9 * 3600; tm = localtime(&t); //
    sprite.setCursor(5, y);                        // 表示位置
    sprite.printf("%02d/%02d|", tm->tm_mon + 1, tm->tm_mday); // 画面表示
    for (int x = 55; x < 180; x = x + 24) {
      String dt = doc[i]["dt"];                    // 予報時刻(Unix,UTC)
      t = dt.toInt() + 9 * 3600; tm = localtime(&t);  //
      if (hanten == i) {
        sprite.setTextColor(TFT_WHITE, TFT_BLACK); // 黒地に白字
      } else {
        sprite.setTextColor(TFT_BLACK, TFT_WHITE); // 白地に黒字
      }
      sprite.setCursor(x, y);                      // 表示位置
      sprite.printf("%02d", tm->tm_hour);          // 画面表示
      i = i + 1;                                   // 次の時刻
    }
    sprite.drawFastHLine(0, y + 16, 199, TFT_BLACK);//横線(x,y,L,色)
  }
  pushSprite(&InkPageSprite, &sprite);        // 1画面表示関数へ
}



void drawHourly() {                     // 48H後までの予報 1画面セットする
  sheet = 4;                            // 画面4
  upy = 0;                              // 表示用y
  sprite.clear(TFT_WHITE);              // 全体の背景色を白
  String hourly = weatherInfo["hourly"];  //
  DynamicJsonDocument doc(35000);         //
  deserializeJson(doc, hourly);           //
  String dt = doc[hanten]["dt"];          // 予報時刻(Unix,UTC)
  t = dt.toInt() + 9 * 3600; tm = localtime(&t);  //
  sprite.setCursor(5, upy);                       // 表示位置
  sprite.printf("%04d/%02d/%02d%s %02d:%02d",     // 画面表示
                tm->tm_year + 1900, tm->tm_mon + 1,
                tm->tm_mday, wd[tm->tm_wday],
                tm->tm_hour, tm->tm_min);
  Serial.printf("%02d/%02d%s%02d",     // シリアルモニタに表示
                tm->tm_mon + 1, tm->tm_mday,
                wd[tm->tm_wday], tm->tm_hour);
  //
  String icon = doc[hanten]["weather"][0]["icon"];  // 天気アイコンID
  upy = upy + dy; sprite.setCursor(10, upy);        // 表示位置
  Serial.printf(" 天気(%s)", icon);     // シリアルモニタに表示
  int x = 70, y = 33, W = 48, H = 48;  // 天気マークの表示位置と大きさ
  if (icon == "01d" || icon == "01n") {
    sprite.pushImage(x, y, W, H, img48hare);       // 晴天(晴れ)
  } else if (icon == "02d" || icon == "02n") {     // or
    sprite.pushImage(x, y, W, H, img48hareKumori); // いくつかの雲:11-25%(晴曇り)
  } else if (icon == "03d" || icon == "03n") {
    sprite.pushImage(x, y, W, H, img48kumori);  // 散在する雲25-50%(曇り)
  } else if (icon == "04d" || icon == "04n") {
    sprite.pushImage(x, y, W, H, img48donyori); // 壊れた雲51-100%(どんより)
  } else if (icon == "09d" || icon == "09n") {
    sprite.pushImage(x, y, W, H, img48kumoriAme);  // にわか雨(曇雨)
  } else if (icon == "10d" || icon == "10n") {
    sprite.pushImage(x, y, W, H, img48ame);        // 雨(雨)
  } else if (icon == "11d" || icon == "11n") {
    sprite.pushImage(x, y, W, H, img48kaminari);   // 雷雨(雷)
  } else if (icon == "13d" || icon == "13n") {
    sprite.pushImage(x, y, W, H, img48yuki);       // 雪(雪)
  } else if (icon == "50d" || icon == "50n") {
    sprite.pushImage(x, y, W, H, img48moya);       // 靄(もや)
  }
  //
  String main = doc[hanten]["weather"][0]["main"];  // 概略気象
  Serial.printf(" %s", main);                       // シリアルモニタに表示
  //
  String id = doc[hanten]["weather"][0]["id"];  // 詳細気象ID
  Serial.printf(" 詳細(%s)", id);               // シリアルモニタに表示
  //
  char buf[100];                             //
  String description = doc[hanten]["weather"][0]["description"]; //詳細気象 日本語
  int len = description.length();         // 文字列長さ
  description.toCharArray(buf, len);      // string → char
  for (int i = 0; i < len; i++) {
    sprite.printf("%c", description[i]);  // 日本語変数表示
  }
  Serial.printf(" %s", description);      // シリアルモニタに表示
  //
  String temp = doc[hanten]["temp"];             // 温度
  upy = upy + dy; sprite.setCursor(130, upy);    // 表示位置
  sprite.printf("温度%2.0f℃", temp.toFloat());   // 文字列→単精度浮動小数点
  Serial.printf(" 温度%2.0f℃", temp.toFloat());  // シリアルモニタに表示
  //
  String feels_like = doc[hanten]["feels_like"];      // 体感温度
  upy = upy + dy; sprite.setCursor(130, upy);         // 表示位置
  sprite.printf("体感%2.0f℃", feels_like.toFloat());  // 画面表示
  Serial.printf(" 体感%2.0f℃", feels_like.toFloat()); // シリアルモニタに表示
  //
  String dew_point = doc[hanten]["dew_point"];       // 結露気温
  upy = upy + dy; sprite.setCursor(130, upy);        // 表示位置
  sprite.printf("結露%2.0f℃", dew_point.toFloat());  // 画面表示
  Serial.printf(" 結露%2.0f℃", dew_point.toFloat()); // シリアルモニタに表示
  //
  sprite.setCursor(10, upy);                         // 表示位置
  sprite.print("(横浜)");                            // 場所
  //
  String humidity = doc[hanten]["humidity"];        // 湿度(%)
  upy = upy + dy; sprite.setCursor(10, upy);        // 表示位置
  sprite.printf("湿度%2.0f%%", humidity.toFloat());  // 画面表示
  Serial.printf(" %2.0f%%", humidity.toFloat());    // シリアルモニタに表示
  //
  String pressure = doc[hanten]["pressure"];            // 海面気圧(hPa)
  sprite.printf("    海面%4.0fhPa", pressure.toFloat()); // 画面表示
  Serial.printf(" %4.0fhPa", pressure.toFloat());       // シリアルモニタに表示
  //
  String wind_speed = doc[hanten]["wind_speed"];      // 風速(m/s)
  upy = upy + dy; sprite.setCursor(10, upy);          // 表示位置
  sprite.printf("風%2.0fm/s", wind_speed.toFloat());  // 画面表示
  Serial.printf(" 風%2.0fm/s", wind_speed.toFloat()); // シリアルモニタに表示
  //
  String wind_deg = doc[hanten]["wind_deg"];  // 風向(方位角)
  int wind_degInt = wind_deg.toInt();         // stringからintに変換
  String wind_houi = houi(wind_degInt);  // wind_degからwind_houi算出
  sprite.printf("(%s%3d°)", wind_houi, wind_degInt);  // 画面表示
  Serial.printf("(%s%3d°)", wind_houi, wind_degInt);  // シリアルモニタに表示
  //
  String clouds = doc[hanten]["clouds"];      // 曇量(%)
  sprite.setCursor(150, upy);                 // 表示位置
  sprite.printf("曇%2d%%", clouds.toInt());   // 画面表示
  Serial.printf(" 曇%2d%%", clouds.toInt());  // シリアルモニタに表示
  //
  String uvi = doc[hanten]["uvi"];             // UV指数
  upy = upy + dy; sprite.setCursor(10, upy);   // 表示位置
  sprite.printf("UV指数%4.1f", uvi.toFloat());  // 画面表示
  Serial.printf(" UV%4.1f", uvi.toFloat());    // シリアルモニタに表示
  //
  String visibility = doc[hanten]["visibility"];               // 可視性(m)
  sprite.printf("  可視%4.1fkm", visibility.toFloat() / 1000);  // 画面表示
  Serial.printf(" 可視性%4.1fkm", visibility.toFloat() / 1000); // シリアルモニタに表示
  //
  String pop = doc[hanten]["pop"];            // 降水確率(%)
  upy = upy + dy; sprite.setCursor(10, upy);  // 表示位置
  sprite.printf("降水%2.0f%%", pop.toInt());   // 画面表示
  Serial.printf(" 降水%2.0f%%", pop.toInt());  // シリアルモニタに表示
  //
  String snow_1h = doc[hanten]["snow"][0]["1h"];
  //利用可能な場合、1H前からの積雪量(mm)
  if (snow_1h = "") {
    sprite.printf(" 積雪なし");  // 画面表示
    Serial.printf(" 積雪なし");  // シリアルモニタに表示
  } else {
    sprite.printf(" 1H雪%.0dmm", snow_1h.toInt());         // 画面表示
    Serial.printf(" 1H前からの積雪%.0dmm", snow_1h.toInt()); // シリアルモニタに表示
  }
  //
  String wind_gust = doc[hanten]["wind_gust"]; // 利用可能な場合、突風(m/s)
  upy = upy + dy; sprite.setCursor(10, upy);   // 表示位置
  if (wind_gust = "") {
    sprite.printf("突風なし");                 // 画面表示
    Serial.printf(" 突風なし");                // シリアルモニタに表示
  } else {
    sprite.printf("突風%2.0fm/s", wind_gust.toFloat());   // 画面表示
    Serial.printf(" 突風%2.0fm/s", wind_gust.toFloat());  // シリアルモニタに表示
  }
  Serial.println("");
}



void drawDaily() {                // 7日後までの予報(daily) 1画面セット
  sheet = 5;                            // 画面5
  upy = 0;                              // 表示用y
  sprite.clear(TFT_WHITE);              // 全体の背景色を白
  String daily = weatherInfo["daily"];  //
  DynamicJsonDocument doc(35000);       //
  deserializeJson(doc, daily);          //
}



void inkSet() {                       // お決まり
  M5.begin();               // E-Ink,RTC,I2C,ブザーを初期化
  if (!M5.M5Ink.isInit()) {           // 初期化出来なければ
    Serial.println("エラー Ink Init"); // シリアルモニタに表示
    while (1) delay(100);             // ずっと待つ
  }
  M5.M5Ink.clear();                   // 画像クリア
  delay(1000);                        // 1秒待つ
  InkPageSprite.creatSprite(0, 0, 200, 200, false);
  // 画像領域を作成し、画面ドライバーから画像データバフを取得しない
}
void wifiSet() {                           // お決まり
  Serial.print(ssid);                      // シリアルモニタに表示
  Serial.print("に接続中");                 // シリアルモニタに表示
  WiFi.begin(ssid, password);              // ネット設定を初期化
  while (WiFi.status() != WL_CONNECTED) {  // 接続状態が接続完でない時
    delay(500);                            // 0.5秒待つ
    Serial.print(".");                     // シリアルモニタに表示
  }
  Serial.println("ok");                    // シリアルモニタに表示
}
void nihonSet() { // efont日本語16太 お決まり(16or24)
  sprite.setColorDepth(2);        // 2ビットパレットモードに設定
  sprite.createPalette();         // パレットモードにする?
  sprite.createSprite(200, 200);  // 200x200でスプライトを作成
  sprite.clear(TFT_WHITE);        // 全体の背景色を白
  sprite.setFont(&fonts::efontJA_16_b); // efont日本語16ドット太字
  sprite.setTextColor(TFT_BLACK, TFT_WHITE);  // 白地に黒字
}

void setup() {
  inkSet();                          // CoreInkの設定
  wifiSet();                         // wifiの設定
  nihonSet();                        // 日本語設定(efont16太)
  weatherInfo = getJson();           // json取得関数へ
  WiFi.disconnect(true);             // ネットワークから切断
  WiFi.mode(WIFI_OFF);               // wifiの動作モード
  Serial.print("wifi offか=");       // シリアルモニタに表示
  Serial.println(WiFi.status());     // wifiの接続状態
  // 0(WiFi.beginが呼出され3or4になるまで),1(使用可能なSSIDがない)
  // 2(スキャン完了),3(WiFi接続),4(すべての試行で接続失敗)
  // 5(接続が失われた),255(WiFiシールドがない)
  drawCcurrent();                      // 現在の気象表示関数へ
}

void loop() {
  if (M5.BtnUP.pressedFor(2000)) {     // ボタンを2秒間上に回したら
    switch (sheet) {
      case 1: break;                   // 現在の気象時
      case 2:                          // 1H後までの降水量時
        drawCcurrent(); break;         // 現在の気象表示へ
      case 3:                          // 48H後までの予報menu時
        if (hanten > 5) {
          hanten = hanten - 6;         // 選択時刻を1行上へ
          drawHourlymenu();            // 48H後までの予報menu表示へ
        }
        break;
      case 4:                          // 48H後までの予報時
        if (hanten > 0) {
          hanten--;                    // 1H前の予報へ
          drawHourly();                // 48H後までの予報表示へ
        }
        break;
      case 5:                          // 7日後までの予報時
        break;
    }
    M5.update();                       // ボタンとブザーを更新
  }
  if (M5.BtnDOWN.pressedFor(2000)) {   // ボタンを2秒間下に回したら
    switch (sheet) {
      case 1:                          // 現在の気象時
        drawMinutely(); break;         // 1H後までの降水量表示へ
      case 2:                          // 1H後までの降水量時
        drawHourlymenu(); break;       // 48H後までの予報menu表示へ
      case 3:                          // 48H後までの予報menu時
        if (hanten < 42) {
          hanten = hanten + 6;         // 選択時刻を1行下へ
          drawHourlymenu();            // 48H後までの予報menu表示へ
        }
        break;
      case 4:                          // 48H後までの予報時
        if (hanten < 47) {
          hanten++;                    // 1H後の予報へ
          drawHourly();                // 48H後までの予報表示へ
        }
        break;
      case 5:                          // 7日後までの予報時
        break;
    }
    M5.update();                       // ボタンとブザーを更新
  }
  if (M5.BtnUP.wasPressed()) {         // ボタンを上に回したら
    switch (sheet) {
      case 1: break;                   // 現在の気象時
      case 2:                          // 1H後までの降水量時
        drawCcurrent(); break;         // 現在の気象表示へ
      case 3:                          // 48H後までの予報menu時
        if (hanten > 0) {
          hanten--;                    // 選択時刻を-1
          drawHourlymenu();            // 48H後までの予報menu表示へ
        }
        break;
      case 4:                          // 48H後までの予報時
        if (hanten > 0) {
          hanten--;                    // 1H前の予報へ
          drawHourly();                // 48H後までの予報表示へ
        }
        break;
      case 5:                          // 7日後までの予報時
        break;
    }
  }
  if (M5.BtnDOWN.wasPressed()) {       // ボタンを下に回したら
    switch (sheet) {
      case 1:                          // 現在の気象時
        drawMinutely(); break;         // 1H後までの降水量表示へ
      case 2:                          // 1H後までの降水量時
        drawHourlymenu(); break;       // 48H後までの予報menu表示へ
      case 3:                          // 48H後までの予報menu時
        if (hanten < 47) {
          hanten++;                    // 選択時刻を+1
          drawHourlymenu();            // 48H後までの予報menu表示へ
        }
        break;
      case 4:                          // 48H後までの予報時
        if (hanten < 47) {
          hanten++;                    // 1H前の予報へ
          drawHourly();                // 48H後までの予報表示へ
        }
        break;
      case 5:                          // 7日後までの予報時
        break;
    }
  }
  if (M5.BtnMID.wasPressed()) {        // ボタンを押し込んだら
    switch (sheet) {
      case 1:                          // 現在の気象時
        drawMinutely(); break;         // 1H後までの降水量表示へ
      case 2:                          // 1H後までの降水量時
        drawHourlymenu(); break;       // 48H後までの予報menu表示へ
      case 3:                          // 48H後までの予報menu時
        drawHourly(); break;           // 48H後までの予報表示へ
      case 4:                          // 48H後までの予報時
      //drawDaily();  break;           // 7日後までの予報表示へ
      case 5:                          // 7日後までの予報時
        break;
    }
  }
  if (M5.BtnEXT.wasPressed()) {        // 上面ボタンを押したら
    switch (sheet) {
      case 1: break;                   // 現在の気象時
      case 2:                          // 1H後までの降水量時
      case 3:                          // 48H後までの予報menu時
      case 4:                          // 48H後までの予報時
      case 5:                          // 7日後までの予報時
        drawCcurrent(); break;         // 現在の気象表示へ
    }
  }

  if (M5.BtnPWR.wasPressed()) {        // 電源ボタンを押したら(電源off)
    M5.PowerDown();//しばらくすると上面緑LED消灯。電源onは電源ボタンを押す
  }
  pushSprite(&InkPageSprite, &sprite);  // 1画面表示関数へ
  M5.update();                          // ボタンとブザーを更新
}