以下にセンサデバイス制御プログラムのソースコード全体を記載する。常時接続のプログラムと比較すると、次の点が異なる。
- 固定IPアドレス採用(デバイスの再起動を監視)
- ディープスリープモード装備
- ブート回数をサーバーに送信(バッテリー寿命を予測)
- 接続プロトコルを設定(ディープスリープモード突入への条件確認)
- タイマー割込み廃止(一定時間毎の処理不要)
ここでの「接続プロトコル」とは、
- デバイス起動(ウェイクアップ)
- サーバーへの接続試行
- 接続イベント発生
- PINGイベント発生
- サーバーからスリープ時間受信
- サーバーからデータサイズ受信
- サーバーへブート回数送信
- サーバーからブート回数確認の受信
と定義した。この一連のプロトコルに要する時間を「(接続)プロトコル時間」と称する。接続プロトコルを経て、センサデバイスはディープスリープモードへ突入する。
#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秒間スリープの後、ブート回数をカウントアップしてサーバーへ送信。これを繰り返す。途中で、サーバーとの接続が絶たれると、ブート回数を保持したまま再接続を試みる。