HueをブリッジなしてM5Atom(lite)からBLE(Bluetooth)で操作する

# 概要

[以前の記事](https://qiita.com/riafeed/items/255fe7781f1eff874bbf)でHueをラズパイから操作することに成功したが、
M5Atom Liteを買ったので、せっかくだからM5AtomからHueを操作してみる。

# 諸注意

- スマホのPhilips Hue Bluetoothアプリで登録した状態のまま以下の操作をしてもうまくいかないようです(最初に接続操作した機器以外からの操作は受け付けない仕様?)。登録してしまった場合はアプリからリセットして登録を解除しましょう。
- HueはリセットするとHue本体のMACアドレスが全く別の値に変わってしまう仕様になっているようです。うっかりリセット前のMACアドレスと間違えないように気をつけましょう。
- いくらなんでも古いBluetooth対応前のHueでは無理なんじゃないかと思います。
- 今回の記事とは関係ないですが、M5StackではBluetoothとWifiを同時に使おうとするとそのままの設定ではライブラリだけでサイズオーバーしてビルドに失敗するので、Arduino IDEの「ツール」→「Partition Scheme」から「No OTA (Large APP)」を選択する必要があります。

# ライブラリ

NimBLE-Arduinoを使っているので、ライブラリーマネージャーからインストールする必要があります。
ESP32のBLEライブラリと似ており、サイズが小さくて安定しているのでおすすめです。

# コード

```hue.cpp
/*
 M5Atom LiteをHueをON/OFFするトグルスイッチにするコード
*/
#include "M5Atom.h"
#include "NimBLEDevice.h"

//LEDの色定義
const int32_t red = 0x004000;
const int32_t green = 0x300000;
const int32_t blue = 0x000040;
const int32_t perple = 0x004040;
const int32_t white = 0x202020;

// Hueの定義情報
NimBLEUUID SERV_HUE("932c32bd-0000-47a2-835a-a8d455b859dd");          // サービス
NimBLEUUID CHAR_HUE_ONOFF("932c32bd-0002-47a2-835a-a8d455b859dd");    // ON/OFF
NimBLEUUID CHAR_HUE_BRIGHT("932c32bd-0003-47a2-835a-a8d455b859dd");   // 明るさ

// 手元のHueのMACアドレス
static String MAC_HUE = "HueのMACアドレス";

NimBLEScan* pBLEScan;
NimBLEAdvertisedDevice* pHue = nullptr;
NimBLEClient* pClient;

//BLE Scanのコールバック
class scanCallback: public NimBLEAdvertisedDeviceCallbacks
{
  void onResult(NimBLEAdvertisedDevice* advertisedDevice)
  {
    if(advertisedDevice->haveServiceUUID()) {
      Serial.println(advertisedDevice->toString().c_str());
      String addr = advertisedDevice->getAddress().toString().c_str();
      //上記で指定したMACアドレスのHueがあれば登録してスキャン終了
      if(addr.equalsIgnoreCase(MAC_HUE)) {
        pHue = advertisedDevice;
        advertisedDevice->getScan()->stop();
      }
    }
  }
};

void disconnect()
{
  if(pClient != nullptr) {
    pClient->disconnect();
    pClient = nullptr;
  }
}

NimBLERemoteCharacteristic* connect(NimBLEUUID servuuid, NimBLEUUID charuuid)
{
  NimBLERemoteCharacteristic* pRemoteCharacteristic = nullptr;
  pClient = NimBLEDevice::createClient();

  while (!pClient->connect(pHue)) {
    delay(1000);
  }

  NimBLERemoteService* pRemoteService = pClient->getService(servuuid);
  if (pRemoteService == nullptr) {
    disconnect();
    return nullptr;
  }

  pRemoteCharacteristic = pRemoteService->getCharacteristic(charuuid);
  if (pRemoteCharacteristic == nullptr) {
    disconnect();
    return nullptr;
  }

  return pRemoteCharacteristic;
}

bool writeBLE(NimBLEUUID servuuid, NimBLEUUID charuuid, uint8_t dat)
{
  NimBLERemoteCharacteristic* pBLEChar = connect(servuuid, charuuid);
  if(pBLEChar == nullptr) return false;
  if(!pBLEChar->canWrite()) return false;
  pBLEChar->writeValue(dat, true);
  disconnect();
  return true;
}

uint8_t readBLEByte(NimBLEUUID servuuid, NimBLEUUID charuuid)
{
  uint8_t ret;
  NimBLERemoteCharacteristic* pBLEChar = connect(servuuid, charuuid);
  if(pBLEChar == nullptr) return 0;
  if(!pBLEChar->canRead()) return 0;
  ret = pBLEChar->readUInt8();
  disconnect();
  return ret;
}

void setup() {
  //M5 Atomの初期化
  M5.begin(true, false, true);
  Serial.begin(115200);
  delay(10);
  Serial.println("");

  //Nimbleの初期化(2行目と3行目がないとHueではうまく動かない模様)
  NimBLEDevice::init("");
  NimBLEDevice::setOwnAddrType(BLE_OWN_ADDR_RANDOM);
  NimBLEDevice::setSecurityAuth(true, true, true);

  //スキャン開始
  pBLEScan = NimBLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new scanCallback());
  pBLEScan->setActiveScan(true);
  pBLEScan->setInterval(100);
  pBLEScan->setWindow(100);
  pBLEScan->start(5, false);
}

void loop() {
  //LEDを緑色に
  M5.dis.drawpix(0, green);

  //ボタンが押されたら
  M5.update();
  if(M5.Btn.wasPressed() && pHue != nullptr) {
    //LEDを白色に
    M5.dis.drawpix(0, white);

    //Hueの現在の状態(ONなのかOFFなのか)を取得
    uint8_t state = readBLEByte(SERV_HUE, CHAR_HUE_ONOFF);
    delay(1000);

    //先程取得した状態を基にHueの状態を変更
    if(state == 0) {
      //ONにする
      writeBLE(SERV_HUE, CHAR_HUE_ONOFF, (uint8_t)1);
    } else {
      //OFFにする
      writeBLE(SERV_HUE, CHAR_HUE_ONOFF, (uint8_t)0);      
    }
    delay(2000);
  }

  delay(1000);
}
```