M5Stack CoreS3でWi-Fi接続とHTTPS通信を実装:WiFiClientSecureでCA証明書を設定する

M5Stack CoreS3は、カメラと画面が内蔵されたマイコンです。 玄関に置いて顔認証するといったことができて便利だと思ったので、購入してみました。

M5Stack CoreS3はESP32-S3を搭載しており、Wi-Fiモジュールが内蔵されています。 ESP32向けのArduinoライブラリがそのまま使えるため、Wi-Fi接続やHTTP通信を手軽に実装できます。

Wi-Fi接続は簡単と思いきや、HTTPS対応のためには事前に接続先の証明書を取得する必要があったりと、少し手順がありました。 この記事では、M5Stack CoreS3でWi-Fi接続をし、天気API(Open-Meteo)を例としてHTTPSでリクエストをする方法をコード付きで紹介します。

接続先の例として、Open-Meteoの天気APIを使いますが、CA証明書を差し替えれば他のAPIにも同じコードで対応できます。

Open-MeteoからはJSONでレスポンスが返ってくるので、そのパース処理についても簡単に触れています。

開発にはPlatformIOを使用しています。

M5Stack CoreS3 デバイス外観
M5Stack CoreS3。2インチカラータッチディスプレイを搭載。

platformio.ini の設定

platformio.inilib_deps で必要なライブラリを指定します。

[env:m5stack-cores3]
platform = espressif32
board = m5stack-cores3
framework = arduino
lib_deps =
  m5stack/M5Unified
  bblanchon/ArduinoJson

m5stack/M5Unified はディスプレイ描画・タッチ検知・バッテリー取得などをまとめて扱えるM5Stack向けのライブラリです。

Wi-FiやHTTPクライアント関連のライブラリはESP32のフレームワークに含まれているため、追加インストールは不要です。 JSONパース用に ArduinoJson のみ追加しています。

事前準備

Wi-Fi接続用の設定

SSIDとパスワードは別ファイルに切り出し、.gitignore でGit管理外にします。

const char* kWifiSsid     = "your-ssid";
const char* kWifiPassword = "your-password";
# .gitignore
src/secrets.h  # ファイル名は任意

CA証明書の設定

HTTPSでは、接続先のサーバーが「信頼できる認証機関(CA)に身元を保証されている」ことを確認してから通信します。 PCやスマートフォンのブラウザには、信頼できるCAの一覧があらかじめ組み込まれています。 https:// のサイトに何も設定せずにアクセスできるのはこのためです。

マイコンにはこの一覧が存在しないため、「このCAを信頼する」とコードで明示的に指定する必要があります。

何の証明書を指定するか

接続先のサーバーには「サーバー証明書」と「それを発行したCA証明書」の2種類があります。

サーバー証明書は定期的に更新されます(多くは1〜2年)。 サーバー証明書をそのままコードに書いた場合、更新のたびにコードも書き直す必要があります。

CA証明書はサーバー証明書より有効期限が長く、頻繁には変わりません。 CA証明書を指定しておけば、サーバー証明書が更新されても引き続き接続できます。

ただしCAが変わった場合はコードの更新が必要です。

実務では外部APIに直接接続するより、自社管理のサーバーを経由する構成が現実的です。 外部APIに直接接続していると、証明書の更新やエンドポイントの変更のたびにOTAでファームウェアを書き換える必要があります。 自社サーバーを経由することで、証明書やデータソースの変更をサーバー側だけで吸収でき、デバイスのファームウェアを触らずに対応できます。

CA証明書は秘密情報ではないため、Git管理下に置いています。 CA証明書はOpenSSLで接続先から取得できます。

openssl s_client -connect api.open-meteo.com:443 -showcerts 2>/dev/null

出力される Certificate chain には複数の証明書が含まれます。

Certificate chain
 0 s:/CN=open-meteo.com
   i:/C=US/O=Let's Encrypt/CN=R12
-----BEGIN CERTIFICATE-----
...(リーフ証明書 — 不要)
-----END CERTIFICATE-----
 1 s:/C=US/O=Let's Encrypt/CN=R12
   i:/C=US/O=Internet Security Research Group/CN=ISRG Root X1
-----BEGIN CERTIFICATE-----
...(中間CA — これを使う)
-----END CERTIFICATE-----
  • 0番open-meteo.com): サーバー自身の証明書。使用しません
  • 1番R12): Let's Encryptの中間CA。これを setCACert() に渡します

ルートCA(ISRG Root X1)はTLSハンドシェイクでサーバーから送られないため出力に含まれません。 「クライアント側がすでに持っているはず」という前提があるためです。マイコンにはトラストストアがないため、代わりに中間CAを指定します。

1番の -----BEGIN CERTIFICATE----- から -----END CERTIFICATE----- までをコピーして定義します。

const char* kOpenMeteoCaCert = R"CERT(
-----BEGIN CERTIFICATE-----
MIIBtjCCAV...(証明書の中身)
-----END CERTIFICATE-----
)CERT";

Wi-Fi 接続

WiFi.mode(WIFI_STA) は、Wi-Fiルーターに接続しに行く側(ステーションモード)の指定です。 ESP32はルーター側として動作するAPモード(WIFI_AP)も持っているため、明示的に指定しないと意図しないモードで動作する場合があります。

WiFi.begin() はノンブロッキングで即リターンします。 接続完了を待たずに次の処理に進んでしまうため、WiFi.status() をポーリングして接続を確認する必要があります。 タイムアウトを設けないと、接続できない場合に無限ループになります。以下では15秒でのタイムアウトを設定しています。

#include <WiFi.h>
 
WifiConnectionResult connectWifiHardware(const char* ssid, const char* password) {
  WifiConnectionResult result;
  result.connected = false;
 
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
 
  const uint32_t start_ms = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - start_ms < 15000) {
    delay(250);
  }
 
  if (WiFi.status() != WL_CONNECTED) {
    return result;
  }
 
  result.connected = true;
  result.ip = WiFi.localIP().toString();
  result.rssi = WiFi.RSSI();
  return result;
}

接続後は WiFi.localIP() でIPアドレスを、WiFi.RSSI() で電波強度(dBm)を取得しています。

HTTPS 通信:CA証明書の設定

ESP32でHTTPSに接続するには WiFiClientSecure を使います。 このコードのポイントは client.setCACert() でCA証明書を設定する点です。

#include <HTTPClient.h>
#include <WiFiClientSecure.h>
 
HttpFetchResult fetchHttpGet(const char* url, const char* ca_cert) {
  HttpFetchResult result;
 
  WiFiClientSecure client;
  client.setCACert(ca_cert);
 
  HTTPClient http;
  if (!http.begin(client, url)) {
    result.error = "HTTP client init error";
    return result;
  }
 
  const int http_code = http.GET();
  if (http_code <= 0) {
    result.error = http.errorToString(http_code);
    http.end();
    return result;
  }
 
  result.success = true;
  result.http_status_code = http_code;
  result.payload = http.getString();
  http.end();
  return result;
}

client.setInsecure() で証明書検証をスキップする方法もあります。 接続はHTTPSのまま暗号化されますが、接続先の身元確認をしないため中間者攻撃のリスクがあります。 テスト用途には使えますが、本番環境では避けた方が良いでしょう。

JSONパース:ArduinoJson v7

接続先のURLは以下です。東京の緯度経度を固定で指定し、現在の気温(temperature_2m)と天気コード(weather_code)を取得しています。

constexpr char kWeatherUrl[] =
    "https://api.open-meteo.com/v1/forecast"
    "?latitude=35.6895&longitude=139.6917"
    "&current=temperature_2m,weather_code"
    "&timezone=Asia%2FTokyo";

レスポンスのパースにはArduinoJsonライブラリを使います。

Open-Meteoのレスポンスは以下の形式です。

{
  "current": {
    "time": "2026-03-23T12:00",
    "interval": 900,
    "temperature_2m": 11.4,
    "weather_code": 51
  },
  ...
}

doc["current"]["temperature_2m"]current オブジェクトの中の temperature_2m キーを指しています。

#include <ArduinoJson.h>
 
JsonDocument doc;
const DeserializationError error = deserializeJson(doc, payload);
if (error) {
  // パース失敗
  return false;
}
 
// キーで値を取り出す
const char* time = doc["current"]["time"];
float temperature = doc["current"]["temperature_2m"].as<float>();

接続先がOpen-Meteoの場合、doc["current"]["temperature_2m"] で現在気温を取得できます。 他のAPIでもキー名を変えるだけで同じパターンで使えます。

動作確認

こちらが実際に天気情報を表示している画面です。 Tokyo 14.3C Clear(晴れ)と表示できています。

その他、Wi-Fiの接続状況や画面タッチした位置もデバッグ用に表示しています。 M5Stack CoreS3の画面への出力については別記事で紹介します。

Wi-Fi接続後、Open-Meteoから天気情報を取得して表示している画面
Wi-Fi接続後、Open-Meteoから東京の天気情報を取得して表示。

サンプルコード

この記事で紹介したWi-Fi接続・HTTPS GET・JSONパースの実装を含むフルコードはGitHubで公開しています。

m5stack-cores3-weather-monitor — iotbuilds-samples

M5Stack CoreS3 Wi-Fi + HTTPS + Open-Meteo 天気表示サンプル(PlatformIO)

github.com

まとめ

この記事では、M5Stack CoreS3でWi-Fi接続・HTTPS GET・JSONパースを実装し、Open-Meteoから東京の天気情報を取得しました。

ESP32のHTTPS通信は WiFiClientSecuresetCACert() でCA証明書を渡すことで有効になります。 証明書はOpenSSLで接続先から取得でき、TLSハンドシェイクで送られてくる中間CA(R12)を使用しています。

今回はWi-Fi接続の動作確認として直接外部APIに接続しましたが、実務では自社管理のサーバーを経由する構成が現実的です。 外部APIに直接接続していると、証明書更新やエンドポイント変更のたびにOTAでファームウェアを書き換える必要があります。 自社サーバーを経由することで、これらの変更をサーバー側だけで吸収できます。

是非試してみてください。

関連記事