M5Unitの一つとして、8個のポテンショメータを持ったユニットがあります。
今回は、これを8軸のアナログ値をもったGamePadにします。GamePadにすることで、Windows等のPCのデバイスとして扱えるようになりますし、HTMLのJavascriptから扱えるようになります。
また、別の方法として、独自のBLEペリフェラルとして定義して、8軸のアナログ値だけでなく、8ポテンショメータユニットについているRGB LEDの色も変えられるようにしてみます。
今回は、ESP32として、M5Stick-Cを採用しています。ESP32であればどれでも大丈夫です。また、M5Stick-Cについているボタンも返せるようにしています。
ESP32の実装は、PlatformIOで作成しています。
ソースコードもろもろは以下に置いてあります。
poruruba/Angle8_Test
BLE接続のGamePadにする
以下のライブラリを使わせていただきました。
以下の投稿を参考にさせていただきました。わかりやすかったです。
ESP32-BLE-Gamepad ライブラリの使用方法
#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色を変えるための入力も備えています。
#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デバイスとして追加しておく必要があります。
ブラウザのJavascriptにWebBluetooth API があるので、独自のBLEペリフェラルを接続できます。
詳しくは、ソースコードを参照して下さい。
Gamepad用
html\angle8_blegamepad\js\start.js
Custom用
html\angle8_blecustom\js\start.js
※Custom用として動作させる場合は、WindowsにGamepadとして登録してある場合は削除してください。
以上