センサデバイス(クライアント)の全体動作は、下記の手順となる。消費電力の観点から考えると、アドバタイジング後に、いかに早くマスター(サーバー)に見つけてもらうか、つまり接続要求が早急に来るかが鍵になるだろう。また、スリープ時間を固定すれば、項目4の「スリープ時間受信」は省略できるが、ここでは実利用を考慮して双方向通信とした。
一方、センサデバイスのデバイス番号は、現状ではプログラム内で定義しているため、デバイスごとにプログラムを書き換えるのは、手間が掛かり間違いも生じやすい。実際の運用では、初回のマスターとの接続時に、マスターからユニークな番号を受け取りEEPROMに書き込んで、その番号で次回から起動する方法などが考えられる。
- 起動後のアドバタイジング(サービスUUID提供)により、マスターからの接続を待つ
- 一定時間(現状5秒)内に接続要求がなければ、ディープスリープモードへ
- 接続要求があれば、デバイス番号とセンサ情報(現在はブート回数)を送信
- マスターからのスリープ時間を受信
- マスターからの切断要求によりディープスリープモードへ(スリープ後に再起動)
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLE2902.h>
#define DEVICE_NAME "BVNET"
#define deviceNumber 1 // 1, 2, 3, ... (連番で設定)
// サービスUUID
#define SERVICE_UUID "19ce426e-6c37-4cd2-b8b4-b7f51b065a3c"
// 送信用UUID
#define CHARACTERISTIC_UUID_TX "76ac6030-b243-439f-b96e-b964c45e4180"
// 受信用UUID
#define CHARACTERISTIC_UUID_RX "4fe2d1f9-c424-4124-8dc5-6f06b73ad9c5"
// 送受信キャラクタリスティック
BLECharacteristic *pCharacteristicTX;
BLECharacteristic *pCharacteristicRX;
// RTCスローメモリ変数:ディープスリープ保持
RTC_DATA_ATTR uint32_t bootCount = 0; // ブート回数
RTC_DATA_ATTR uint8_t sleepTime = 30; // デフォルト・スリープ時間(秒単位)
uint16_t timeOut = 50; // マスター不在タイムアウト時間(100msec単位)
int unitTime = 100; // 100msec基本割込み
bool sleepFlag = false; // ディープスリープ突入フラグ
BLEAdvertising *pAdvertising; // アドバタイズクラス
bool masterConnected = false; // マスター接続フラグ
bool masterDisconnected = false; // マスター切断フラグ
uint8_t notifyData[5]; // Notifyデータ(デバイス番号1byte, ブート回数4byte)
// マスター接続・切断イベント
class masterNetWorkEvents: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
masterConnected = true;
}
void onDisconnect(BLEServer* pServer) {
masterDisconnected = true;
}
};
// マスター受信イベント
class masterReceiveEvent: public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristicRX) {
std::string rxValue = pCharacteristicRX->getValue();
sleepTime = rxValue[0];
Serial.printf("Sleep Time %d\n", sleepTime);
}
};
// 起動方法取得(ディープスリープからのウェイクアップ状態)
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;
}
}
// タイマー管理用の構造体ポインター
hw_timer_t *timer = NULL;
// 基本割込みサービスルーチン(100msec間隔)
void IRAM_ATTR baseInterrupt() {
static int loop = 0;
static bool flush = true;
// マスター不在タイムアウト(ディープスリープ突入確認)
loop++;
if (loop == timeOut) sleepFlag = true;
}
// 遅延処理(単位:ミリ秒)
void delayMSec(int mSecond) {
vTaskDelay(mSecond / portTICK_RATE_MS);
}
// スリープモード突入
void enterSleepMode() {
esp_sleep_enable_timer_wakeup(1000 * 1000 * sleepTime); // 秒からマイクロ秒へ変換
esp_deep_sleep_start();
}
// マスターとの双方向通信(Notify後にスリープ時間受信イベント発生)
void handshakeMaster() {
notifyData[0] = (uint8_t)deviceNumber;
notifyData[1] = bootCount >> 24 & 0xFF;
notifyData[2] = bootCount >> 16 & 0xFF;
notifyData[3] = bootCount >> 8 & 0xFF;
notifyData[4] = bootCount & 0xFF;
pCharacteristicTX->setValue((uint8_t*)¬ifyData, sizeof(notifyData));
pCharacteristicTX->notify();
}
// セットアップ
void setup() {
Serial.begin(115200);
while(!Serial) {} // シリアルポート準備待ち
Serial.println("");
getWakeupReason(); // ウェイクアップ要因取得(ブート回数更新)
// デバイス初期化
BLEDevice::init(DEVICE_NAME);
// サーバーオブジェクト生成
BLEServer *pServer = BLEDevice::createServer();
// マスター接続・切断イベント登録
pServer->setCallbacks(new masterNetWorkEvents());
// サービスオブジェクト生成
BLEService *pService = pServer->createService(SERVICE_UUID);
// Notifyキャラクタリスティック生成
pCharacteristicTX = pService->createCharacteristic(
CHARACTERISTIC_UUID_TX,
BLECharacteristic::PROPERTY_NOTIFY
);
pCharacteristicTX->addDescriptor(new BLE2902());
// 受信キャラクタリスティック生成
pCharacteristicRX = pService->createCharacteristic(
CHARACTERISTIC_UUID_RX,
BLECharacteristic::PROPERTY_WRITE
);
// マスター受信イベント登録
pCharacteristicRX->setCallbacks(new masterReceiveEvent());
// サービス開始
pService->start();
// アドバタイジング開始
BLEAdvertising *pAdvertising = pServer->getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->start();
// タイマー割込み設定
timer = timerBegin(0, 80, true); // timer = 1usec
timerAttachInterrupt(timer, &baseInterrupt, true);
timerAlarmWrite(timer, unitTime * 1000, true);
timerAlarmEnable(timer);
}
// メインループ
void loop() {
static bool advertisingStop = false;
// マスター接続直後にアドバタイジング停止
if (masterConnected && !advertisingStop) {
advertisingStop = true;
pAdvertising->stop();
}
// 「マスターとの双方向通信(接続中 and 切断前)」
if (masterConnected && !masterDisconnected) {
delayMSec(100); // Notify連打防止(100msec間隔)
handshakeMaster();
}
// 「タイムアウト and 非接続状態」 or 「接続・切断プロセス」
if ((sleepFlag && !masterConnected) || (masterConnected && masterDisconnected)) {
enterSleepMode(); // ディープスリープ突入
}
}
// End of File