ESP32で8ポテンショメータをBLEデバイスにする

M5Unitの一つとして、8個のポテンショメータを持ったユニットがあります。

https://www.switch-science.com/products/8667

今回は、これを8軸のアナログ値をもったGamePadにします。GamePadにすることで、Windows等のPCのデバイスとして扱えるようになりますし、HTMLのJavascriptから扱えるようになります。
また、別の方法として、独自のBLEペリフェラルとして定義して、8軸のアナログ値だけでなく、8ポテンショメータユニットについているRGB LEDの色も変えられるようにしてみます。

今回は、ESP32として、M5Stick-Cを採用しています。ESP32であればどれでも大丈夫です。また、M5Stick-Cについているボタンも返せるようにしています。
ESP32の実装は、PlatformIOで作成しています。

ソースコードもろもろは以下に置いてあります。

poruruba/Angle8_Test

https://github.com/poruruba/Angle8_Test

# BLE接続のGamePadにする

以下のライブラリを使わせていただきました。

https://github.com/lemmingDev/ESP32-BLE-Gamepad

以下の投稿を参考にさせていただきました。わかりやすかったです。
　[ESP32-BLE-Gamepad ライブラリの使用方法](https://qiita.com/yomori/items/a0319a7eb0fe2a9e145a)

```c:Arduino\Angle8_BleGamepad\src\main.c
#include <M5StickC.h>
#include <Wire.h>
#include <BleGamepad.h>
#include "M5_ANGLE8.h"

#define ANGLE_MARGIN  5

BleGamepad bleGamepad("Angle8BleGamepad", "MyHome", 100);
M5_ANGLE8 angle8;

void setup() {
  // put your setup code here, to run once:
  M5.begin(true, true, true);
  Serial.println("setup start");

  Wire.begin(32, 33);

  while (!angle8.begin(ANGLE8_I2C_ADDR)) {
    Serial.println("angle8 Connect Error");
    delay(100);
  }

  BleGamepadConfiguration config;
  config.setButtonCount(3);
  config.setHatSwitchCount(0);
  config.setWhichAxes(true, true, true, true, true, true, true, true);
  config.setAxesMax(0xFFF);
  config.setAxesMin(0);
  bleGamepad.begin(&config);

  Serial.println("setup finished");
}

void loop() {
  // put your main code here, to run repeatedly:
  M5.update();

  static bool buttons[3] = { 0 };
  if( M5.BtnA.isPressed() ){
    if( !buttons[0] ){
      buttons[0] = true;
      bleGamepad.press(1);
      Serial.printf("BtnA Pressed\n");
    }
  }else{
    if( buttons[0] ){
      buttons[0] = false;
      bleGamepad.release(1);
      Serial.printf("BtnA Released\n");
    }
  }
  if( M5.BtnB.isPressed() ){
    if( !buttons[1] ){
      buttons[1] = true;
      bleGamepad.press(2);
      Serial.printf("BtnB Pressed\n");
    }
  }else{
    if( buttons[1] ){
      buttons[1] = false;
      bleGamepad.release(2);
      Serial.printf("BtnB Released\n");
    }
  }
  if( buttons[2] != angle8.getDigitalInput() ){
    buttons[2] = !buttons[2];
    if( buttons[2] ){
      bleGamepad.press(3);
      Serial.printf("Switch On\n");
    }else{
      bleGamepad.release(3);
      Serial.printf("Switch Off\n");
    }
  }


  static uint16_t values[8] = { 0 };
  bool changed = false;
  for( uint8_t i = 0 ; i < 8 ; i++ ){
    uint16_t value = angle8.getAnalogInput(i, _12bit);
    if( abs(value - values[i] ) >= ANGLE_MARGIN ){
      values[i] = value;
      changed = true;
    }
  }
  if( changed ){
    bleGamepad.setAxes(values[0], values[1], values[2], values[3], values[4], values[5], values[6], values[7]);
  }

  delay(1);
}
```

# 独自のBLEペリフェラルにする

BLEのプライマリサービスやキャラクタリスティックを独自に定義します。
GamePadの方では、ESP32からみて出力のみでしたが、独自の方では、出力に加えて、LED色を変えるための入力も備えています。

```c:Arduino\Angle8_BleCustom\src\main.c
#include <M5StickC.h>
#include <Wire.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLE2902.h>
#include "M5_ANGLE8.h"

M5_ANGLE8 angle8;

#define DISCONNECT_WAIT 3000

#define UUID_SERVICE "08030900-7d3b-4ebf-94e9-18abc4cebede"
#define UUID_WRITE "08030901-7d3b-4ebf-94e9-18abc4cebede"
#define UUID_READ "08030902-7d3b-4ebf-94e9-18abc4cebede"
#define UUID_NOTIFY "08030903-7d3b-4ebf-94e9-18abc4cebede"

#define ANGLE_MARGIN  5

BLECharacteristic *pCharacteristic_write;
BLECharacteristic *pCharacteristic_read;
BLECharacteristic *pCharacteristic_notify;
BLEAdvertising *g_pAdvertising = NULL;

#define UUID_VALUE_SIZE 20
uint8_t value_buffer[UUID_VALUE_SIZE] = { 0 };

bool isConnected = false;

void readValue(void);
void sendBuffer(void);

class MyCallbacks : public BLEServerCallbacks {
  void onConnect(BLEServer* pServer){
    isConnected = true;
    Serial.println("Connected\n");
  }

  void onDisconnect(BLEServer* pServer){
    isConnected = false;

    BLE2902* desc = (BLE2902*)pCharacteristic_notify->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
    desc->setNotifications(false);

    Serial.println("Disconnected\n");

    g_pAdvertising->stop();
    delay(DISCONNECT_WAIT);
    g_pAdvertising->start();
  }
};

class MyCharacteristicCallbacks : public BLECharacteristicCallbacks{
  void onWrite(BLECharacteristic* pCharacteristic){
    Serial.print("onWrite");
    uint8_t* value = pCharacteristic->getData();
    std::string str = pCharacteristic->getValue(); 
    int recv_len = str.length();
    Serial.printf("(%d)\n", recv_len);

    if( recv_len >= 5 ){
      uint32_t color = (value[2] << 16) | (value[3] << 8) | value[4];
      angle8.setLEDColor(value[0], color, value[1]);
    }
  }
/*
  void onStatus(BLECharacteristic* pCharacteristic, Status s, uint32_t code){
  }
  void BLECharacteristicCallbacks::onRead(BLECharacteristic *pCharacteristic){
  };
*/
};

void taskServer(void*) {
  BLEDevice::init("Angle8BleCustom");

  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyCallbacks());

  BLEService *pService = pServer->createService(UUID_SERVICE);

  pCharacteristic_write = pService->createCharacteristic( UUID_WRITE, BLECharacteristic::PROPERTY_WRITE );
  pCharacteristic_write->setAccessPermissions(ESP_GATT_PERM_WRITE);
  pCharacteristic_write->setCallbacks(new MyCharacteristicCallbacks());

  pCharacteristic_read = pService->createCharacteristic( UUID_READ, BLECharacteristic::PROPERTY_READ );
  pCharacteristic_read->setAccessPermissions(ESP_GATT_PERM_READ);
  pCharacteristic_read->setValue(value_buffer, sizeof(value_buffer));

  pCharacteristic_notify = pService->createCharacteristic( UUID_NOTIFY, BLECharacteristic::PROPERTY_NOTIFY );
  pCharacteristic_notify->setAccessPermissions(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE);
  pCharacteristic_notify->addDescriptor(new BLE2902());

  pService->start();

  g_pAdvertising = pServer->getAdvertising();
  g_pAdvertising->addServiceUUID(UUID_SERVICE);
  g_pAdvertising->start();

  vTaskDelay(portMAX_DELAY); //delay(portMAX_DELAY);
}

void setup() {
  // put your setup code here, to run once:
  M5.begin(true, true, true);
  Serial.printf("setup start\n");

  Wire.begin(32, 33);

  while (!angle8.begin(ANGLE8_I2C_ADDR)) {
    Serial.println("angle8 Connect Error");
    delay(100);
  }

  xTaskCreate(taskServer, "server", 20000, NULL, 5, NULL);

  Serial.printf("setup finished\n");
}

void loop() {
  // put your main code here, to run repeatedly:
  M5.update();

  readValue();

  bool changed = false;

  static uint8_t buttons[3] = { 0 };

  for( uint8_t i = 0 ; i < 3 ; i++ ){
    if( value_buffer[i] != buttons[i] ){
      buttons[i] = value_buffer[i];
      changed = true;
      if( buttons[i] )
        Serial.printf("Btn(%d) Pressed\n", i);
      else
        Serial.printf("Btn(%d) Released\n", i);
    }    
  }

  static uint8_t values[8] = { 0 };
  for( uint8_t i = 0 ; i < 8 ; i++ ){
    if( abs( value_buffer[3 + i] - values[i]) >= ANGLE_MARGIN ){
      values[i] = value_buffer[3 + i];
      changed = true;
      Serial.printf("Angle(%d) Changed\n", i);
    }
  }

  if( changed ){
    sendBuffer();
  }

  delay(1);
}

void readValue(void){
  value_buffer[0] = M5.BtnA.isPressed() ? 0x01 : 0x00;
  value_buffer[1] = M5.BtnB.isPressed() ? 0x01 : 0x00;
  value_buffer[2] = angle8.getDigitalInput() ? 0x01 : 0x00;
  for( uint8_t i = 0 ; i < 8 ; i++ ){
    value_buffer[3 + i] = (uint8_t)angle8.getAnalogInput(i, _8bit);
  }

  if( isConnected )
    pCharacteristic_read->setValue(value_buffer, 11);
}

void sendBuffer(void){
  Serial.println("SendBuffer");

  if( !isConnected )
    return;

  BLE2902* desc = (BLE2902*)pCharacteristic_notify->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
  if( !desc->getNotifications() )
    return;

  pCharacteristic_notify->setValue(value_buffer, 11);
  pCharacteristic_notify->notify();
}
```

# ブラウザからESP32に接続する

Gamepadとして認識する場合には、ブラウザのJavascriptにGamepad APIがあるので、そのまま扱えます。ただし、Windowsにおいて、事前にBluetoothデバイスとして追加しておく必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/b47ef9c6-c606-a10b-b16f-fba0a2b4ad56.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/aee9f29a-c0ad-8fa2-9e82-cff436b4fa12.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/a37fe205-3371-3eb6-053d-5d859988e456.png)

ブラウザのJavascriptにWebBluetooth API があるので、独自のBLEペリフェラルを接続できます。

詳しくは、ソースコードを参照して下さい。

Gamepad用
html\angle8_blegamepad\js\start.js

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/1b076b3b-0184-7bad-6fdf-e10ed5f25554.png)

Custom用
html\angle8_blecustom\js\start.js

※Custom用として動作させる場合は、WindowsにGamepadとして登録してある場合は削除してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/7241f18a-f5a7-3a4d-fdac-d8fa8087edd2.png)

以上