M5Stack-CoreInkにBLE経由で文字列を表示する(その1)

# 使ったもの
 - M5Stack-CoreInk : [スイッチサイエンス](https://www.switch-science.com/catalog/6735/)で購入
 - LightBlue: iPhoneで動くBLEテストアプリ [AppStore](https://apps.apple.com/jp/app/lightblue/id557428110)

# 参考にしたサイト
 - [M5StackでBLEを使う - Qiita](https://qiita.com/nsawa/items/2afd4ae9c10af87d0133)
 - [M5 CoreInk RTCのコト＋NTPから時刻ゲット](https://note.com/rasen/n/nfe2b5a67eaa9)
 - [M5Stack CoreInkをWeb経由で取得して表示するやつ作った記録](https://zenn.dev/terrierscript/articles/2020-11-17-m5-coreink-web-pix)

# 注意点
 - Mac版のAruduino1.8.3で標準で使えるM5-CoreInkライブラリは0.0.1でshutdown()が使えないので、[GitHub](https://github.com/m5stack/M5-CoreInk)からzipをダウンロードする必要がある。
   - 既存のライブラリをzipで固めるなどバックアップして削除
   - ダウンロードしたzipをライブラリに追加

# やりたいこと
###  step1
 1. BLEServer(ペリフェラル）として動作する
 1. アドバタイズ開始
 1. 1秒間接続待ち
   - 接続したら書き込み待ち
   - 接続しなければシャットダウン（9秒後に起動）
 1. 書き込まれた文字列を表示する
 1. EXTボタンで画面クリア
 1. 接続中に電源ボタンでシャットダウン(9秒後に起動）
 1. 電源ボタンを押されたら起動する
 1. MIDボタンを押されるとBLEで通知する(notify)

### step2（別記事にする予定）->[こちら](https://qiita.com/sushisushiBoo/items/2a01d5098469e598afe1)
   step1に以下を加える

 1. 1時間に一回バッテリー電圧を確認して表示する
 1. DOWNボタンでNTPで調時する（日付を表示できるようにする）

### step3（別記事にする予定）
 - M5StickCをBLEClient（セントラル）として動作させる(1)
   - 置く向きよってCoreInkに送る文字列を選択させる
   - ボタン押下でscan、connect、write実行
 - M5StickCをBLEClient(セントラル）として動作させる(2)
   - CoreInkのボタン状態がオンならば、表示、ブザー鳴動させる 

# ソースコード

``` BleMessage1.ino
#include <M5CoreInk.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLE2902.h>
#include <utility/fontResource.h>
#include <Preferences.h>

Preferences preferences;

Ink_Sprite InkPageSprite(&M5.M5Ink);

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID        "1b62948e-7ccd-4b41-ad1b-181830a5742c"
#define CHARACTERISTIC_UUID "b6727492-b2ff-4563-aa2c-90c08e1bf879"

BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic = NULL;
bool deviceConnected = false;

#define TIME_ADVERTISE_DURATION_MSEC 1*1000 //< アドバタイズ、接続待ちする時間
#define TIME_SLEEP_SEC 9 //< スリープ時間

unsigned long tStart; // 起動時刻
char message[16];

void initInk() {
  if (!M5.M5Ink.isInit()) {
    Serial.println("Ink init failed");
  }
  M5.M5Ink.clear();
  delay(1000);

  /// create ink Sprite
  if (InkPageSprite.creatSprite(0, 0, 200, 200, true) != 0) {
    Serial.println("Ink Sprite create failed");
  }  
}

void drawString(const char* str) {
  initInk();  
  InkPageSprite.clear(CLEAR_DRAWBUFF | CLEAR_LASTBUFF);
  // TODO: 
  InkPageSprite.drawString(0, 0, str, &AsciiFont24x48);
  InkPageSprite.pushSprite();
  delay(2000);
}

class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      Serial.println("#connect");
      deviceConnected = true;
    };

    void onDisconnect(BLEServer* pServer) {
      Serial.println("#disconnect");
      deviceConnected = false;
    }
};

class MyCallbacks: public BLECharacteristicCallbacks {
  void onRead(BLECharacteristic *pCharacteristic) {
    Serial.println("#read");
    pCharacteristic->setValue("Hello World!");
  }

  void onWrite(BLECharacteristic *pCharacteristic) {
    Serial.println("#write");
    std::string value = pCharacteristic->getValue();
    Serial.println(value.c_str());
    // write to EInk
    sprintf(message, "%s", value.c_str());
    // save to preferences
    preferences.putString("message", message);
  }

  void onIndicate(BLECharacteristic *pCharacteristic) {
    Serial.println("#indicate");
  }
};

void setup() {
  /// init
  M5.begin();

  // initInk(); // 起動時に画面を初期化したくないのでコメントアウト

  Serial.println("BLE start.");
  BLEDevice::init("M5StackCoreInk");
  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());
  BLEService *pService = pServer->createService(SERVICE_UUID);
  pCharacteristic = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID,
                                         BLECharacteristic::PROPERTY_READ |
                                         BLECharacteristic::PROPERTY_WRITE |
                                         BLECharacteristic::PROPERTY_NOTIFY |
                                         BLECharacteristic::PROPERTY_INDICATE
                                       );
  pCharacteristic->setCallbacks(new MyCallbacks());
  pCharacteristic->addDescriptor(new BLE2902());

  pService->start();
  BLEAdvertising *pAdvertising = pServer->getAdvertising();
  pAdvertising->start();

  tStart = millis(); // 起動時刻を取得

  M5.Speaker.setBeep(1200, 1);
}

void beep() {
  M5.Speaker.beep();
  delay(100);
}

void powerDown() {
  M5.shutdown(TIME_SLEEP_SEC);  
}


void loop() {
  // EXTボタンで画面クリア
  if (M5.BtnEXT.wasPressed()) {
    Serial.println("BtnEXT pressed");
    Serial.println("init ink");
    beep();
    delay(100);
    initInk();
  }
  if (deviceConnected) {
    // 強制切断（シャットダウン）
    if (M5.BtnPWR.wasPressed()) {
      Serial.println("BtnPWR pressed");
      beep();
      powerDown();
    }
    // Notify
    if (M5.BtnMID.wasPressed()) {
      Serial.println("BtnMID pressed!");
      pCharacteristic->setValue("BtnMID pressed!");
      pCharacteristic->notify();
      beep();
    }
  } else {
    // 自動シャットダウン（USB給電中は無効）
    if (TIME_ADVERTISE_DURATION_MSEC <= (millis() - tStart)) {
      powerDown();
    }
  }
  
  if (0 < strlen(message)) {
    Serial.println("write message");
    drawString(message);
    memset(message, 0, sizeof(message));
  }

  M5.update();
}
```

# ちょっと工夫した点

 - シャットダウンから起動時にsetup()が実行されるのサンプルのままでは画面が一度黒くなる
 - 書換え時にのみ初期化処理を行うようにした