M5StackでBLEを使う

この記事は[M5Stack Advent Calendar 2018](https://qiita.com/advent-calendar/2018/m5stack)の5日目の記事です。

# 概要

M5StackからBluetooth Low Energy(BLE)を使う[サンプル](https://github.com/naoki-sawada/m5stack-ble)を作ったのでその紹介です。

<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">BLE通信できた！ <a href="https://twitter.com/hashtag/M5Stack?src=hash&amp;ref_src=twsrc%5Etfw">#M5Stack</a> <a href="https://t.co/SM0ynKv8i5">pic.twitter.com/SM0ynKv8i5</a></p>&mdash; さわ/Naoki Sawada (@sawachyon) <a href="https://twitter.com/sawachyon/status/1027126972931956736?ref_src=twsrc%5Etfw">August 8, 2018</a></blockquote>

ソースコードは[こちら](https://github.com/naoki-sawada/m5stack-ble)から。

# M5Stackの準備

## M5Stackにソースコードを書き込む

M5Stackに次のBLEのソースコードを書き込みます。

```c:m5stack-ble.ino
#include <M5Stack.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLE2902.h>

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

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

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

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

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

class MyCallbacks: public BLECharacteristicCallbacks {
  void onRead(BLECharacteristic *pCharacteristic) {
    M5.Lcd.println("read");
    pCharacteristic->setValue("Hello World!");
  }
  
  void onWrite(BLECharacteristic *pCharacteristic) {
    M5.Lcd.println("write");
    std::string value = pCharacteristic->getValue();
    M5.Lcd.println(value.c_str());
  }
};

void setup() {
  Serial.begin(115200);
  M5.begin();
  M5.setWakeupButton(BUTTON_A_PIN);
  M5.Lcd.println("BLE start.");
  m5.Speaker.mute();

  BLEDevice::init("m5-stack");
  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();
}

void loop() {
  if(M5.BtnA.wasPressed()) {
    M5.powerOFF();
  }
  if (deviceConnected) {
    if(M5.BtnB.wasPressed()) {
      M5.Lcd.println("Button B pressed!");
      pCharacteristic->setValue("Button B pressed!");
      pCharacteristic->notify();
    }
  }
  M5.update();
}
```

# Clientの準備

次に、BLE Clientを準備します。

今回は、Pythonを使ったBLE Clientを作成します。


## Bleakのインストール

ここでは、BLEモジュール[Bleak](https://github.com/hbldh/bleak)を使います。

```
pip install bleak
```

## Python Client

次のコードを保存して、BLE Clientを作成します。

```client.py
#!/usr/bin/env python3
import asyncio
import logging
import uuid

from bleak import BleakScanner, BleakClient

DEVICE_NAME = "m5-stack"
SERVICE_UUID = uuid.UUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b")
CHAR_UUID = uuid.UUID("beb5483e-36e1-4688-b7f5-ea07361b26a8")


async def run(loop):
    print("Searching devices...")
    devices = await BleakScanner.discover()

    device = list(filter(lambda d: d.name == DEVICE_NAME, devices))
    if len(device) == 0:
        raise RuntimeError(f"Failed to find a device name '{DEVICE_NAME}'")

    address = device[0].address
    print(f"Connecting to the device... (address: {address})")
    async with BleakClient(address, loop=loop) as client:
        print("Message from the device...")
        value = await client.read_gatt_char(CHAR_UUID)
        print(value.decode())

        print("Sending message to the device...")
        message = bytearray(b"hi!")
        await client.write_gatt_char(CHAR_UUID, message, True)

        def callback(sender, data):
            print(f"Received: {data}")

        print("Subscribing to characteristic changes...")
        await client.start_notify(CHAR_UUID, callback)

        print("Waiting 60 seconds to receive data from the device...")
        await asyncio.sleep(60)


loop = asyncio.get_event_loop()
loop.run_until_complete(run(loop))
```

# BLEを試す

Clientを起動します。 `client.py` を実行します。

```
$ python3 client.py
Searching devices...
Connecting to the device... (address: 6B6ABA49-B0CA-4461-994E-A70A88E0E354)
Message from the device...
Hello World!
Sending message to the device...
Subscribing to characteristic changes...
Waiting 60 seconds to receive data from the device...
Received: b'Button B pressed!'
Received: b'Button B pressed!'
Received: b'Button B pressed!'
```

コネクションが確立できたら、M5StackのBボタンを押します。

`Received: b'Button B pressed!'` が表示されれば、正常に動作しています。

# まとめ

この記事ではM5Stackを使ったBLE通信について紹介しました。

M5StackはESP32を使っているので、BLEを手軽に使えるのが良いですね。

# 参考

## BLEについて

BLEの技術的な内容については、こちらのページがとても参考になりました。

AdvertisingやServices, Characteristicsなど、BLEの基本的な要素についてまとまっていておすすめです。

https://learn.adafruit.com/introduction-to-bluetooth-low-energy

## BLE Clientについて

今回の記事ではPythonを使ったBLE Clientの紹介をしましたが、
[Web Bluetooth API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API)を使ったサンプルや、Node.jsを使ったサンプルも作りました。よければ使ってみてください。

* [Web Bluetooth APIのサンプル](https://github.com/naoki-sawada/m5stack-ble/tree/master/web-client)
* [Node.jsのサンプル](https://github.com/naoki-sawada/m5stack-ble/tree/nodejs/nodejs-client)