M5StickCでNimBLE-Arduinoのシンプルなペリフェラルを作ってみました



# 1 はじめに
ardino-esp32のv2.0.0でnimbleが採用されるようです  
ちょっと予習を兼ねて、超シンプルなベースを作ってみました！！  

# 2 接続環境

central(client):Android BLE Scannerアプリ  
https://play.google.com/store/apps/details?id=com.macdom.ble.blescanner&hl=ja&gl=US

peripheral(server):M5StickC 今回の開発対象  

# 3 開発環境
PlatformIO+vscodeで開発します

# 4 手順

## 4.1 プロジェクト作成

project 好きな名前  
board M5Stickc  
framework Arduino  

## 4.2 platform.iniを修正します

ライブラリにm5stick-cとNimBLEを追加します  
ビルドするとライブラリをダウンロードしてくれます  

~~~diff
[env:m5stick-c]
platform = espressif32
+ lib_deps = 
+	M5StickC
+	h2zero/NimBLE-Arduino
board = m5stick-c
framework = arduino
+monitor_speed = 115200
~~~

## 4.3 サンプルを修正します

ベースは下記のソース  
https://github.com/h2zero/NimBLE-Arduino/blob/master/examples/NimBLE_Server/NimBLE_Server.ino

### 4.3.1 ヘッダを追加します

~~~diff
/** NimBLE_Server Demo:
 *
 *
 *  Demonstrates many of the available features of the NimBLE server library.
 *  
 *  Created: on March 22 2020
 *      Author: H2zero
 * 
*/

+ #include<M5StickC.h>
#include <NimBLEDevice.h>
~~~

### 4.3.2 UUIDを定義します

~~~diff
static NimBLEServer* pServer;
+ #define MACHINE_NAME        "M5-Stickc-0"
+ #define SERVICE_UUID        "your_serviceUUID"
+ #define CHARACTERISTIC_UUID "your_characteristicUUID"
~~~

### 4.3.3 setupを修正します

* sampleでは複数のUUIDを利用しています  
  シンプルに１つのUUIDを利用するように変更します  
* M5の初期化を追加します  

~~~
void setup() {
    Serial.begin(115200);

    M5.begin();

    Serial.println("Starting NimBLE Server");

    /** sets device name */
    NimBLEDevice::init("NimBLE-Arduino");

    /** Optional: set the transmit power, default is 3db */
    NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */

    //NimBLEDevice::setSecurityAuth(false, false, true); 
    NimBLEDevice::setSecurityAuth(/*BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM |*/ BLE_SM_PAIR_AUTHREQ_SC);

    pServer = NimBLEDevice::createServer();
    pServer->setCallbacks(new ServerCallbacks());

    NimBLEService* pService = pServer->createService(SERVICE_UUID);
    NimBLECharacteristic* pCharacteristic = pService->createCharacteristic(
                                               CHARACTERISTIC_UUID,
                                               NIMBLE_PROPERTY::READ |
                                               NIMBLE_PROPERTY::WRITE |
                                               NIMBLE_PROPERTY::NOTIFY
                                              );
  
    pCharacteristic->setValue("Burger");
    pCharacteristic->setCallbacks(&chrCallbacks);

    /** Start the services when finished creating all Characteristics and Descriptors */  
    pService->start();

    NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising();
    /** Add the services to the advertisment data **/
    pAdvertising->addServiceUUID(pService->getUUID());
    /** If your device is battery powered you may consider setting scan response
     *  to false as it will extend battery life at the expense of less data sent.
     */
    pAdvertising->setScanResponse(true);
    pAdvertising->start();

    Serial.println("Advertising Started");
}
~~~

### 4.3.4 loopを修正します

* Aボタンの押し検知を追加します  

~~~
  if (M5.BtnA.wasPressed()) {
~~~

* "button 押した回数"をsetValueにセットします  

~~~
  pChr->setValue(str);
~~~

* loop全体  

~~~diff
static uint8_t cnt = 0;
void loop() {
  /** Do your thing here, this just spams notifications to all connected clients */
  if(pServer->getConnectedCount()) {
    NimBLEService* pSvc = pServer->getServiceByUUID(SERVICE_UUID);
    if(pSvc) {
        NimBLECharacteristic* pChr = pSvc->getCharacteristic(CHARACTERISTIC_UUID);
        if(pChr) {
+            if (M5.BtnA.wasPressed()) {
+                char str[32];
+                sprintf(str,"button %d", cnt );
+                pChr->setValue(str);
+                cnt++;
+            }
            pChr->notify(true);
        }
    }
  }

+  M5.update();

  delay(100);
}

~~~

# 5 コード全体

~~~

/** NimBLE_Server Demo:
 *
 *  Demonstrates many of the available features of the NimBLE server library.
 *  
 *  Created: on March 22 2020
 *      Author: H2zero
 * 
*/

#include<M5StickC.h>
#include <NimBLEDevice.h>

static NimBLEServer* pServer;

#define MACHINE_NAME        "M5-Stickc-0"
#define SERVICE_UUID        "your_serviceUUID"
#define CHARACTERISTIC_UUID "your_characteristicUUID"

/**  None of these are required as they will be handled by the library with defaults. **
 **                       Remove as you see fit for your needs                        */  
class ServerCallbacks: public NimBLEServerCallbacks {
    void onConnect(NimBLEServer* pServer) {
        Serial.println("Client connected");
        Serial.println("Multi-connect support: start advertising");
        NimBLEDevice::startAdvertising();
    };
    /** Alternative onConnect() method to extract details of the connection. 
     *  See: src/ble_gap.h for the details of the ble_gap_conn_desc struct.
     */  
    void onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) {
        Serial.print("Client address: ");
        Serial.println(NimBLEAddress(desc->peer_ota_addr).toString().c_str());
        /** We can use the connection handle here to ask for different connection parameters.
         *  Args: connection handle, min connection interval, max connection interval
         *  latency, supervision timeout.
         *  Units; Min/Max Intervals: 1.25 millisecond increments.
         *  Latency: number of intervals allowed to skip.
         *  Timeout: 10 millisecond increments, try for 5x interval time for best results.  
         */
        pServer->updateConnParams(desc->conn_handle, 24, 48, 0, 60);
    };
    void onDisconnect(NimBLEServer* pServer) {
        Serial.println("Client disconnected - start advertising");
        NimBLEDevice::startAdvertising();
    };
    void onMTUChange(uint16_t MTU, ble_gap_conn_desc* desc) {
        Serial.printf("MTU updated: %u for connection ID: %u\n", MTU, desc->conn_handle);
    };
    
/********************* Security handled here **********************
****** Note: these are the same return values as defaults ********/
    uint32_t onPassKeyRequest(){
        Serial.println("Server Passkey Request");
        /** This should return a random 6 digit number for security 
         *  or make your own static passkey as done here.
         */
        return 123456; 
    };

    bool onConfirmPIN(uint32_t pass_key){
        Serial.print("The passkey YES/NO number: ");Serial.println(pass_key);
        /** Return false if passkeys don't match. */
        return true; 
    };

    void onAuthenticationComplete(ble_gap_conn_desc* desc){
        /** Check that encryption was successful, if not we disconnect the client */  
        if(!desc->sec_state.encrypted) {
            NimBLEDevice::getServer()->disconnect(desc->conn_handle);
            Serial.println("Encrypt connection failed - disconnecting client");
            return;
        }
        Serial.println("Starting BLE work!");
    };
};

/** Handler class for characteristic actions */
class CharacteristicCallbacks: public NimBLECharacteristicCallbacks {
    void onRead(NimBLECharacteristic* pCharacteristic){
        Serial.print(pCharacteristic->getUUID().toString().c_str());
        Serial.print(": onRead(), value: ");
        Serial.println(pCharacteristic->getValue().c_str());
    };

    void onWrite(NimBLECharacteristic* pCharacteristic) {
        Serial.print(pCharacteristic->getUUID().toString().c_str());
        Serial.print(": onWrite(), value: ");
        Serial.println(pCharacteristic->getValue().c_str());
    };
    /** Called before notification or indication is sent, 
     *  the value can be changed here before sending if desired.
     */
    void onNotify(NimBLECharacteristic* pCharacteristic) {
        Serial.println("Sending notification to clients");
    };


    /** The status returned in status is defined in NimBLECharacteristic.h.
     *  The value returned in code is the NimBLE host return code.
     */
    void onStatus(NimBLECharacteristic* pCharacteristic, Status status, int code) {
        String str = ("Notification/Indication status code: ");
        str += status;
        str += ", return code: ";
        str += code;
        str += ", "; 
        str += NimBLEUtils::returnCodeToString(code);
        Serial.println(str);
    };

    void onSubscribe(NimBLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc, uint16_t subValue) {
        String str = "Client ID: ";
        str += desc->conn_handle;
        str += " Address: ";
        str += std::string(NimBLEAddress(desc->peer_ota_addr)).c_str();
        if(subValue == 0) {
            str += " Unsubscribed to ";
        }else if(subValue == 1) {
            str += " Subscribed to notfications for ";
        } else if(subValue == 2) {
            str += " Subscribed to indications for ";
        } else if(subValue == 3) {
            str += " Subscribed to notifications and indications for ";
        }
        str += std::string(pCharacteristic->getUUID()).c_str();

        Serial.println(str);
    };
};
    
/** Handler class for descriptor actions */    
class DescriptorCallbacks : public NimBLEDescriptorCallbacks {
    void onWrite(NimBLEDescriptor* pDescriptor) {
        std::string dscVal((char*)pDescriptor->getValue(), pDescriptor->getLength());
        Serial.print("Descriptor witten value:");
        Serial.println(dscVal.c_str());
    };

    void onRead(NimBLEDescriptor* pDescriptor) {
        Serial.print(pDescriptor->getUUID().toString().c_str());
        Serial.println(" Descriptor read");
    };
};


/** Define callback instances globally to use for multiple Charateristics \ Descriptors */ 
static DescriptorCallbacks dscCallbacks;
static CharacteristicCallbacks chrCallbacks;

void setup() {
    Serial.begin(115200);

    M5.begin();

    Serial.println("Starting NimBLE Server");

    /** sets device name */
    NimBLEDevice::init(MACHINE_NAME);

    /** Optional: set the transmit power, default is 3db */
    NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */

    /** 2 different ways to set security - both calls achieve the same result.
     *  no bonding, no man in the middle protection, secure connections.
     *   
     *  These are the default values, only shown here for demonstration.   
     */ 
    //NimBLEDevice::setSecurityAuth(false, false, true); 
    NimBLEDevice::setSecurityAuth(/*BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM |*/ BLE_SM_PAIR_AUTHREQ_SC);

    pServer = NimBLEDevice::createServer();
    pServer->setCallbacks(new ServerCallbacks());

    NimBLEService* pService = pServer->createService(SERVICE_UUID);
    NimBLECharacteristic* pCharacteristic = pService->createCharacteristic(
                                               CHARACTERISTIC_UUID,
                                               NIMBLE_PROPERTY::READ |
                                               NIMBLE_PROPERTY::WRITE |
                                               NIMBLE_PROPERTY::NOTIFY
                                              );
  
    pCharacteristic->setValue("Burger");
    pCharacteristic->setCallbacks(&chrCallbacks);

    /** Start the services when finished creating all Characteristics and Descriptors */  
    pService->start();

    NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising();
    /** Add the services to the advertisment data **/
    pAdvertising->addServiceUUID(pService->getUUID());
    /** If your device is battery powered you may consider setting scan response
     *  to false as it will extend battery life at the expense of less data sent.
     */
    pAdvertising->setScanResponse(true);
    pAdvertising->start();

    Serial.println("Advertising Started");
}

static uint8_t cnt = 0;
void loop() {
  /** Do your thing here, this just spams notifications to all connected clients */
  if(pServer->getConnectedCount()) {
    NimBLEService* pSvc = pServer->getServiceByUUID(SERVICE_UUID);
    if(pSvc) {
        NimBLECharacteristic* pChr = pSvc->getCharacteristic(CHARACTERISTIC_UUID);
        if(pChr) {
            if (M5.BtnA.wasPressed()) {
                char str[32];
                sprintf(str,"button %d", cnt );
                pChr->setValue(str);
                Serial.println(str);
                cnt++;
            }
            pChr->notify(true);
        }
    }
  }

  M5.update();

  delay(100);
}

~~~

# 6 結果

~~~
Client connected
Multi-connect support: start advertising
Client address: 4c:45:60:eb:5c:84
0x000c: onRead(), value: button 3
Client ID: 0 Address: 4c:45:60:eb:5c:84 Subscribed to notfications for 0x000c
Sending notification to clients
Notification/Indication status code: 1, return code: 0,
button 4
Sending notification to clients
Notification/Indication status code: 1, return code: 0,
button 5
Sending notification to clients
Notification/Indication status code: 1, return code: 0,
Sending notification to clients
Notification/Indication status code: 1, return code: 0,
Client ID: 0 Address: 4c:45:60:eb:5c:84 Unsubscribed to 0x000c
Client disconnected - start advertising
~~~