📶

M5Stack で気象庁天気予報 JSONとひまわり画像を取得し表示する

2023/02/06に公開

概要

天気予報 ひまわり画像(Core2のみ)
forecast himawari

以前作成した WxBeacon2のデータを M5Stack で取得する に、Wi-Fi 経由で気象庁の天気予報 JSON の取得と表示 (Core2 ではさらに気象衛星ひまわり画像の取得と表示) を追加してみました。
WxBeacon2 との BLE 通信機能は維持したままで機能追加した際に得た知見を徒然と書きたいと思います。

BLE と Wi-Fi の同居

ESP32 は Bluetooth と Wi-Fi を内蔵したマイクロコントローラです。M5Stack ではそれらの機能を用いることができます。が、両方同時に使用しようとすると簡単にはいきませんでした。

  • タスクの問題
    通信関連の内部処理は双方 Core0 で動作しており、CPU の取り合いが起こる。(自分のタスクも含め Core0 の処理負荷が大きくなると WDT[1] に引っかかる)
    また内部で使用されているタスクの優先順位は外部から(たぶん)変更できないので、タイミングに起因する問題や、予期せぬ長い待ちが発生しかねない)
  • メモリの問題
    通信関連処理内部でのメモリ割り当てが想像以上に大きく、インターナルヒープの枯渇によりエラーが返ったり、ハングアップする。
    (PSRAM のある Core2 でも内部動作においては通常のヒープ領域が使われる)

よって、BLE と Wi-Fi は排他的に動作させることにしました。(片方が動作中はもう片方は動かさない)
排他制御は自ら任意の方法で行うことになります。

BLE 関連の後始末と再起動

通常 BLE を初期化した後はそのままで運用しているかと思いますが、使用後は完全に機能を停止してヒープを空けます。

wxbeacon2_task.cpp
NimBLEDevice::init(""); // 初期化
//
// 諸々の処理
//
/*
void NimBLEDevice::deinit(bool clearAll = false) 
clearAll If true, deletes all server/advertising/scan/client objects after deinitializing. 
*/
NimBLEDevice::deinit(true); // 使い終わったので関連オブジェクトも含めて解放

Wi-Fi関連の後始末と再起動

同じく Wi-Fi も使い終わったら完全に停止します。

jma_task.cpp
WiFi.mode(WIFI_STA); // 初期化
WiFi.begin(); // 開始
//
// 諸々の処理
//
/*
WiFi.disconnect(bool wifioff = false, bool eraseap = false)
wifioff `true` to turn the Wi-Fi radio off.
eraseap `true` to erase the AP configuration from the NVS memory.
*/
WiFi.disconnect(true); // 切断(電波も OFF)
WiFi.mode(WIFI_OFF); // Wi-Fi 機構全体を OFF

どちらも中途半端な状態で存在しているとヒープや CPU、電流が消費されるので完全に機能を停止させ、使う度に再初期化するようにします。その為、通信開始時には初期化で少しもっさりとした動作感になるかと思います。

SSL[2] 通信 (https[3] 接続)

無限に接続を試行しない

通信接続系のサンプルでは無限試行している例が散見されますが、試行回数を設定して失敗時はきちんとエラーとして処理すべきです。
無限試行では Wi-Fi アクセスポイントや接続先サーバーへの予期せぬ負荷をかけることになります。
当プログラムでは接続時、https 接続時共に以下の様にして試行しています。

jma_task.cpp
// Wi-Fi 接続リトライ
int8_t ctry = 3; // 試行回数
do
{
    WiFi.mode(WIFI_STA);
    WiFi.begin(); // 内部保存された情報で接続
    // 接続待ち 0.5 sec x 20
    int tcount = 20;
    while(tcount-- > 0 && WiFi.status() != WL_CONNECTED)
    {
        delay(500);
    }
    if(WiFi.status() == WL_CONNECTED) { break; } // 成功したら do-while 脱出
    // 後始末
    WiFi.disconnect(true);
    WiFi.mode(WIFI_OFF);
    WB2_LOGD("Retry");
    delay(10);
}while(ctry--);

// 再度確認
if(WiFi.status() != WL_CONNECTED)
{
// エラー処理
}
//接続成功した場合の処理へ
jma_task.cpp
// https 接続リトライ
HTTPClient http;
WiFiClientSecure client;
client.setCACert(root_ca_JMA);

int8_t rtry = 3; // 試行回数
int httpcode = 0;
// https GET
do
{
    // HttpClient の開始失敗?
    if(!http.begin(client, url))
    {
        WB2_LOGE("Failed to begin");
        continue;
    }
    httpcode = http.GET();
    if(httpcode >= 0) { break; } // 何某か受信したので脱出

    // HTTPClient の内部エラーなので後始末して再試行
    WB2_LOGE("%s", http.errorToString(httpcode).c_str());
    client.stop();
    http.end();
    delay(100);
}while(rtry--);

// HttpCode の再確認
if(httpcode != HTTP_CODE_OK)
{
    // エラー処理
    return;
}
// 正常取得後の処理
// ...
// 後始末
client.stop();
http.end();

ルート証明書

SSL 証明書はルート証明書、中間 CA 証明書、サーバ証明書による階層構造になっています。
https 接続(SSL通信)のためには接続先サーバーのルート証明書[4]が必要です。
ブラウザで当該サーバーへアクセスしてルート証明書をダウンロードやエクスポートしてファイル化し、それをソースコードへ埋め込みます。

各ブラウザにおけるルート証明書の表示と取得
ルート証明書の例
PROGMEM const char root_ca_JMA[] =
R"***(-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
 -----END CERTIFICATE-----
)***";

改行も情報として含まれるので、生文字列リテラルを使って記述すると楽です。
https://cpprefjp.github.io/lang/cpp11/raw_string_literals.html

トラブルシューティング

よくある接続失敗事例です。シリアルに出ているであろうメッセージをしっかりと確認しましょう。

X509 - Certificate verification failed

ルート証明書が間違っています。
指定した証明書が当該サーバーの物かを確認してください。また誤って中間 CA 証明書やサーバ証明書を指定していないかも要確認です。
証明書には有効期限があり、それを超えていた場合は別の値の証明書になっているはずです。また有効期限前でもサーバー運営者が別の証明書に変えている場合もあります。
Web 上のサンプルプログラムでこのエラーが発生する場合は、ブラウザ等から当該サーバーの最新のルート証明書を取得しましょう。

SSL - Memory allocation failed

メモリが足りない為動作できません。
https 接続にはインターナルヒープが約 70KiB (実際は internal largest heap が 50KiB程度?) 必要です。
PSRAM は使われないので注意。

ヒープを空ける

当初のプログラムではヒープ領域が足りず、 https 接続時にメモリ不足で通信が確立できませんでした。ヒープ領域を空ける為に以下の様な修正を施しました。

PROGMEM の使用

https://garretlab.web.fc2.com/arduino_reference/language/variables/variable_scope_and_qualifiers/progmem.html
通常文字列リテラル等は SRAM 上に配置されます。ヒープメモリは SRAM 全体からそれら配置物を除いた分だけの領域が使用されます。
つまり SRAM 上の配置物を減らせばその分ヒープが空けられるというわけです。(正確には色々とあるのですが、ここは大雑把な説明に留めます)

Arduino においては PROGMRM 修飾子を用いることで、配置先をフラッシュメモリへ変更することができます。デメリットとしてはフラッシュメモリ使用サイズの増加によって使用しているパーテーションテーブル[5]に収まらなくなる場合があります。
その場合は可能であればパーテーションテーブルの変更で対応してください(当プログラムでは Basic,Gray の場合 min_spiffs.csv を使用しています)
当プログラムでは画像リソースや文字列テーブル類を PROGMEM に変更しました。

PROGMEM変更前
// SRAM上に 要素数 38518 の uint8_t が存在していた
const uint8_t ponko_face_bmp[] = {
  0x42, 0x4d, 0x76, 0x96, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x00,
PROGMEM変更後
#include <pgmspace.h>
// フラッシュ領域へ移動したのでヒープが増加する
PROGMEM const uint8_t ponko_face_bmp[] = {
  0x42, 0x4d, 0x76, 0x96, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x00,

顔のリソースは 38518 byte だったので効果は大きいです。

配置位置の確認方法

PlatformIO では build_options に -Wl,-Map,output.map をつけてビルドすることでマップファイルを出力することができます。
出力されたマップファイルをエディタやマップファイル用のビューア等で閲覧し、
当該データが セクション .flash.rodata サブセクション .rodata にあればフラッシュメモリに配置されるデータです。

筆者はビューアとしてこちらをを使用しました。
https://www.sikorskiy.net/info/prj/amap/

画像リソースの直接描画

https://github.com/m5stack/M5GFX
当初はビットマップの描画に M5GFX のスプライトクラスを使用していました。取り回しはしやすいのですが、リソースと同サイズのメモリが確保される為ヒープが圧迫されていました。
M5GFX には Bitmap, Png, Jpg を直接描画する関数が存在するので、それらを使用して一部の画像の描画でスプライトクラスを使用しない様にしました。なお Png, Jpg の直接描画の際には展開用のメモリが一時的に内部で確保されるので注意が必要です。
当プログラムでは 3種類共に使用しています。(顔画像などは Bitmap、天気アイコンは 4色(2bpp) Png、 ひまわり画像は Jpg)

旧来

修正後

M5GFX 直接描画サンプル

気象庁の天気予報データ

データ利用における注意
https://twitter.com/e_toyoda/status/1364504338572410885

参考にしたページ
https://github.com/misohena/el-jma/blob/main/docs/how-to-get-jma-forecast.org#L1-L3

天気予報の表示の為、https 接続にて気象庁提供の JSON データを取得します。
当プログラムでは以下の都市のデータのみを取得します。

取得する都市の officecode
PROGMEM const jma::officecode_t requestTable[] =
{
     16000, // Sapporo
     40000, // Sendai
    130000, // Tokyo
    150000, // Niigata
    170000, // Kanazawa
    230000, // Nagoya
    270000, // Oosaka
    340000, // Hiroshima
    390000, // Kochi
    400000, // Fukuoka
    471000, // Okinawa
};
気象庁 JSON データの例

"https://www.jma.go.jp/bosai/forecast/data/forecast/170000.json"

[{
	"publishingOffice": "金沢地方気象台",
	"reportDatetime": "2023-01-30T11:00:00+09:00",
	"timeSeries": [{
		"timeDefines": ["2023-01-30T11:00:00+09:00", "2023-01-31T00:00:00+09:00", "2023-02-01T00:00:00+09:00"],
		"areas": [{
			"area": {
				"name": "加賀",
				"code": "170010"
			},
			"weatherCodes": ["413", "200", "218"],
			"weathers": ["雪 夜 くもり 所により 夕方 まで 雷 を伴う", "くもり 所により 明け方 まで 雪", "くもり 後 雨か雪"],
			"winds": ["西の風 やや強く 海上 では 西の風 強く", "北の風 後 南の風 海上 では 後 南の風 やや強く", "南の風 やや強く 後 西の風 強く"],
			"waves": ["3メートル 後 4メートル", "3メートル 後 1.5メートル", "2メートル 後 4メートル"]
		}, {
			"area": {
				"name": "能登",
				"code": "170020"
			},
			"weatherCodes": ["413", "200", "218"],
			"weathers": ["雪 夜 くもり 所により 夕方 まで 雷 を伴う", "くもり 所により 明け方 まで 雪", "くもり 後 雨か雪"],
			"winds": ["西の風 やや強く 海上 では 西の風 非常に強く", "北西の風 後 南の風 海上 では 南西の風 やや強く", "南の風 やや強く 後 西の風 強く"],
			"waves": ["3メートル 後 4メートル", "3メートル 後 2メートル", "2.5メートル 後 5メートル"]
		}]
	}, {
		"timeDefines": ["2023-01-30T12:00:00+09:00", "2023-01-30T18:00:00+09:00", "2023-01-31T00:00:00+09:00", "2023-01-31T06:00:00+09:00", "2023-01-31T12:00:00+09:00", "2023-01-31T18:00:00+09:00"],
		"areas": [{
			"area": {
				"name": "加賀",
				"code": "170010"
			},
			"pops": ["80", "40", "40", "20", "10", "10"]
		}, {
			"area": {
				"name": "能登",
				"code": "170020"
			},
			"pops": ["80", "40", "20", "10", "10", "20"]
		}]
	}, {
		"timeDefines": ["2023-01-30T09:00:00+09:00", "2023-01-30T00:00:00+09:00", "2023-01-31T00:00:00+09:00", "2023-01-31T09:00:00+09:00"],
		"areas": [{
			"area": {
				"name": "金沢",
				"code": "56227"
			},
			"temps": ["4", "4", "-1", "5"]
		}, {
			"area": {
				"name": "輪島",
				"code": "56052"
			},
			"temps": ["4", "4", "-1", "5"]
		}]
	}]
}, {
	"publishingOffice": "金沢地方気象台",
	"reportDatetime": "2023-01-30T11:00:00+09:00",
	"timeSeries": [{
		"timeDefines": ["2023-01-31T00:00:00+09:00", "2023-02-01T00:00:00+09:00", "2023-02-02T00:00:00+09:00", "2023-02-03T00:00:00+09:00", "2023-02-04T00:00:00+09:00", "2023-02-05T00:00:00+09:00", "2023-02-06T00:00:00+09:00"],
		"areas": [{
			"area": {
				"name": "石川県",
				"code": "170000"
			},
			"weatherCodes": ["200", "218", "205", "201", "206", "206", "200"],
			"pops": ["", "70", "70", "30", "50", "50", "40"],
			"reliabilities": ["", "", "B", "A", "C", "C", "C"]
		}]
	}, {
		"timeDefines": ["2023-01-31T00:00:00+09:00", "2023-02-01T00:00:00+09:00", "2023-02-02T00:00:00+09:00", "2023-02-03T00:00:00+09:00", "2023-02-04T00:00:00+09:00", "2023-02-05T00:00:00+09:00", "2023-02-06T00:00:00+09:00"],
		"areas": [{
			"area": {
				"name": "金沢",
				"code": "56227"
			},
			"tempsMin": ["", "-1", "0", "-2", "-1", "2", "1"],
			"tempsMinUpper": ["", "2", "1", "0", "1", "3", "3"],
			"tempsMinLower": ["", "-4", "-2", "-4", "-4", "-1", "-3"],
			"tempsMax": ["", "10", "5", "5", "9", "8", "8"],
			"tempsMaxUpper": ["", "12", "7", "7", "11", "11", "11"],
			"tempsMaxLower": ["", "9", "4", "3", "7", "6", "6"]
		}]
	}],
	"tempAverage": {
		"areas": [{
			"area": {
				"name": "金沢",
				"code": "56227"
			},
			"min": "0.6",
			"max": "6.8"
		}]
	},
	"precipAverage": {
		"areas": [{
			"area": {
				"name": "金沢",
				"code": "56227"
			},
			"min": "35.4",
			"max": "55.5"
		}]
	}
}]

JSON パース

Arduino では Ardiono JSON という優秀なライブラリがあります。しかし数値他 JSON が扱えるであろう大きなサイズ(intmax_t相当?) で保持される為、ある程度以上大きな JSON を扱うとメモリが大量に消費されてしまいます。
気象庁の JSON データの構成は大きく、配列の要素になっているオブジェクトが同型ではない、値が全て文字列扱い デジタル庁さんか気象庁さん、フォーマット改訂してくれませんか なので、ストリーミング型 JSON パーサで値を適切な型、サイズにします。またストリーミング型では必要ないデータを棄却することも容易です。

当初は json-streaming-parser、後に json-streaming-parser2 を使用していたのですが、色々と不適合な部分や値の取得が面倒くさかったこともあり、 json-streaming-parser2 を修正、発展させたライブラリを自作する事にしました。
https://github.com/GOB52/gob_json

Datetime(日付時刻)

気象庁の JSON データに日付時刻を表す値として 2023-01-30T11:00:00+09:00 という表記が出てきます。この表記は ISO 8601[6] による Datetime 表記です。日付と時刻に UTC[7] との時差(タイムゾーンではないことに注意)が付与された物で、一意で絶対的な日付時刻の値です。
単純に std::strptime 等でパースして time_t としても良いのですが、ここでは Java の JSR-310 Date and Time API にならい、 OffsetDateTime として扱える様に最低限のライブラリを仕立てました。(なお今回使う部分のみの実装で全てのJSR-310 API はサポートされていません)

https://github.com/GOB52/gob_datetime

JSR-310における LocalDateTime,ZonedDateTime,OffsetDateTime の違い
  • LocalDateTime
    なんらの付加情報が付与されていないただの日付時刻
    ユーザーからの入力等に用いられる
  • ZonedDateTime
    タイムゾーン情報が付与された日付時刻
    タイムゾーンのルールによっては存在しない日付時刻であったり、多重に存在する日付時刻の場合がある
    よって一意な日付時刻ではない
  • OffsetDateTime
    時差が付与された日付時刻
    一意で絶対的な日付時刻である為、シリアライズに適したデータと言える

タイムゾーン

タイムゾーンに関しては以下の記事がとても参考になりました。真面目に対応や実装しようとするとまさにカオスですね(´・ω・`)
https://zenn.dev/dmikurube/articles/curse-of-timezones-common-ja
https://zenn.dev/dmikurube/articles/curse-of-timezones-impl-ja
https://zenn.dev/dmikurube/articles/curse-of-timezones-java-ja

Arduino では POSIX スタイルのタイムゾーン が内部的に使用されます。
例えば configTzTime() 等の NTP による時刻合わせの中で 指定した引数に基づく tzset() が行われます。
POSIX スタイルなのでタイムゾーンデータベースは参照されません。全ての時は指定されたタイムゾーンで一括に処理されます。つまり現在の表記と違う過去や未来のタイムゾーン、一年の中でコロコロ変更される特殊なタイムゾーンは簡単に対応できないと言う事です。

気象衛星ひまわりの画像データの取得と表示

こちらのページを参考にしました
https://mag.switch-science.com/2021/07/14/m5stack-core2/

接続後、 Jpg データを順次読み込んでいくわけですが、回線状態などの要因で client の読み込み準備が整っていない場合を考慮したリトライ機構を仕込みました。
適宜 delay によって準備が整うのを待ちつつ、一定サイズごとに読み込んでいきます。

himawari_task.cpp
constexpr size_t blockSize = 1024 * 4; // 1回で読み込むサイズ
WiFiClientSecure client;
//
// 接続等中略
//
size_t jpgsz{}, off{};
szTmp = jpgsz = http.getSize(); // 画像データサイズの取得
WB2_LOGD("jpgsz:%zu ava:%zu", jpgsz, client.available());
// メモリ確保 (大きいので PSRAM に確保されているはず)
jpg = static_cast<uint8_t*>(malloc(jpgsz));
if(!jpg)
{
// 異常時処理へ
}
int gtry = 16; // リトライ回数
do
{
    // client の stream が準備されきってていないと available == 0 の場合がある
    while(http.connected() && client.available())
    {
        size_t read = std::min(blockSize, szTmp);
        while(read)
        {
            auto len = client.readBytes(jpg + off, read);
            read -= len;
            off += len;
            WB2_LOGD("len:%zu off:%zu ava:%zu", len, off, client.available());
            delay(100); // Wait
        }
        szTmp -= std::min(blockSize, szTmp);
        if(callbackOnProgress) { callbackOnProgress(off, jpgsz); } // 自分用読み込み進捗表示用コールバック
        delay(100); // Wait
    }
    WB2_LOGD("http:%d", http.connected());
    if(off < jpgsz) { WB2_LOGD("Not enough read"); delay(500); } // Wait
}while(off < jpgsz && gtry--); // 全データかが読み切れていない、かつリトライ回数が残っていれば再試行
if(off != jpgsz)
{
// 全データが読みこまれていない
}

付録:NTP時刻合わせの接続先順を乱数で変更する

作成中、setup にて NTP 時刻合わせの後の処理に問題がありリセットがかかる事態がありました。そうなるとリセットによる再起動、NTP時刻合わせ、リセット... の繰り返しが頻繁に行われてしまい、予期せぬ頻度で NTP サーバーへの接続を試みる事になります。
そうなると意図せぬ DoS攻撃[8] になりかねません。
新たなプログラムを書き込むのにも時間がかかりますし、電源を落とすにしても USB 接続状態の Basic 系列は不可能です(外せは可能)
そこで気休め的ではありますが、接続先を乱数で毎回変更して同一サーバーへの接続回数を減らす様にしてみました。

configTime, configTzTime のサーバー指定は 3個まで可能です。内部的には試行して失敗したら次に指定されている物へという挙動の様ですが、引数の指定順を乱数で変更する事で同一サーバーへの連続した接続の頻度を減らします。

main.cpp
// NTP サーバーアドレス
PROGMEM const char ntp0[] = "ntp.nict.jp";
PROGMEM const char ntp1[] = "ntp.jst.mfeed.ad.jp";
PROGMEM const char ntp2[] = "time.cloudflare.com";
const char* ntpURLTable[] = { ntp0, ntp1, ntp2 };
// std::shuffle に渡す乱数生成器 関数オブジェクト
struct ESP32RNG
{
    using result_type = uint32_t;
    static result_type min() { return 0; }
    static result_type max() { return sizeof(ntpURLTable) / sizeof(ntpURLTable[0]); }
    // (*) esp_random はハードウェア乱数生成器なので乱数種はいらない
    result_type  operator()() { return esp_random() % max(); } 
};
void setup() 
{
//
// 中略
//
std::shuffle(std::begin(ntpURLTable), std::end(ntpURLTable), ESP32RNG()); // 配列のシャッフル
configTzTime(posixStyleTZString, ntpURLTable[0], ntpURLTable[1], ntpURLTable[2]);

esp_random は乱数の種を必要としない乱数生成器なので手軽に使用するには良いですが、
種が無い故に擬似乱数の様な再現性がない、散らばり具合等がどの程度なのか乱数の検定がどの程度行われているかは不明、といった問題もあるので他の場面で使うには適さないかもしれません。

リポジトリ

以上を踏まえて修正されたものがこちらです。
以前のバージョンでは画像リソースを作らないとコンパイルを通せなかったのですが、新バージョンではダミーリソースを同梱しました。ポン子ちゃんアバター等他の画像を使用したい場合のみ自作する必要があります。

https://github.com/GOB52/M5S_WxBeacon2

謝辞

試行、検証の上、情報として公開している先人の皆様、有用なライブラリを公開されている皆様、日々気象データと格闘されている気象庁や WNI の皆様他に改めて感謝申し上げます。

気象庁 トゥルーカラー再現画像の利用規約に基づく表記

日本語

(1) トゥルーカラー再現画像の説明
トゥルーカラー再現画像は、ひまわり8号・9号の可視3バンド(バンド1、2、3)、近赤外1バンド(バンド4)及び赤外1バンド(バンド13)を利用し、人間の目で見たような色を再現した衛星画像です。本画像は、衛星によって観測された画像を人間の目で見たように再現する手法(参考文献[1])によって作成されています。この色の再現過程において緑色を調節するために、Millerらによる手法(参考文献[2])の応用として、バンド2、3、4が使用されています。また、画像をより鮮明にするために、大気分子により太陽光が散乱される影響を除去するための手法(レイリー散乱補正)(参考文献[2])が利用されています。

(2) 謝辞
トゥルーカラー再現画像は、気象庁気象衛星センターと米国海洋大気庁衛星部門GOES-Rアルゴリズムワーキンググループ画像チーム(NOAA/NESDIS/STAR GOES-R Algorithm Working Group imagery team)との協力により開発されました。また、レイリー散乱補正のためのソフトウェアは、NOAA/NESDISとコロラド州立大学との共同研究施設(Cooperative Institute for Research in the Atmosphere: CIRA)から気象庁気象衛星センターに提供されました。関係機関に感謝いたします。

(3) 参考文献
[1] Murata, H., K. Saitoh, Y. Sumida, 2018: True color imagery rendering for Himawari-8 with a color reproduction approach based on the CIE XYZ color system. J. Meteor. Soc. Japan., doi: 10.2151/jmsj.2018-049.
[2] Miller, S., T. Schmit, C. Seaman, D. Lindsey, M. Gunshor, R. Kohrs, Y. Sumida, and D. Hillger, 2016: A Sight for Sore Eyes - The Return of True Color to Geostationary Satellites. Bull. Amer. Meteor. Soc. doi: 10.1175/BAMS-D-15-00154.1

English

  1. True Color Reproduction imagery
    True Color Reproduction (TCR) technology enables the display of earth images taken from space in a way that is familiar to the human eye. The imagery consists of data from three visible bands (Band 1, 2 and 3), one near-infrared band (Band 4) and one infrared band (Band 13). To reproduce colors as seen by the human eye, RGB signals observed by AHI are converted into CIE XYZ values and reconverted into RGB signals for output devices compliant with sRGB (an international standard for RGB color space) (Murata et al., 2018). In this process, as an alternative to the bi-spectral hybrid green method outlined by Miller et al. (2016), the green band is optimally adjusted using Band 2, 3 and 4. To make the imagery more vivid, atmospheric correction (Rayleigh correction, Miller et al., 2016) is also applied to AHI Bands 1-4. Software for this purpose was provided by the Cooperative Institute for Research in the Atmosphere (CIRA) established by NOAA/NESDIS and Colorado State University in United States of America.

  2. Acknowledgement
    The imagery was developed on the basis of collaboration between the JMA Meteorological Satellite Center and the NOAA/NESDIS/STAR GOES-R Algorithm Working Group imagery team. We would like to acknowledge them for the collaboration and their permission to use the software.

  3. Reference
    Murata, H., K. Saitoh, Y. Sumida, 2018: True color imagery rendering for Himawari-8 with a color reproduction approach based on the CIE XYZ color system. J. Meteor. Soc. Japan. doi: 10.2151/jmsj. 2018-049
    Miller, S., T. Schmit, C. Seaman, D. Lindsey, M. Gunshor, R. Kohrs, Y. Sumida, and D. Hillger, 2016: A Sight for Sore Eyes - The Return of True Color to Geostationary Satellites. Bull. Amer. Meteor. Soc. doi: 10.1175/BAMS-D-15-00154.1

脚注
  1. watchdog timer https://ja.wikipedia.org/wiki/ウォッチドッグタイマー ↩︎

  2. Hypertext Transfer Protocol Secure https://ja.wikipedia.org/wiki/HTTPS ↩︎

  3. Secure Sockets Layer https://ja.wikipedia.org/wiki/Transport_Layer_Security ↩︎

  4. Root certificate https://ja.wikipedia.org/wiki/ルート証明書 ↩︎

  5. ESP32のPartition Tableの書き方 https://qiita.com/yomori/items/743bc84c961bb921d8cb ↩︎

  6. ISO 8601 https://ja.wikipedia.org/wiki/ISO_8601 ↩︎

  7. Coordinated Universal Time https://ja.wikipedia.org/wiki/協定世界時 ↩︎

  8. denial-of-service attack https://ja.wikipedia.org/wiki/DoS攻撃 ↩︎

Discussion