これまでの節で、各機能について分けて説明してきた。最後に、セットアップとメインループに関するプログラムを掲載する。

セットアップの主要項目は、SPIFFS初期設定、アクセスポイントモード起動、小型有機LED初期化、タイマー割込み設定である。これまで触れてこなかったSPIFFSとは、SPI Flash File Systemの略で、フラッシュメモリをファイル保存用に利用する機能である。ウェブページを生成するファイル群をフラッシュメモリに保存し、接続するブラウザに供給する目的で用いる。

// クライアント状態変数初期化
void initClientStatus() {
  for (uint8_t num = 0; num < CLIENT_MAX; num++) {
    clientSTAT.Address[num] = 0; // IPアドレス末尾
    clientSTAT.Station[num] = 0; // 未確認, ブラウザ, デバイス 識別
    clientSTAT.Connect[num] = 0; // 切断, 接続, データ受信 識別
    clientSTAT.Elapsed[num] = 0; // 連続接続時間
    clientSTAT.GetData[num] = 0; // 受信1byteデータ
    clientSTAT.TimeOut[num] = 0; // 非応答タイムアウトカウンター
  }
}

// セットアップ
void setup() {
  Serial.begin(115200);
  while(!Serial) {}           // シリアルポート準備待ち  
  Serial.println("");
  pinMode (STATPin, OUTPUT);  // 動作状態出力ピン設定
  digitalWrite(STATPin, LOW);
  SPIFFS.begin();             // SPIFFS初期設定
  initClientStatus();         // クライアント状態変数初期化
  setupAPMode();              // APモード起動
  displayInit();              // OLED初期化
  displayAPMode();            // OLED表示開始
  // タイマー割込み設定(ソケット通信に対して割込み優先を確認済み)
  timer = timerBegin(0, 80, true); // timer = 1usec
  timerAttachInterrupt(timer, &baseInterrupt, true);
  timerAlarmWrite(timer, unitTime * 1000, true);
  timerAlarmEnable(timer);
}

メインループから定期的に呼び出される関数は3つある。2つはアクセスポイント関連、1つはブラウザへの配信である。アクセスポイント関連の関数を以下に記載する。

// APモードループ
void loopAPMode() {
  uint8_t payloadBIN[2];
  // インターバル更新告知
  if (intervalFlag) {
    intervalFlag = false;
    payloadBIN[0] = 'I'; // "I"nterval
    payloadBIN[1] = (uint8_t)intervalBase;
    // 全接続クライアントへインターバル告知
    for (uint8_t num = 0; num < CLIENT_MAX; num++) {
      if (clientSTAT.Connect[num] != 0) webSocket.sendBIN(num, payloadBIN, 2); 
    }
    Serial.printf("Interval Broadcast %d x 100msec\n", payloadBIN[1]);
  }
  // データサイズ更新告知
  if (datasizeFlag) {
    datasizeFlag = false;
    payloadBIN[0] = 'S'; // "S"ize
    payloadBIN[1] = datasizeBase;
    // 全接続クライアントへデータサイズ告知
    for (uint8_t num = 0; num < CLIENT_MAX; num++) {
      if (clientSTAT.Connect[num] != 0) webSocket.sendBIN(num, payloadBIN, 2); 
    }
    if (datasizeBase == 0) Serial.printf("DataSize Broadcast 1byte\n");
    else Serial.printf("DataSize Broadcast %d x 1024byte\n", payloadBIN[1]);
  }
}

// APモード動作確認
void confirmAPMode() {
  if (!WiFi.enableAP(true)) setupAPMode(); // 再稼働
}

次は、ブラウザへの配信関数である。「WebSocket通信(概要)」の節で述べたが、不用意にブラウザが切断した場合、アクセスポイントはそれを感知できない。そこで、送信処理が失敗したときは、ブラウザ切断と判断しなければならない。この機能を盛り込まないと、アクセスポイントがロック状態に陥ってしまう。

// ウェブブラウザ配信
void webAPMode() {
  char payloadTXT[PayloadTXTSize], buf[PayloadTXTSize];
  uint8_t num;
  int size, pnt = 0;
  // 各クライアントの状態抽出
  for (num = 0; num < CLIENT_MAX; num++) {
    size = snprintf_P(&buf[pnt], sizeof(buf), "%d,", clientSTAT.Address[num]); // IPアドレス末尾
    pnt += size;
    size = snprintf_P(&buf[pnt], sizeof(buf), "%d,", clientSTAT.Station[num]); // 未確認:0, ブラウザ:1, デバイス:2 
    pnt += size;
    size = snprintf_P(&buf[pnt], sizeof(buf), "%d,", clientSTAT.Connect[num]); // 切断:0, 接続:1, データ受信:2
    pnt += size;
    size = snprintf_P(&buf[pnt], sizeof(buf), "%d,", clientSTAT.Elapsed[num]); // 連続接続時間
    pnt += size;
    if (num < CLIENT_MAX - 1) {
      size = snprintf_P(&buf[pnt], sizeof(buf), "%d,", clientSTAT.GetData[num]); // 受信先頭1byteデータ
      pnt += size;
    }
    else {
      snprintf_P(&buf[pnt], sizeof(buf), "%d", clientSTAT.GetData[num]); // 最終データ
    }
  }
  // PROGMEM文字列使用フォーマッティング
  snprintf_P(payloadTXT, sizeof(payloadTXT), SERVER_JSON, buf);
  // 各ウェブブラウザへ配信
  for (num = 0; num < CLIENT_MAX; num++) {
    if (clientSTAT.Station[num] == 1 && clientSTAT.Connect[num] == 1) {
      // ブラウザ切断時には送信結果確認に10秒程度要する
      // この期間はタイマー割込み発生せず(サーバーはロック状態)
      bool send = webSocket.sendTXT(num, payloadTXT, strlen(payloadTXT));
      if (!send) {
        clientSTAT.Connect[num] = 0; // ウェブブラウザへの配信エラー(強制切断)
        Serial.printf("Web Send Error %d\n", num);
      }
    }
  }
}

最後にメインループを掲載する。内容はプログラムのコメントを参照して欲しい。

void loop() {
  bool timeout = false;
  webSocket.loop();                // WebSocketクライアント接続待ち
  webServer.handleClient();        // WebSocket新規クライアント対応
  loopAPMode();                    // イベント発生確認
  if (webFlag) {                   // ウェブブラウザ定期配信処理
    webFlag = false;
    webAPMode();                   // ウェブブラウザ配信
  }
  if (oneSecFlag) {                // 1秒間隔処理
    oneSecFlag = false;
    setupCounter++;                // ESP32起動カウンター更新
    for (uint8_t num = 0; num < CLIENT_MAX; num++) { 
      // クライアント連続接続時間更新
      if (clientSTAT.Connect[num] != 0) ++clientSTAT.Elapsed[num];
    }
    confirmAPMode();               // APモード動作確認
    timeout = timeoutDisconnect(); // タイムアウト切断処理
    if (timeout) webAPMode();      // タイムアウト即時配信
    displayAPMode();               // 表示更新
  }
}

これまで説明してきたセンサネットワークの動作をブラウザの動画画面で紹介する。