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

2023.9.11 Coskx Lab  

1 はじめに

Wi-FiマイコンXIAO ESP32S3はArduinoIDEでの開発が便利です。
ボードとしてXIAO ESP32S3を選んでおきます。
ここでは,温度センサの値を発信するXIAO ESP32S3がWebクライアントとして働くようにします。 温度センサはBMP280を使用します。
このWebクライアントはWebサーバ(例えばPC上で動作しているApache2)に,温度データを伴ってアクセスします。Webサーバの受付プログラムがこの温度データを処理します。

    "マイコンのクライアント"と"PCのWebサーバ"のふるまい
    閲覧するのはスマホやタブレットのWebブラウザでもよい

2 使用環境

3 準備

3.1 ArduinoIDE

(1)Arduino IDEのメニューバーから[ファイルFile] -> [環境設定(Preferences)]
Additional board manager URLsに
https://files.seeedstudio.com/arduino/package_seeeduino_boards_index.json
および
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
を設定してOK
(2)ツール[Tools] > ボード[Board] > Boards Manager
検索boxに「ESP」を入力すると複数のボード候補が出てくるので
esp32 by Espressif Systems
をinstall
(3)ツール[Tools] > ボード[Board]でESP32を探すとXIAO ESP32S3が見つかるので,これを選ぶ。

3.2 XIAO ESP32S3

XIAO ESP32S3の説明書を読んでプログラムの転送方法を確認します。
(USBケーブルでつないで書き込むだけでした。)

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

温度センサBMP280は温度をデジタル値に変換しているので,そのデータをXIAO ESP32S3が読み出します。
XIAO ESP32S3と温度センサBMP280はI2C接続ですので,電源線,GND線の他はSCLとSDAの2本のみで通信できます。

CSBをVCCにつなぐとBMP280はI2C接続になります。(データシートによれば確実にVCCにつなぐべきとなっています)
SDO(シルク印刷は間違えていてSDDとなっている)をVCCにつなぐとBMP280のI2Cアドレスは77になります。GNDにつなぐとBMP280のI2Cアドレスは76になります。Groveライブラリのデフォルトは77なのでVCCにつないでいます。

なおBMP280ではSDA,SCLをpullupせずに動作しています。

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

ブレッドボードには載せないので,XIAO ESP32S3のピンを上向きに付けています。
   XIAO ESP32S3と温度センサBMP280

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

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

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

次のコードがXIAO ESP32S3で使用されています。
ライブラリ"Grove_-_Barometer_Sensor_BMP280"をArduinoIDEに予め追加しておいてください。

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

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

#define STASSID "REPLACE_WITH_YOUR_SSID"
#define STAPSK "REPLACE_WITH_YOUR_PASSWORD"

const char* ssid = STASSID;
const char* password = STAPSK;
const String clientname = "esp32a";
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");
  }

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

void loop() {
  // wait for WiFi connection
  if ((WiFi.status() == WL_CONNECTED)) {

    WiFiClient client;
    HTTPClient http;
    String url = String("http://") + SERVER_IP + SERVER_PATH;
    //Serial.println("url : " + url);

    Serial.print("[HTTP] begin...\n");
    http.begin(client, url);  // HTTP access with post method
    http.addHeader("Content-Type", "application/json");

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

    // start connection and send HTTP 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 = http.POST(jsonstr);

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

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

    http.end();
    digitalWrite(led, ledonoff = 1 - ledonoff);
  }

  delay(60000);//also allow the cpu to switch to other tasks
}

BMP280のオリジナルライブラリでは,BMP280操作のクラス名はBMP280です。
BMP280の初期設定を手直ししたかったので,クラスBMP280の継承クラスBMP280PPを作って,このクラスを使用しています。
#include <Seeed_BMP280PP.h>
BMP280PP bmp280;
の2行が継承クラスBMP280PPを使っているのですが,オリジナルライブラリを使用する場合はこの2行を
#include <Seeed_BMP280.h>
BMP280 bmp280;
のように戻してください。

次の2つのファイルを作りましたがそれらは,Seeed_BMP280.h,Seeed_BMP280.cppと同じフォルダ(Arduino\libraries\Grove_-_Barometer_Sensor_BMP280)内に保存されています。

BMP280PP.h

#include <Seeed_BMP280.h>

class BMP280PP : public BMP280
{
public:
  bool init(void);
private:
  void writeRegister(uint8_t reg, uint8_t val);
};

BMP280PP.ino

#include "Seeed_BMP280PP.h"

bool BMP280PP::init(void)
{
  
  if (!BMP280::init()) return false;
  //changeig Temperature oversampling to index=4
  //(oversampling x8)
  //100 111 11
  writeRegister(BMP280_REG_CONTROL, 0x9F);
  //adding, IIR filter coeficient = 8 (index=3)
  //000 011 00
  writeRegister(BMP280_REG_CONFIG, 0xC);
  return true;
}

void BMP280PP::writeRegister(uint8_t reg, uint8_t val)
{
  Wire.beginTransmission(BMP280_ADDRESS); // start transmission to device
  Wire.write(reg);       // send register address
  Wire.write(val);         // send value to write
  Wire.endTransmission();     // end transmission
}

プログラムをXIAO ESP32S3に書き込んで起動させると,直ちにIPアドレスを取得したことをArduinoIDEのシリアルモニターに表示します。
"IP address: 192.168.1.19"は,取得したIPアドレスでアクセスポイントに接続したことを表しています。
しかし,まだWebサーバの準備ができていないので,その後は失敗した(connection refused)と表示されます。
そのまま60秒間隔で失敗したと表示を続けます。

Connected to xxxxxxxxxxxxx
IP address: 192.168.1.16
Device bmp280 started
[HTTP] begin...
[HTTP] POST...
[HTTP] POST... failed, error: connection refused
[HTTP] begin...
[HTTP] POST...
[HTTP] POST... failed, error: connection refused

6 PCのWebサーバの準備

XIAO ESP32S3のクライアントプログラムは単体では意味を成しません。対応するWebサーバ側にxxxxx.phpのような受け皿が必要です。
ここではWindowsPCにWindows Subsystem for Linuxを動かし,apache2をインストール・起動し,PHPもインストールして,Webサーバを構築してあるものとします。このWebサーバのWebDocumentに.phpファイルを作ります。
WindowsPC(Webサーバマシン)のIPアドレスは予め調べておき,192.168.1.25だったらXIAO ESP32S3のプログラムに埋め込んでおきます
 #define SERVER_IP "192.168.1.25" //IP address of Web Server
.phpファイルは/jsontest/jsontest.phpにしたことにすると,XIAO ESP32S3のプログラムにこのURLも埋め込みます。
 #define SERVER_PATH "/jsontest/jsontest.php" //access target path in the Web Server

注意
Webサーバでは,ディレクトリ・ファイルのオーナやグループを,apache2が操作できるように設定する必要があります。
また,WinPCを使っている場合はWindowsPCのファイアウォールでポート番号80 (HTTP)を通す設定が必要です。

jsontest.phpでは,"ホスト名","標高","温度","気圧"のjson型式のデータを受け取り,それを四つの文字列に分解し,現在時刻とともにファイルsample.txtに追加保存します。
 $fp = fopen("sample.txt", "a");
複数のクライアントから非同期でアクセスが来るため,ファイル書き込みでは衝突しないように排他制御を行います。

jsontest.phpは次のようなphpプログラムです。

<?php
    print "Hello! from jsontest.php<br>\n";

    $body = file_get_contents('php://input');   //BODYの取得
 
    if (is_null($body)) {
        # error データが無い
        http_response_code(500);        //HTTP response code 500
        echo "No data (JSON)<br>";
        exit();
    }
 
    $jsonarry = json_decode($body, true);   //連想配列に変換
    if (is_null($jsonarry)) {
        # error JSONをデコードできない
        http_response_code(500);        //HTTP response code 500
        echo "Cannot decode JSON<br>";
        exit();
    }

    echo $jsonarry;
    var_dump($jsonarry);

    $str = date("Y/m/d H:i:s");
    foreach ($jsonarry as $i => $value) {
        $str .= ", " . $value;
    }
    // ファイルへ書き込み
    $fp = fopen("sample.txt", "a");
    flock($fp, LOCK_EX); //排他制御ここから
    fwrite($fp, $str . "\n");
    flock($fp, LOCK_UN); //排他制御ここまで
    fclose($fp);

?>

補足
Webブラウザ(client)からのアクセスに対してWebサーバはhtmlデータを返しますが,ここではマイコン(client)からのアクセスなので,htmlデータではなく最小限のデバッグ用データを返しています。

Webサーバの準備ができたところで,XIAO ESP32S3を起動すると,ArduinoIDEのシリアルモニターに成功の様子が表示されるようになります。
うまくアクセスして,データがWebサーバに届くと,届けられたデータを返してくるのでその様子を見ることができます。
jsontest.phpの内容に不具合があると
[HTTP] POST... code: 500
が返ってくることもあります。

[HTTP] begin...
[HTTP] POST...
[HTTP] POST... code: 200
received payload:
<<
Hello! from jsontest.php<br>
Arrayarray(4) {
  ["clientname"]=>
  string(6) "esp32a"
  ["altitude"]=>
  string(6) "168.76"
  ["temperature"]=>
  string(5) "33.39"
  ["pressure"]=>
  string(8) "99314.00"
}

>>

7 3つのXIAO ESP32S3がWebサーバにアクセス

3つのXIAO ESP32S3(それぞれ温度センサBMP280を持っている)がクライアントとしてWebサーバにアクセスします。

"3つのマイコンのクライアント"と"PCのWebサーバ"

XIAO ESP32S3から見ると"http://192.168.1.25/jsontest/jsontest.php"とアクセスしていることになります。
表からは見えませんがpostメソッドでjson型式の"ホスト名","標高","温度","気圧"のデータも同時に送信されます。
3つのXIAO ESP32S3はそれぞれ,60秒間隔でアクセスします。
Webサーバー側のjsontest.phpはいつアクセスしてくるかわからないけれど,アクセスしてきたら,ファイルsample.txtにアクセス内容を保存します。
保存されたファイルsample.txtの内容は次のようになりました。
Webブラウザで
"http://192.168.1.25/jsontest/sample.txt
のようにすればファイルsample.txtの内容が表示されます。
約60秒間隔で3つのクライアントXIAO ESP32S3が,温度データを送信してきているのがわかります。

2023/09/05 12:14:27, esp32a, 171.89, 33.47, 99277.00
2023/09/05 12:14:56, esp32b, 175.02, 33.31, 99240.00
2023/09/05 12:15:03, esp32c, 170.62, 33.93, 99292.00
2023/09/05 12:15:28, esp32a, 171.38, 33.50, 99283.00
2023/09/05 12:15:57, esp32b, 175.45, 33.26, 99235.00
2023/09/05 12:16:04, esp32c, 170.62, 33.94, 99292.00
2023/09/05 12:16:29, esp32a, 171.30, 33.51, 99284.00
2023/09/05 12:16:58, esp32b, 174.94, 33.34, 99241.00
2023/09/05 12:17:05, esp32c, 170.20, 33.99, 99297.00
2023/09/05 12:17:30, esp32a, 171.30, 33.56, 99284.00
2023/09/05 12:17:59, esp32b, 174.77, 33.50, 99243.00
2023/09/05 12:18:06, esp32c, 170.45, 34.06, 99294.00
2023/09/05 12:18:31, esp32a, 171.72, 33.57, 99279.00
2023/09/05 12:19:00, esp32b, 174.86, 33.56, 99242.00
2023/09/05 12:19:07, esp32c, 170.88, 34.15, 99289.00
2023/09/05 12:19:32, esp32a, 171.21, 33.65, 99285.00
2023/09/05 12:20:01, esp32b, 174.52, 33.58, 99246.00
2023/09/05 12:20:08, esp32c, 170.79, 34.18, 99290.00

8 通信距離

Androidスマホをモバイルアクセスポイントとして,XIAO ESP32S3にロッドアンテナをつけた状態で通信距離を測定しました。
見通しの良い直線で75mまでの通信ができました。
それを超えると通信が途切れ始めます。

9 まとめ

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