センサデバイスを制御するプログラム全体を以下に記載する。アクセスポイントのプログラムと共通する箇所が多いため、個々の機能に関する説明は割愛する。デバイスの動作を撮影した画像と動画を次に掲載しているので、それらを参照すればプログラムの内容を理解しやすいと思う。

/**********************************************
 センサネットワーク・クライアント(センサデバイス)
 **********************************************/
#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を表示させて再接続モードに入る。

コメントを残す

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