Wi-FiマイコンXIAO ESP32S3のhttpsWebクライアント
温度センサBMP280ブレイクアウトボード使用

2023.9.11 Coskx Lab  

1 はじめに

Wi-FiマイコンXIAO ESP32S3でhttpクライアントを作りました。
これに手を加えてhttpsクライアントにします。
Wi-FiマイコンXIAO ESP32S3,温度センサBMP280はそのまま使います。
またWebサーバの受け皿も同じphpプログラムを使います。
httpsを受け取るWebサーバはインターネット上にあることにします。
XIAO ESP32S3のプログラムだけ差し替えます。

2 使用環境

3 HTTPS

HTTPS(Hytertext Transfer Protocol Secure)は,HTTPで通信を安全に行うための仕組みです。
HTTPSでは「SSLサーバ証明書」によるサーバの認証と,SSL(Secure Sockets Layer)/TLS(Transport Layer Security)と呼ばれるプロトコルによる暗号化の2つの仕組みを持っています。
「SSLサーバ証明書」はhttps接続時にサーバからクライアントに送られてきます。
その「SSLサーバ証明書」が信頼できるかどうかを確かめる手順もあり,そのためには,クライアントは「SSLサーバ証明書」を事前に入手して,持っている必要があります。
「SSLサーバ証明書」には暗号化に関する情報も含まれていますので,それを使って暗号通信をします。暗号化・復号化の作業を自分でプログラムする必要はありません。

SSLサーバ証明書の取得
アクセス対象のWebサーバの「SSLサーバ証明書」の事前入手はChromeなどのWebブラウザで出来ます。
Chromeを使用する場合,アクセス対象のWebサイトを閲覧し,ブラウザのアドレスバーにある鍵マークをクリックし,「この接続は保護されています」→「証明書は有効です」→「詳細」へ進んでいくと「SSLサーバ証明書」の情報が見えます。
「SSLサーバ証明書」は複数の階層になっていて,ルート CA 証明書,中間 CA 証明書,サーバー証明書があり,「証明書の階層」欄に上から順番に並んでいます。

    SSLサーバ証明書取得

この後の作業で使用するのは,ルート CA 証明書(「証明書の階層」欄の一番上)です。それを選び,exportで受け取っておきます。(一番下のサーバの名前で表示されているのはサーバー証明書なのでこれではありません。)
取得した「SSLサーバ証明書」の中身はテキストなので,エディタで内容が見られます。
-----BEGIN CERTIFICATE-----で始まり,-----END CERTIFICATE-----で終わっているはずです。

4 XIAO ESP32S3と温度センサBMP280の配線

httpクライアント制作記事を参照してください。

5 XIAO ESP32S3のスケッチコード(クライアントプログラム)

クライアントであるXIAO ESP32S3は,一定の時間間隔で温度センサBMP280の値を取得し,Weサーバに送信します。
送信ではHTTPS通信の手順に従います。

setupでは,自分が接続すべきWi-FiアクセスポイントのSSIDとパスワードを設定して,LANの一員になろうと試みます。
成功すると,LAN内のDHCPサーバーからIPアドレス(一次的な)をもらいます。
XIAO ESP32S3は,クライアントなので他の機器からアクセスされることはないので,このIPアドレスもらって,自分が使うだけです。 一定時間ごとに,温度センサBMP280から,温度を取得し,"ホスト名","標高","温度","気圧"の文字列をjson型式のデータとしてWebサーバにpostメソッドで送信します。

HTTPSの手順はWi-FiClientSecure *clientが行ってくれます。
client->setCACert(Root_CaStr);
で「SSLサーバ証明書」の確認をするように設定します。
「SSLサーバ証明書」の確認を省略することもできますが,その場合は
client->setInsecure();
を使います。

次のコードがXIAO ESP32S3で使用されています。
ライブラリ"Grove_-_Barometer_Sensor_BMP280"を予め追加しておいてください。
「Root_CaStr」のところは,「ISRG Root X1」というLet's Encrypt(レンタルサーバの無料sslでよく使われています。)のルート CA 証明書が貼り付けてあります。
対象webサーバのルート CA 証明書に貼り替えてください。

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <Seeed_BMP280PP.h>
#include <Wire.h>
#include <Arduino_JSON.h>

//IP address of Web Server
#define SERVER_IP "REPLACE_WITH_YOUR_TARGET_SERVER_NAME_OR_IP_ADDRESS"
//access target path in the Web Server
#define SERVER_PATH "jsontest/jsontest.php"

#define STASSID "REPLACE_WITH_YOUR_SSID"
#define STAPSK "REPLACE_WITH_YOUR_PASSWORD"
#define CLIENTNAME "esp32a"

const char* Root_CaStr=
"-----BEGIN CERTIFICATE-----\n"
"MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n"
"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n"
"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n"
"WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n"
"ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n"
"MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n"
"h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n"
"0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n"
"A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n"
"T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n"
"B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n"
"B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n"
"KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n"
"OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n"
"jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n"
"qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n"
"rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n"
"HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n"
"hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n"
"ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n"
"3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n"
"NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\n"
"ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\n"
"TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\n"
"jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n"
"oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n"
"4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n"
"mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n"
"emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n"
"-----END CERTIFICATE-----\n";
// ISRG Root X1.crt

const char* ssid = STASSID;
const char* password = STAPSK;
const String clientname = CLIENTNAME;
const int led = LED_BUILTIN;

BMP280PP bmp280;
int ledonoff = 1;

void setup() {
  Serial.begin(115200);

  Serial.print("\n\n\n");

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Wire.begin();

  int connectcount = 0;
  while (WiFi.status() != WL_CONNECTED) {
    if (10 <= connectcount++) ESP.restart();
    Serial.print(".");
    delay(500);
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  if (!bmp280.init()) {
    Serial.println("Device bmp280 not connected or broken");
  } else {
    Serial.println("Device bmp280 started");
    delay(1000); //For stable operation of the device
  }

  pinMode(led, OUTPUT);
  digitalWrite(led, ledonoff); //turn LED on
}

void loop() {
  WiFiClientSecure *client = new WiFiClientSecure;
  if(client) {
    //// Use one or the other.
    //// set secure client with certificate (HTTPS Request with Certificate)
    client->setCACert(Root_CaStr);
    //// set secure client without certificate (HTTPS Request without Certificate)
    //client->setInsecure();
    
    //create an HTTPClient instance
    HTTPClient https;    

    String url = String("https://") + SERVER_IP + SERVER_PATH;

    Serial.print("[HTTPS] begin...\n");
    if (https.begin(*client, url)) {  // HTTPS
      https.addHeader("Content-Type", "application/json");

      Serial.print("[HTTPS] POST...\n");

      // start connection and send HTTPS header and body
      float temperature =  bmp280.getTemperature();
      float pressure = bmp280.getPressure();
      float altitude = bmp280.calcAltitude(pressure);
      //Serial.println(temperature);

      //json format data
      JSONVar doc;
      doc["clientname"] = clientname;
      doc["altitude"]   = String(altitude);
      doc["temperature"] = String(temperature);
      doc["pressure"] = String(pressure);
      String jsonstr = JSON.stringify(doc);

      int httpCode = https.POST(jsonstr);

      // httpCode will be negative on error
      if (httpCode > 0) {
        // HTTPS header has been sent and Server response header has been handled
        Serial.printf("[HTTPS] POST... code: %d\n", httpCode);

        // file found at server
        if (httpCode == HTTP_CODE_OK) {
          const String& payload = https.getString();
          Serial.println("received payload:\n<<");
          Serial.println(payload);
          Serial.println(">>");
        }
      } else {
        Serial.printf("[HTTPS] POST... failed, error: %s\n", https.errorToString(httpCode).c_str());
      }

      https.end();
      digitalWrite(led, ledonoff = 1 - ledonoff);
    }
  } else {
    Serial.printf("[HTTPS] Unable to connect\n");
  }
  delay(60000);
}

BMP280PPに関する説明はhttpクライアント制作記事を参照してください。

6 HTTPS接続のWebサーバ

HTTPS接続のWebサーバはレンタルサーバを用いました。
使用したphpプログラムに関してはhttpクライアント制作記事を参照してください。

7 まとめ

Wi-FiマイコンXIAO ESP32S3をHTTPS接続のWebクライアントとして動作させ,XIAO ESP32S3に接続された温度センサBMP280の値を,をHTTPS接続のWebサーバの受け皿URLに60秒間隔でデータを送信するようにしました。
Webサーバの受け皿であるphpプログラムは受け取ったデータをファイルに追記するようにしました。