センサデバイスを制御するプログラム全体を以下に記載する。アクセスポイントのプログラムと共通する箇所が多いため、個々の機能に関する説明は割愛する。デバイスの動作を撮影した画像と動画を次に掲載しているので、それらを参照すればプログラムの内容を理解しやすいと思う。
/**********************************************
センサネットワーク・クライアント(センサデバイス)
**********************************************/
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiGeneric.h>
#include <WebSocketsClient.h>
#include <SSD1306.h>
// 接続アクセスポイント
const char *ssid = "AP-ESP32";
const char *password = "12345678";
const char *ipString = "192.168.5.1";
const IPAddress ip(192, 168, 5, 1);
const IPAddress subnet(255, 255, 255, 0);
// ステーションモード(自動設定IPアドレス)
IPAddress st;
// ポート番号設定
int webPort = 80;
int socketPort = 81;
WebSocketsClient webSocketClient;
int unitTime = 100; // 100msec基本割込み
int intervalBase = 10; // 基本インターバル 100msec x 10 = 1000msec
int intervalCounter = 0; // インターバルカウンター
bool intervalFlag = false; // インターバル経過フラグ
uint8_t datasizeBase = 0; // 基本データサイズ 1024byte x N(例外0:1byte)
int sleepTime = 100; // 接続10秒後にOLEDスリープ状態(100msec単位)
int sleepCounter = 0; // スリープカウンター
// 送信データバッファ
// WebSockets.h で定義
// WEBSOCKETS_MAX_DATA_SIZE (15 * 1024) ⇒ 15,360
uint8_t buffer[WEBSOCKETS_MAX_DATA_SIZE];
// タイマー管理用の構造体ポインター設定
hw_timer_t *timer = NULL;
// OLED関連定義
#define OLED_Address 0x3C
#define OLED_SDAPin 21
#define OLED_SCLPin 22
#define OLED_Font24 ArialMT_Plain_24
#define OLED_Font16 ArialMT_Plain_16
// 動作状態出力ピン
#define STATPin 19
SSD1306 display(OLED_Address, OLED_SDAPin, OLED_SCLPin);
// 遅延処理(単位:ミリ秒)
void delayMSec(int mSecond) {
vTaskDelay(mSecond / portTICK_RATE_MS);
}
// 基本割込みサービスルーチン(100msec間隔)
void IRAM_ATTR baseInterrupt() {
static int base = 0, sleep = 0;
// WebSocket送信時間設定
base++;
if (base >= intervalBase) {
intervalCounter++;
if (intervalCounter == 100) intervalCounter = 0;
// インターバル0の場合は送信せず(バッテリー駆動試験のため接続のみ)
if (intervalBase != 0) intervalFlag = true;
base = 0;
}
// OLEDスリープカウンター更新
if (sleepCounter < sleepTime) sleepCounter++;
}
/****************
ディスプレイ関連
****************/
// OLED初期化
void displayInit() {
display.init(); // SSD1306初期化
display.clear(); // 表示エリアクリア
display.setFont(OLED_Font24); // フォントサイズ設定
display.flipScreenVertically(); // 180度回転表示
display.display(); // 表示更新
}
// OLEDスリープモード
void displaySleep() {
display.displayOff();
}
// アクセスポイントへの接続状態表示
void displayTry() {
static bool flush = true;
char digit[3], address[12]; // [192.]168.5.N
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, "Counter");
digit[0] = intervalCounter / 10 + 0x30;
digit[1] = intervalCounter % 10 + 0x30;
digit[2] = 0x00;
display.drawString(98, 20, digit);
display.drawString( 0, 40, "Brainvision");
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: Serial.println("Event: connect");
break;
case WStype_TEXT: Serial.println("Event: text");
break;
case WStype_BIN: {
if (length == 2) {
// 0x49="I" インターバル更新, 0x53="S" データサイズ更新
if (payload[0] == 0x49) {
intervalBase = (uint8_t)payload[1];
Serial.printf("Event: bin - Interval %d\n", intervalBase);
}
else if (payload[0] == 0x53) {
datasizeBase = (uint8_t)payload[1];
Serial.printf("Event: bin - DataSize %d\n", datasizeBase);
}
}
}
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: {
Serial.println("Event: ping"); // 接続イベントの次に発生
// デバイス識別文字列"Dev"送信
if (webSocketClient.sendTXT("Dev")) Serial.println("STMode Finished");
}
break;
case WStype_PONG: Serial.println("Event: pong");
break;
}
}
/************
STモード関連
************/
// ステーションモード初期設定
void setupStationMode() {
wl_status_t status;
sleepCounter = 0;
WiFi.mode(WIFI_STA);
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();
sleepCounter = 0;
delayMSec(500);
}
st = WiFi.localIP();
Serial.print("Connected to "); Serial.println(ipString);
Serial.print("This Address "); Serial.println(st);
// WebSocket接続(戻り値なし ⇒ 合否判定不可)
webSocketClient.begin(ipString, socketPort, "/");
// クライアントイベント設定
webSocketClient.onEvent(webSocketClientEvent);
}
// STモード循環
void repeatSTMode() {
uint16_t size;
if (WiFi.status() != WL_CONNECTED) setupStationMode();
// 先頭カウンター値, 連続0x00, 終端0xFF 送信
buffer[0] = (uint8_t)intervalCounter;
if (datasizeBase == 0) size = 1;
else {
size = datasizeBase * 1024;
for (uint16_t n = 1; n < size - 2; n++) buffer[n] = 0x00;
buffer[size - 1] = 0xFF;
}
digitalWrite(STATPin, HIGH); // 送信開始
webSocketClient.sendBIN(buffer, size);
digitalWrite(STATPin, LOW); // 送信終了
}
/********
初期設定
********/
void setup() {
Serial.begin(115200);
while(!Serial) {} // シリアルポート準備待ち
Serial.println("");
pinMode (STATPin, OUTPUT); // 動作状態出力ピン設定
digitalWrite(STATPin, LOW);
displayInit(); // OLED初期化
setupStationMode(); // STモード起動
// タイマー割込みセットアップ(ソケット通信に対して割込み優先を確認済み)
timer = timerBegin(0, 80, true); // timer = 1usec
timerAttachInterrupt(timer, &baseInterrupt, true);
timerAlarmWrite(timer, unitTime * 1000, true);
timerAlarmEnable(timer);
}
/************
メインループ
************/
void loop() {
// WebSocket循環
if (WiFi.status() == WL_CONNECTED) webSocketClient.loop();
// インターバル間隔処理
if (intervalFlag) {
intervalFlag = false;
repeatSTMode(); // STモード循環
if (sleepCounter < sleepTime) displayTry();
else displaySleep();
}
}
// End of File

アクセスポイントに接続した状態のセンサデバイス。デバイス自身のIPアドレス・ホスト部(末尾数値)は、5が付与されている。この数値は、接続クライアントの状況によって変動する。なお、小型有機LEDの画素数の関係で、先頭アドレス192の表示は省略。そして、センサ情報を代替するカウンター値は22を示している。
センサデバイス起動時の動画。アクセスポイントが立ち上がっていなければ、接続を500msec間隔で試みる。アクセスポイントが起動されて接続が完了すると、自身のIPアドレスを表示し、カウンター値を更新。10秒後に省電力化のため、小型有機LEDをスリープさせる。その後、何らかの理由でアクセスポイントとの接続が閉じると、カウンター値の更新は続けながら、有機LEDを表示させて再接続モードに入る。