以下にセンサデバイス制御プログラムのソースコード全体を記載する。常時接続のプログラムと比較すると、次の点が異なる。

  • 固定IPアドレス採用(デバイスの再起動を監視)
  • ディープスリープモード装備
  • ブート回数をサーバーに送信(バッテリー寿命を予測)
  • 接続プロトコルを設定(ディープスリープモード突入への条件確認)
  • タイマー割込み廃止(一定時間毎の処理不要)

ここでの「接続プロトコル」とは、

  1. デバイス起動(ウェイクアップ)
  2. サーバーへの接続試行
  3. 接続イベント発生
  4. PINGイベント発生
  5. サーバーからスリープ時間受信
  6. サーバーからデータサイズ受信
  7. サーバーへブート回数送信
  8. サーバーからブート回数確認の受信

と定義した。この一連のプロトコルに要する時間を「(接続)プロトコル時間」と称する。接続プロトコルを経て、センサデバイスはディープスリープモードへ突入する。

#include <Arduino.h>
#include <WiFi.h>
#include <WiFiGeneric.h>
#include <WebSocketsClient.h>
#include <SSD1306.h>

// センサデバイスNo.21(IP固定アドレス・ホスト部)
#define station 21
// センサデバイスNo.22(IP固定アドレス・ホスト部)
// #define station 22

// RTCスローメモリ変数:ディープスリープ中にブート回数を保持
RTC_DATA_ATTR uint16_t bootCount = 0;

// 接続アクセスポイント
const char *ssid = "AP-ESP32";
const char *password = "12345678";
const char *ipString = "192.168.5.1";
const IPAddress gateway(192, 168, 5, 1);
const IPAddress subnet(255, 255, 255, 0);

// ステーションモード(固定IPアドレス)
const IPAddress st(192, 168, 5, station);

// ポート番号設定
int webPort = 80;
int socketPort = 81;
WebSocketsClient webSocketClient;

bool connectFlag = false;  // アクセスポイント接続フラグ
bool pingFlag = false;     // PINGイベント発生フラグ
bool sleepFlag = false;    // スリープ時間受信フラグ
bool datasizeFlag = false; // データサイズ受信フラグ
bool receiveFlag = false;  // アクセスポイントからの受信確認フラグ
uint8_t sleepTime = 3;     // スリープ時間(秒単位)
uint8_t datasizeBase = 1;  // 基本データサイズ 1024byte x N(例外0:2byte)

// 送信データバッファ(WebSockets.h で定義)
// WEBSOCKETS_MAX_DATA_SIZE (15 * 1024) ⇒ 15,360
uint8_t buffer[WEBSOCKETS_MAX_DATA_SIZE];

// OLED関連定義
#define OLED_Address  0x3C
#define OLED_SDAPin   21
#define OLED_SCLPin   22
#define OLED_Font24   ArialMT_Plain_24

SSD1306 display(OLED_Address, OLED_SDAPin, OLED_SCLPin);

// 起動方法取得(ディープスリープからのウェイクアップ状態)
void getWakeupReason() { 
  esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
  switch(wakeup_reason) {
    case ESP_SLEEP_WAKEUP_EXT0     : break; // 外部割り込み
    case ESP_SLEEP_WAKEUP_EXT1     : break; // 外部割り込み
                                            // タイマー割り込み
    case ESP_SLEEP_WAKEUP_TIMER    : Serial.printf("\nWake Up %d\n", ++bootCount); break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD : break; // タッチ割り込み
    case ESP_SLEEP_WAKEUP_ULP      : break; // ULPプログラム
    case ESP_SLEEP_WAKEUP_GPIO     : break; // ライトスリープからGPIO割り込み
    case ESP_SLEEP_WAKEUP_UART     : break; // ライトスリープからUART割り込み
    default                        : Serial.println("\nPower ON"); break;
  }
}

// 遅延処理(単位:ミリ秒)
void delayMSec(int mSecond) {
  vTaskDelay(mSecond / portTICK_RATE_MS);
}

// OLED初期化
void displayInit() {
  display.init();                 // SSD1306初期化
  display.clear();                // 表示エリアクリア
  display.setFont(OLED_Font24);   // フォントサイズ設定
  display.flipScreenVertically(); // 180度回転表示
  display.display();              // 表示更新
}

// OLEDスリープモード
void displaySleep() {
  display.displayOff();
}

// アクセスポイントへの接続状態表示
void displayTry(uint8_t mode) {
  static bool flush = true;
  char digit[6], address[12]; // [192.]168.5.N
  uint16_t count = bootCount;
  display.clear();
  if (WiFi.status() != WL_CONNECTED) {
    if (flush) display.drawString( 0,  0, "Connect");
  }
  else {
    snprintf_P(&address[0], sizeof(address), "%d.%d.%d", st[1], st[2], st[3]); // 先頭アドレス192省略 
    display.drawString( 0,  0, address);
  }
  flush = !flush;
  display.drawString( 0, 20, "Boot");
  digit[0] = count / 10000 + 0x30;
  digit[1] = (count / 1000) % 10 + 0x30;
  digit[2] = (count / 100) % 10 + 0x30;
  digit[3] = (count / 10) % 10 + 0x30;
  digit[4] = count % 10 + 0x30;
  digit[5] = 0x00;
  display.drawString(62, 20, digit);
  if (mode == 0) display.drawString(0, 40, "Brainvision");
  else if (mode == 1) display.drawString(0, 40, "Protocol...");
  else if (mode == 2) display.drawString(0, 40, "Sleep Mode");
  display.displayOn(); // Sleepからの復帰時に必要
  display.display();
}

// WebSocketイベント発生
void webSocketClientEvent(WStype_t type, uint8_t * payload, size_t length) {
   switch(type) {
    case WStype_DISCONNECTED: {
      Serial.println("Event: disconnect"); // 接続最大数を超えるとイベント連発
    }
      break;
    case WStype_CONNECTED: {
      connectFlag = true;
      Serial.println("Event: connect");
    }
      break;
    case WStype_TEXT: Serial.println("Event: text");
      break;
    case WStype_BIN: {
      // 0x54="T" スリープ時間更新
      if (length == 2 && payload[0] == 0x54) {
        sleepFlag = true;
        sleepTime = (uint8_t)payload[1];
        Serial.printf("Event: bin - SleepTime %d\n", sleepTime);
      }
      // 0x53="S" データサイズ更新
      else if (length == 2 && payload[0] == 0x53) {
        datasizeFlag =  true;
        datasizeBase = (uint8_t)payload[1];
        Serial.printf("Event: bin - DataSize %d\n", datasizeBase);
      }
      // 0x52="R" データ受信確認
      else if (length == 1 && payload[0] == 0x52) {
        receiveFlag =  true;
        Serial.printf("Event: bin - Data Received\n");
      }
    }
      break;
    case WStype_ERROR: Serial.println("Event: error");
      break;
    case WStype_FRAGMENT: Serial.println("Event: fragment");
      break;
    case WStype_FRAGMENT_FIN: Serial.println("Event: fragment fin");
      break;    
    case WStype_FRAGMENT_TEXT_START: Serial.println("Event: text start");   
      break;
    case WStype_FRAGMENT_BIN_START: Serial.println("Event: bin start");         
      break;
    case WStype_PING: {
      pingFlag = true;
      Serial.println("Event: ping"); // 接続イベントの次に発生
    }
      break;
    case WStype_PONG: Serial.println("Event: pong");
      break;
  }
}

// ステーションモード初期設定
void setupStationMode() {
  wl_status_t status;
  WiFi.mode(WIFI_STA);
  WiFi.config(st, gateway, subnet);
  status = WiFi.begin(ssid, password);
  if (status == WL_NO_SHIELD)            Serial.println("AP: No Shield");
  else if (status == WL_IDLE_STATUS)     Serial.println("AP: Idle Status");
  else if (status == WL_NO_SSID_AVAIL)   Serial.println("AP: No SSID Avail");
  else if (status == WL_SCAN_COMPLETED)  Serial.println("AP: Scan Completed");
  else if (status == WL_CONNECTED)       Serial.println("AP: Connected");
  else if (status == WL_CONNECT_FAILED)  Serial.println("AP: Connect Failed");
  else if (status == WL_CONNECTION_LOST) Serial.println("AP: Connection Lost");
  else if (status == WL_DISCONNECTED)    Serial.println("AP: Disconnected");
  // アクセスポイントへの接続
  while (WiFi.status() != WL_CONNECTED) {
    displayTry(0);
    WiFi.begin(ssid, password);
    delayMSec(250);
  }
  Serial.print("Connected to "); Serial.println(gateway);
  Serial.print("This Address "); Serial.println(st);
  displayTry(0);  
  // WebSocket接続(戻り値なし ⇒ 合否判定不可)
  webSocketClient.begin(ipString, socketPort, "/");
  // クライアントイベント設定
  webSocketClient.onEvent(webSocketClientEvent);
}

// ブート回数・ダミーデータ送信
void sendBootCounter() {
  uint16_t size;
  // 先頭ブート回数2byte, 連続0x00, 終端0xFF 送信
  buffer[0] = bootCount % 256; // 下位1byte
  buffer[1] = bootCount / 256; // 上位1byte
  if (datasizeBase == 0) size = 2; // 例外2byte
  else {
    size = datasizeBase * 1024;
    for (uint16_t n = 2; n < size - 2; n++) buffer[n] = 0x00;
    buffer[size - 1] = 0xFF;
  }
  webSocketClient.sendBIN(buffer, size);
}

// スリープモード突入
void enterSleepMode() {
  Serial.println("Enter Sleep Mode");
  displaySleep();
  // ソケット強制切断(切断イベント発生)
  // ウェイクアップ後の切断イベント複数回発生を防止
  webSocketClient.disconnect();
  esp_sleep_enable_timer_wakeup(1000 * 1000 * sleepTime); // 秒からマイクロ秒へ変換
  esp_deep_sleep_start();
}

// セットアップ
void setup() {
  Serial.begin(115200);
  while(!Serial) {}   // シリアルポート準備待ち  
  Serial.println("");
  displayInit();      // OLED初期化
  getWakeupReason();  // ウェイクアップ要因取得(ブート回数更新)
  setupStationMode(); // ステーションモード起動
}

// メインループ
void loop() {
  static bool waitFlag = false;
  // WebSocket循環
  if (WiFi.status() == WL_CONNECTED) webSocketClient.loop();
  else setupStationMode();
  // 接続開始プロトコル確認(接続⇒PING⇒スリープ時間⇒データサイズ)
  if (connectFlag && pingFlag && sleepFlag && datasizeFlag) {
    connectFlag = false;
    pingFlag = false;
    sleepFlag = false;
    datasizeFlag = false;
    displayTry(2);     // スリープモード突入待ち
    sendBootCounter(); // ブート回数送信
  }
  else if (!waitFlag) {
    waitFlag = true;
    displayTry(1); // 接続プロトコル完了待ち
  }
  // アクセスポイントからのブート回数受信報告確認
  if (receiveFlag) enterSleepMode();
}

// End of File

センサデバイスの動作例を以下に示す。サーバーが起動していない状態で、デバイスを立ち上げる。サーバーが起動すると、接続してブート回数0を送信。3秒間スリープの後、ブート回数をカウントアップしてサーバーへ送信。これを繰り返す。途中で、サーバーとの接続が絶たれると、ブート回数を保持したまま再接続を試みる。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です