Edited at

M5StackにbtstackでBluetoothコントローラを接続する方法

この記事の目標はM5StackにBTコントローラ(Bluetooth3.0HID)を接続したうえでM5Stackのディスプレイなどを利用することです。

※例ではJoyConを使っていますがBlueTooth3.0のコントローラーなら大体繋がるはずです

btstackを使ってM5StackにBTコントローラを接続することはできるのですが、

素のESP32として利用されるだけでディスプレイなどM5Stack固有の機能が使えなくてあまり面白くありません。

ESP32とBluetoothコントローラーをHID(Bluetooth Classic)で接続する簡単な方法 - Qiita

https://qiita.com/coppercele/items/b1df50cea8fa883fc692

そこでESP-IDFのM5Stackライブラリを併用することでM5Stackの機能を利用できるようにします。


ESP-IDFをセットアップする

Windows10を前提とします。

参考サイト:ESP-IDF ( ESP32 開発環境 ) の使い方 | mgo-tec電子工作

https://www.mgo-tec.com/esp32-idf-howto-01

(よくわからない記述があったらこちらで確認してください)


Toolchainのインストール

以下のサイトからToolchainをダウンロードして解凍します。

https://docs.espressif.com/projects/esp-idf/en/latest/get-started/windows-setup.html

image.png

ここのリンクからzipをダウンロードしてください。(600MBくらいあります)

c:\msys32に解凍します(任意の場所で大丈夫です)

c:\msys32\mingw32.exeをダブルクリックするとコマンドウィンドウが開きます。


ESP-IDFをセットアップする

コマンドウィンドウが開いた状態だと/home/user-name/がカレントディレクトリになります。

(user-nameの部分はWindowsのユーザーネーム)

pwd(Enter)

/home/user-name

pwdコマンドで確認できます

作業ディレクトリを作成して移動します

mkdir -p esp

cd esp

ESP-IDFをダウンロードするためにgitコマンドを実行します

git clone -b v3.1.3 --recursive https://github.com/espressif/esp-idf.git

注意!Arduino-esp32(後述)にバージョン依存があるので最新バージョンではなくVersion3.1.3を指定してください

Ver3.1.3以外を指定するとmakeが失敗します

このバージョンの組み合わせ以外で成功した人は教えてください

参考サイト:ESP32/M5Stack の開発環境構築(esp-idf と Arduino Core) | hiromasa.another :o)

https://another.maple4ever.net/archives/2615/

ダウンロードが終わったらESP-IDFにパスを通します。

エクスプローラでc:\msys32\etc\profile.d\export_idf_path.shというファイルを作り

export IDF_PATH="c:/msys32/home/user-name/esp/esp-idf"

export BTSTACK_ROOT="c:/msys32/home/user-name/btstack/src"

(user-nameは読み替えてください)

として保存し、一度コマンドウィンドウを閉じて開き直します

printenvコマンドでファイルの内容が表示されればOKです

printenv IDF_PATH(Enter)

c:/msys32/home/user-name/esp/esp-idf


btstackのインストール

espディレクトリの下にbtstackをダウンロードします

この一連のコマンドをコマンドウィンドウにコピペすると自動で進みます

cd ~/esp

git clone https://github.com/bluekitchen/btstack.git
cd btstack/port/esp32/
./integrate_btstack.py
cd ~/esp/btstack/port/esp32/example/hid_host_demo
cp -r . ../hid_host_demo_bak

hid_host_demo/main内に以下のソースコードをコピーしてmain.cppとして保存し、

hid_host_demo.cを以下のものと置き換えます

元のhid_host_demoはhid_host_demo_bakとしてコピーされているので不具合が起きたときはそちらから復旧してください

main.cpp


main.cpp

#include <M5Stack.h>

#include <vector>
#include <string>

using namespace std;

#if CONFIG_FREERTOS_UNICORE
#define ARDUINO_RUNNING_CORE 0
#else
#define ARDUINO_RUNNING_CORE 1
#endif

vector<string> addresses = { };
vector<string> names = { };

int indexDevice = 0;

bool isSelect = false;

extern "C" void connect(char* addr);

void setup() {

// Initialize the M5Stack object
M5.begin();

// M5.Speaker.write(0);

// LCD display
M5.Lcd.setTextColor(GREEN, BLACK);
M5.Lcd.setTextSize(2);
M5.Lcd.setCursor(0, 0);
M5.Lcd.printf("M5 BT test");
M5.Lcd.setTextColor(WHITE, BLACK);
}

void printDevice(int index) {
M5.Lcd.setTextColor(WHITE, BLACK);
M5.Lcd.setCursor(0, 16);
M5.Lcd.printf("address %s",addresses[index].c_str());
M5.Lcd.setCursor(0, 32);
M5.Lcd.printf("name %s", names[index].c_str());
M5.Lcd.setCursor(0, 48);
M5.Lcd.printf("%d of %d", index + 1, addresses.size());
M5.Lcd.setCursor(0, 80);
M5.Lcd.printf("Connect > RIGHT BUTTON");
M5.Lcd.setCursor(0, 96);
M5.Lcd.printf("Next > LEFT BUTTON");

}

void loop() {
M5.update();

if (isSelect) {
}
else {
if (addresses.size() != 0) {
printDevice(indexDevice);
}
if (M5.BtnA.wasPressed()) {
if (indexDevice != addresses.size() - 1) {
indexDevice++;
M5.Lcd.fillRect(0, 16, 320, 240, BLACK);
}
else {
indexDevice = 0;
}
M5.Lcd.fillRect(0, 16, 320, 240, BLACK);

}
if (M5.BtnB.wasPressed()) {

}
delay(100);
if (M5.BtnC.wasPressed()) {
connect((char *) addresses[indexDevice].c_str());
M5.Lcd.fillRect(0, 16, 320, 240, BLACK);
isSelect = true;
}

}

delay(1);
}

// The arduino task
void loopTask(void *pvParameters) {
setup();

while (1) {
loop();
}
}

int i = 0;

// 文字列を表示
extern "C" void m5print(const char* str) {
if (240 < i * 16) {
i = 1;
M5.Lcd.fillRect(0, 16, 320, 240, BLACK);
}
M5.Lcd.setTextColor(WHITE, BLACK);
M5.Lcd.setCursor(0, i * 16);
M5.Lcd.printf(str);
i++;
}

extern "C" void m5packetReceive(const void *data, int size) {
vector<uint8_t> packet;
const uint8_t * ptr = (const uint8_t *) data;

for (int i = 0; i < size; i++) {
packet.push_back(*ptr++);
}
string str;
for (int i = 0; i < size; i++) {
char tempstr[2];
sprintf(tempstr,"%02X",packet[i]);
str += tempstr;
}
printf("%s\n", str.c_str());
m5print(str.c_str());
}

extern "C" void m5addDevice(char* addr, char* name) {
addresses.push_back(addr);
names.push_back(name);
if (addresses.size() == 1) {
printDevice(0);
}
}

// システムメッセージ表示用
extern "C" void m5message(char* str) {
M5.Lcd.setTextColor(GREEN, BLACK);
M5.Lcd.setTextSize(2);
M5.Lcd.setCursor(0, 0);
M5.Lcd.fillRect(0, 0, 320, 16, BLACK);
M5.Lcd.printf(str);
i++;
}

extern "C" void m5_arduino_main() {
initArduino();

xTaskCreatePinnedToCore(loopTask, "loopTask", 8192, NULL, 1, NULL,
ARDUINO_RUNNING_CORE);

}






hid_host_demo.c


hid_host_demo.c

/*

* Copyright (C) 2017 BlueKitchen GmbH
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holders nor the names of
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
* 4. Any redistribution, use, or modification is done solely for
* personal benefit and not for any commercial purpose or for
* monetary gain.
*
* THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS
* RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* Please inquire about commercial licensing options at
* contact@bluekitchen-gmbh.com
*
*/

#define BTSTACK_FILE__ "hid_host_demo.c"

/*
* hid_host_demo.c
*/

/* EXAMPLE_START(hid_host_demo): HID Host Demo
*
* @text This example implements an HID Host. For now, it connnects to a fixed device, queries the HID SDP
* record and opens the HID Control + Interrupt channels
*/

#include <inttypes.h>
#include <stdio.h>
#include <stdbool.h>

#include <string.h>

#include "btstack_config.h"
#include "btstack.h"

#define MAX_ATTRIBUTE_VALUE_SIZE 300

#define ENABLE_LOG_INFO
#define ENABLE_LOG_ERROR
void hci_dump_open(const char *filename, hci_dump_format_t format);

// SDP
static uint8_t hid_descriptor[MAX_ATTRIBUTE_VALUE_SIZE];
static uint16_t hid_descriptor_len;

static uint16_t hid_control_psm;
static uint16_t hid_interrupt_psm;

static uint8_t attribute_value[MAX_ATTRIBUTE_VALUE_SIZE];
static const unsigned int attribute_value_buffer_size = MAX_ATTRIBUTE_VALUE_SIZE;

// L2CAP
static uint16_t l2cap_hid_control_cid;
static uint16_t l2cap_hid_interrupt_cid;

// MBP 2016
// static const char * remote_addr_string = "F4-0F-24-3B-1B-E1";
// iMpulse static const char * remote_addr_string = "64:6E:6C:C1:AA:B5";
// 8bitdo
static const char * remote_addr_string = "00:00:00:00:00:";

static bd_addr_t remote_addr;

static btstack_packet_callback_registration_t hci_event_callback_registration;

// Simplified US Keyboard with Shift modifier

#define CHAR_ILLEGAL 0xff
#define CHAR_RETURN '\n'
#define CHAR_ESCAPE 27
#define CHAR_TAB '\t'
#define CHAR_BACKSPACE 0x7f

#define MAX_DEVICES 20
enum DEVICE_STATE {
REMOTE_NAME_REQUEST, REMOTE_NAME_INQUIRED, REMOTE_NAME_FETCHED
};
struct device {
bd_addr_t address;
uint8_t pageScanRepetitionMode;
uint16_t clockOffset;
enum DEVICE_STATE state;
};

#define INQUIRY_INTERVAL 5
struct device devices[MAX_DEVICES];
int deviceCount = 0;

/* @section Main application configuration
*
* @text In the application configuration, L2CAP is initialized
*/

/* LISTING_START(PanuSetup): Panu setup */
static void packet_handler(uint8_t packet_type, uint16_t channel,
uint8_t *packet, uint16_t size);
static void handle_sdp_client_query_result(uint8_t packet_type,
uint16_t channel, uint8_t *packet, uint16_t size);

static void hid_host_setup(void) {

// Initialize L2CAP
l2cap_init();

// register for HCI events
hci_event_callback_registration.callback = &packet_handler;
hci_add_event_handler(&hci_event_callback_registration);

// Disable stdout buffering
setbuf(stdout, NULL);
}

extern void m5message(char* str);

/* LISTING_END */

/* @section SDP parser callback
*
* @text The SDP parsers retrieves the BNEP PAN UUID as explained in
* Section [on SDP BNEP Query example](#sec:sdpbnepqueryExample}.
*/

static void handle_sdp_client_query_result(uint8_t packet_type,
uint16_t channel, uint8_t *packet, uint16_t size) {

UNUSED(packet_type);
UNUSED(channel);
UNUSED(size);

des_iterator_t attribute_list_it;
des_iterator_t additional_des_it;
des_iterator_t prot_it;
uint8_t *des_element;
uint8_t *element;
uint32_t uuid;
uint8_t status;
switch (hci_event_packet_get_type(packet)) {
case SDP_EVENT_QUERY_ATTRIBUTE_VALUE:
if (sdp_event_query_attribute_byte_get_attribute_length(packet)
<= attribute_value_buffer_size) {
attribute_value[sdp_event_query_attribute_byte_get_data_offset(
packet)] = sdp_event_query_attribute_byte_get_data(
packet);
if ((uint16_t) (sdp_event_query_attribute_byte_get_data_offset(
packet) + 1)
== sdp_event_query_attribute_byte_get_attribute_length(
packet)) {
switch (sdp_event_query_attribute_byte_get_attribute_id(
packet)) {
case BLUETOOTH_ATTRIBUTE_PROTOCOL_DESCRIPTOR_LIST:
for (des_iterator_init(&attribute_list_it,
attribute_value);
des_iterator_has_more(&attribute_list_it);
des_iterator_next(&attribute_list_it)) {
if (des_iterator_get_type(&attribute_list_it)
!= DE_DES)
continue;
des_element = des_iterator_get_element(
&attribute_list_it);
des_iterator_init(&prot_it, des_element);
element = des_iterator_get_element(&prot_it);
if (!element)
continue;
if (de_get_element_type(element) != DE_UUID)
continue;
uuid = de_get_uuid32(element);
des_iterator_next(&prot_it);
switch (uuid) {
case BLUETOOTH_PROTOCOL_L2CAP:
if (!des_iterator_has_more(&prot_it))
continue;
de_element_get_uint16(
des_iterator_get_element(
&prot_it),
&hid_control_psm);
printf("HID Control PSM: 0x%04x\n",
(int) hid_control_psm);
break;
default:
break;
}
}
break;
case BLUETOOTH_ATTRIBUTE_ADDITIONAL_PROTOCOL_DESCRIPTOR_LISTS:
for (des_iterator_init(&attribute_list_it,
attribute_value);
des_iterator_has_more(&attribute_list_it);
des_iterator_next(&attribute_list_it)) {
if (des_iterator_get_type(&attribute_list_it)
!= DE_DES)
continue;
des_element = des_iterator_get_element(
&attribute_list_it);
for (des_iterator_init(&additional_des_it,
des_element);
des_iterator_has_more(
&additional_des_it);
des_iterator_next(&additional_des_it)) {
if (des_iterator_get_type(
&additional_des_it) != DE_DES)
continue;
des_element = des_iterator_get_element(
&additional_des_it);
des_iterator_init(&prot_it, des_element);
element = des_iterator_get_element(
&prot_it);
if (!element)
continue;
if (de_get_element_type(element) != DE_UUID)
continue;
uuid = de_get_uuid32(element);
des_iterator_next(&prot_it);
switch (uuid) {
case BLUETOOTH_PROTOCOL_L2CAP:
if (!des_iterator_has_more(
&prot_it))
continue;
de_element_get_uint16(
des_iterator_get_element(
&prot_it),
&hid_interrupt_psm);
printf(
"HID Interrupt PSM: 0x%04x\n",
(int) hid_interrupt_psm);
break;
default:
break;
}
}
}
break;
case BLUETOOTH_ATTRIBUTE_HID_DESCRIPTOR_LIST:
for (des_iterator_init(&attribute_list_it,
attribute_value);
des_iterator_has_more(&attribute_list_it);
des_iterator_next(&attribute_list_it)) {
if (des_iterator_get_type(&attribute_list_it)
!= DE_DES)
continue;
des_element = des_iterator_get_element(
&attribute_list_it);
for (des_iterator_init(&additional_des_it,
des_element);
des_iterator_has_more(
&additional_des_it);
des_iterator_next(&additional_des_it)) {
if (des_iterator_get_type(
&additional_des_it) != DE_STRING)
continue;
element = des_iterator_get_element(
&additional_des_it);
const uint8_t * descriptor = de_get_string(
element);
hid_descriptor_len = de_get_data_size(
element);
memcpy(hid_descriptor, descriptor,
hid_descriptor_len);
printf("HID Descriptor:\n");
printf_hexdump(hid_descriptor,
hid_descriptor_len);
}
}
break;
default:
break;
}
}
}
else {
fprintf(stderr,
"SDP attribute value buffer size exceeded: available %d, required %d\n",
attribute_value_buffer_size,
sdp_event_query_attribute_byte_get_attribute_length(
packet));
}
break;

case SDP_EVENT_QUERY_COMPLETE:
if (!hid_control_psm) {
printf("HID Control PSM missing\n");
m5message("HID Device not found");
break;
}
if (!hid_interrupt_psm) {
printf("HID Interrupt PSM missing\n");
m5message("HID Device not found");
break;
}
printf("Setup HID\n");
status = l2cap_create_channel(packet_handler, remote_addr,
hid_control_psm, 48, &l2cap_hid_control_cid);
if (status) {
printf("Connecting to HID Control failed: 0x%02x\n", status);
}
break;
}
}

extern void m5print(char* str);

enum STATE {
INIT, ACTIVE, CONNECT
};
enum STATE state = INIT;

static btstack_packet_callback_registration_t hci_event_callback_registration;

static int getDeviceIndexForAddress(bd_addr_t addr) {
int j;
for (j = 0; j < deviceCount; j++) {
if (bd_addr_cmp(addr, devices[j].address) == 0) {
return j;
}
}
return -1;
}

static void start_scan(void) {
printf("Starting inquiry scan..\n");
gap_inquiry_start(INQUIRY_INTERVAL);
}

static int has_more_remote_name_requests(void) {
int i;
for (i = 0; i < deviceCount; i++) {
if (devices[i].state == REMOTE_NAME_REQUEST)
return 1;
}
return 0;
}

static void do_next_remote_name_request(void) {
int i;
for (i = 0; i < deviceCount; i++) {
// remote name request
if (devices[i].state == REMOTE_NAME_REQUEST) {
devices[i].state = REMOTE_NAME_INQUIRED;
printf("Get remote name of %s...\n",
bd_addr_to_str(devices[i].address));
gap_remote_name_request(devices[i].address,
devices[i].pageScanRepetitionMode,
devices[i].clockOffset | 0x8000);
return;
}
}
}

static void continue_remote_names(void) {
if (has_more_remote_name_requests()) {
do_next_remote_name_request();
return;
}
start_scan();
}

extern void m5addDevice(char* addr, char* name);

extern void m5packetReceive(const void *data, int size);

void connect(char* addr) {
printf("Connect with %s", addr);
state = CONNECT;
gap_inquiry_stop();
sscanf_bd_addr(addr, remote_addr);
printf("Start SDP HID query for remote HID Device.\n");
sdp_client_query_uuid16(&handle_sdp_client_query_result, remote_addr,
BLUETOOTH_SERVICE_CLASS_HUMAN_INTERFACE_DEVICE_SERVICE);

}

char * tempAddr;
/*
* @section Packet Handler
*
* @text The packet handler responds to various HCI Events.
*/

/* LISTING_START(packetHandler): Packet Handler */
static void packet_handler(uint8_t packet_type, uint16_t channel,
uint8_t *packet, uint16_t size) {
/* LISTING_PAUSE */
uint8_t event = 0;
bd_addr_t event_addr;
uint8_t status;
uint16_t l2cap_cid;

bd_addr_t addr;
int i;
int index;
switch (packet_type) {
case HCI_EVENT_PACKET:
event = hci_event_packet_get_type(packet);
switch (state) {
/* @text In INIT, an inquiry scan is started, and the application transits to
* ACTIVE state.
*/

case INIT:
switch (event) {
case BTSTACK_EVENT_STATE:
if (btstack_event_state_get_state(packet)
== HCI_STATE_WORKING) {
start_scan();
state = ACTIVE;
}
break;
default:
break;
}
break;

/* @text In ACTIVE, the following events are processed:
* - GAP Inquiry result event: BTstack provides a unified inquiry result that contain
* Class of Device (CoD), page scan mode, clock offset. RSSI and name (from EIR) are optional.
* - Inquiry complete event: the remote name is requested for devices without a fetched
* name. The state of a remote name can be one of the following:
* REMOTE_NAME_REQUEST, REMOTE_NAME_INQUIRED, or REMOTE_NAME_FETCHED.
* - Remote name request complete event: the remote name is stored in the table and the
* state is updated to REMOTE_NAME_FETCHED. The query of remote names is continued.
*/

case ACTIVE:
switch (event) {
case GAP_EVENT_INQUIRY_RESULT:
if (deviceCount >= MAX_DEVICES)
break; // already full
gap_event_inquiry_result_get_bd_addr(packet, addr);
index = getDeviceIndexForAddress(addr);
if (index >= 0)
break; // already in our list

memcpy(devices[deviceCount].address, addr, 6);
devices[deviceCount].pageScanRepetitionMode =
gap_event_inquiry_result_get_page_scan_repetition_mode(
packet);
devices[deviceCount].clockOffset =
gap_event_inquiry_result_get_clock_offset(
packet);
// print info
printf("Device found: %s ", bd_addr_to_str(addr));
printf("with COD: 0x%06x, ",
(unsigned int) gap_event_inquiry_result_get_class_of_device(
packet));
printf("pageScan %d, ",
devices[deviceCount].pageScanRepetitionMode);
printf("clock offset 0x%04x",
devices[deviceCount].clockOffset);
if (gap_event_inquiry_result_get_rssi_available(
packet)) {
printf(", rssi %d dBm",
(int8_t) gap_event_inquiry_result_get_rssi(
packet));
}
if (gap_event_inquiry_result_get_name_available(
packet)) {
char name_buffer[240];
int name_len =
gap_event_inquiry_result_get_name_len(
packet);
memcpy(name_buffer,
gap_event_inquiry_result_get_name(
packet), name_len);
name_buffer[name_len] = 0;
printf(", name '%s'", name_buffer);

m5addDevice(bd_addr_to_str(addr), name_buffer);

devices[deviceCount].state =
REMOTE_NAME_FETCHED;
}
else {
devices[deviceCount].state =
REMOTE_NAME_REQUEST;
tempAddr = bd_addr_to_str(addr);
}
printf("\n");
deviceCount++;
break;

case GAP_EVENT_INQUIRY_COMPLETE:
for (i = 0; i < deviceCount; i++) {
// retry remote name request
if (devices[i].state == REMOTE_NAME_INQUIRED)
devices[i].state = REMOTE_NAME_REQUEST;
}
continue_remote_names();
break;

case HCI_EVENT_REMOTE_NAME_REQUEST_COMPLETE:
reverse_bd_addr(&packet[3], addr);
index = getDeviceIndexForAddress(addr);
if (index >= 0) {
if (packet[2] == 0) {
printf("Name: '%s'\n", &packet[9]);
devices[index].state = REMOTE_NAME_FETCHED;

m5addDevice(tempAddr,
(const char *) &packet[9]);

}
else {
printf(
"Failed to get name: page timeout\n");
}
}
continue_remote_names();
break;
default:
break;
}
break;
case CONNECT:
switch (event) {

case BTSTACK_EVENT_STATE:
if (btstack_event_state_get_state(packet)
== HCI_STATE_WORKING) {
printf(
"Start SDP HID query for remote HID Device.\n");
sdp_client_query_uuid16(
&handle_sdp_client_query_result,
remote_addr,
BLUETOOTH_SERVICE_CLASS_HUMAN_INTERFACE_DEVICE_SERVICE);
}
break;

/* LISTING_PAUSE */
case HCI_EVENT_PIN_CODE_REQUEST:
// inform about pin code request
printf("Pin code request - using '0000'\n");
hci_event_pin_code_request_get_bd_addr(packet,
event_addr);
gap_pin_code_response(event_addr, "0000");
break;

case HCI_EVENT_USER_CONFIRMATION_REQUEST:
// inform about user confirmation request
printf(
"SSP User Confirmation Request with numeric value '%"PRIu32"'\n",
little_endian_read_32(packet, 8));
printf("SSP User Confirmation Auto accept\n");
break;

/* LISTING_RESUME */

case L2CAP_EVENT_CHANNEL_OPENED:
status = packet[2];
if (status) {
printf("L2CAP Connection failed: 0x%02x\n",
status);
break;
}
l2cap_cid = little_endian_read_16(packet, 13);
if (!l2cap_cid)
break;
if (l2cap_cid == l2cap_hid_control_cid) {
status = l2cap_create_channel(packet_handler,
remote_addr, hid_interrupt_psm, 48,
&l2cap_hid_interrupt_cid);
if (status) {
printf(
"Connecting to HID Control failed: 0x%02x\n",
status);
break;
}
}
if (l2cap_cid == l2cap_hid_interrupt_cid) {
printf("HID Connection established\n");
m5message("HID Device connected");
}
break;
default:
break;

}
break;
default:
break;
}
break;
case L2CAP_DATA_PACKET:
printf_hexdump(packet, size);
m5packetReceive(packet, size);
break;
default:
break;

}
}

extern void m5_arduino_main();

int btstack_main(int argc, const char * argv[]);
int btstack_main(int argc, const char * argv[]) {

(void) argc;
(void) argv;

hid_host_setup();

// Turn on the device
hci_power_control(HCI_POWER_ON);

m5_arduino_main();

return 0;
}







M5Stackライブラリをインストールする

btstackに対してコンポーネントとしてM5Stackのライブラリをインストールします

参考サイト:M5Stack-IDF = ESP-IDF + arduino の仕掛け

https://kazkojima.github.io/esp-idf-components.html

こちらのファイル構成をパク参考にさせていただきました

https://github.com/kazkojima/m5stack-app/tree/master/bltest

この一連のコマンドをコマンドウィンドウにコピペすると自動で進みます

cd ~/esp/btstack/port/esp32/example/hid_host_demo

mkdir -p components
cd components
git clone --recursive https://github.com/m5stack/M5Stack-IDF.git
cp ./M5Stack-IDF/sdkconfig ..
mv ./M5Stack-IDF/components/m5stack .
rm -rf M5Stack-IDF/
git clone -b 1.0.0 https://github.com/espressif/arduino-esp32.git arduino
rm arduino/cores/esp32/main.cpp
cd ..
make menuconfig

Serial flasher config ---> Default serial port

デバイスマネージャーでポートを確認して自分の環境に合わせて変更(COM666など)

image.png

Component config ---> Bluetooth

image.png

make

プロンプトで何か聞かれた場合はEnterでOK

ビルドが終わったらM5StackをPCに接続してプログラムを転送します

make flash monitor

うまく行くと周囲のbluetoothデバイスを検索して表示します

左ボタンでデバイスを選択し、右ボタンで接続します

image.png

接続できるとHID Device connectedと表示され

ボタンを押す/離すごとにコントローラから受信したパケットのデータが表示されます

また、コンソールにコントローラから受信したデータが表示されます

image.png

HID Device not foundと表示された場合はコントローラーが接続待機状態になってるか確認してM5Stackをリセットしてください

また、コントローラがSPPモードに変わっていると繋がらないのでHIDモードに変更してみてください

プロファイルの確認方法はこちら


おまけ

指示に合わせてボタンを押すだけでキーコードを取得して設定ファイルに保存する機能をつけました

※デジタルボタンのみ、SDカード必須

以下のソースを使ってください

main.cpp


main.cpp

#include <M5Stack.h>

#include <vector>
#include <SD.h>

using namespace std;

#if CONFIG_FREERTOS_UNICORE
#define ARDUINO_RUNNING_CORE 0
#else
#define ARDUINO_RUNNING_CORE 1
#endif

vector<String> addresses = { };
vector<String> names = { };

int indexDevice = 0;

bool isConnect = false;
bool isConfigure = false;

extern "C" void connect(char* addr);

void setup() {

// Initialize the M5Stack object
M5.begin();

// M5.Speaker.write(0);

// LCD display
M5.Lcd.setTextColor(GREEN, BLACK);
M5.Lcd.setTextSize(2);
M5.Lcd.setCursor(0, 0);
M5.Lcd.printf("M5 BT test");
M5.Lcd.setTextColor(WHITE, BLACK);
}

void printDevice(int index) {
M5.Lcd.setTextColor(WHITE, BLACK);
M5.Lcd.setCursor(0, 16);
M5.Lcd.printf("address %s", addresses[index].c_str());
M5.Lcd.setCursor(0, 32);
M5.Lcd.printf("name %s", names[index].c_str());
M5.Lcd.setCursor(0, 48);
M5.Lcd.printf("%d of %d", index + 1, addresses.size());
M5.Lcd.setCursor(0, 80);
M5.Lcd.printf("Connect > RIGHT BUTTON");
M5.Lcd.setCursor(0, 96);
M5.Lcd.printf("Next > LEFT BUTTON");
// M5.Lcd.setCursor(0, 112);
// M5.Lcd.printf("Semi-auto Config > CENTER BUTTON");

}

int screenIndex = 0;

// �������\��
extern "C" void m5print(const char* str) {
if (240 < screenIndex * 16) {
screenIndex = 1;
M5.Lcd.fillRect(0, 16, 320, 240, BLACK);
}
M5.Lcd.setTextColor(WHITE, BLACK);
M5.Lcd.setCursor(0, screenIndex * 16);
M5.Lcd.printf(str);
screenIndex++;
}

void m5Clear() {
screenIndex = 1;
M5.Lcd.fillRect(0, 16, 320, 240, BLACK);
}

void loop() {
M5.update();

if (isConnect) {
if (M5.BtnB.wasPressed()) {
isConfigure = true;
m5Clear();
m5print("PUSH ANY KEY TO START");
}
}
else {
if (addresses.size() != 0) {
printDevice(indexDevice);
}
if (M5.BtnA.wasPressed()) {
if (indexDevice != addresses.size() - 1) {
indexDevice++;
}
else {
indexDevice = 0;
}
M5.Lcd.fillRect(0, 16, 320, 240, BLACK);

}
if (M5.BtnB.wasPressed()) {

}
delay(100);
if (M5.BtnC.wasPressed()) {
connect((char *) addresses[indexDevice].c_str());
m5Clear();
isConnect = true;
m5print("Semi-auto Config(SD REQUIRED) > CENTER BUTTON");
}

}

delay(1);
}

// The arduino task
void loopTask(void *pvParameters) {
setup();

while (1) {
loop();
}
}

vector<uint8_t> standard;

String keys[] = { "ARROW_UP", "ARROW_DOWN", "ARROW_RIGHT", "ARROW_LEFT",
"BUTTON_TOP", "BUTTON_BOTTOM", "BUTTON_RIGHT", "BUTTON_LEFT", "L", "R",
"START", "SELECT", "" };

//char keys2[10][10] = {"ARROW_UP", "ARROW_DOWN"};

int keyIndex = 0;

vector<String> keyStr;

bool skip = true;
bool configConnected = false;
vector<String> configKeys;

extern "C" void m5packetReceive(const void *data, int size) {
vector<uint8_t> packet;
const uint8_t * ptr = (const uint8_t *) data;

// uint8_tをvectorに詰める
for (int i = 0; i < size; i++) {
packet.push_back(*ptr++);
}

if (isConfigure) {
// Semi-auto configure
if (standard.size() == 0) {
if (skip) {
// SKIP something pushed code
skip = false;
return;
}
// Recode released code
printf("STANDARD = ");
standard = packet;
for (int i = 0; i < size; i++) {
printf("%02X", standard[i]);
}
printf("\n");
String tempstr = "PUSH " + keys[keyIndex];
m5print(tempstr.c_str());
keyIndex++;
}
else {

bool difference = true;
vector<uint8_t> diff;
for (int i = 0; i < size; i++) {
// standardとXORを取る
diff.push_back(standard[i] ^ packet[i]);
if (diff[i] != 0) {
// Diffして違いがあればフラグを立てる
difference = false;
}
}
if (difference) {
// standardなら何も表示しない
return;
}
printf("KEY CODE = ");
String str;
for (int i = 0; i < size; i++) {
printf("%02X", diff[i]);
char tempstr[2];
sprintf(tempstr, "%02X", diff[i]);
str += tempstr;
}
printf("\n");
keyStr.push_back(str);
m5print(str.c_str());
if (keys[keyIndex].length() == 0) {
m5print("FINISHED");

File file = SD.open("/config.txt", FILE_WRITE);
file.print("ADDRESS=");
file.println(addresses[indexDevice].c_str());
file.print("NAME=");
file.println(names[indexDevice].c_str());
file.print("NEUTRAL=");
for (int i = 0; i < standard.size(); ++i) {
String tempstr = String(standard[i],HEX);
if (tempstr.length() == 1) {
tempstr = "0" + tempstr;
}
file.print(tempstr);
}
file.println("");

for (int i = 0; i < keyStr.size(); ++i) {
String tempStr = keys[i];
tempStr += "=";
tempStr += keyStr[i].c_str();
file.println(tempStr.c_str());
}
file.close();
isConfigure = false;
return;
}
String tempstr ="PUSH " + keys[keyIndex];
m5print(tempstr.c_str());
keyIndex++;
}
}
else {
if (configConnected) {

for (int j = 0; j < configKeys.size(); ++j) {

String keyCode = configKeys[j].substring(configKeys[j].indexOf('=') + 1);
vector<uint8_t> keyCodeUint8;
for (int i = 0; i < keyCode.length(); i+=2) {
// Upper 4bit
uint8_t tempUint = (uint8_t)atoi(keyCode.substring(i,i + 1).c_str());
tempUint = tempUint << 4;
// Lower 4bit
tempUint = tempUint | (uint8_t)atoi(keyCode.substring(i + 1 ,i + 2).c_str());
keyCodeUint8.push_back(tempUint);
}
printf("keycode=%s\n", configKeys[j].c_str());

for (int i = 0; i < packet.size(); ++i) {
printf("%02X %02X\n",packet[i], keyCodeUint8[i]);
if (packet[i] & keyCodeUint8[i]) {
printf("%s\n",configKeys[j].substring(0,configKeys[j].indexOf('=')).c_str());
m5print(configKeys[j].substring(0,configKeys[j].indexOf('=')).c_str());
break;
}
}
}
return;
}
else {
File file = SD.open("/config.txt", FILE_READ);

if (file) {
// configファイルがある場合
printf("CONFIG.TXT found\n");
String address = file.readStringUntil('\n');
address.trim();
address = address.substring(address.indexOf("=") + 1);
String deviceAddress = addresses[indexDevice];
printf("ADDRESS=%s size=%d\n", address.c_str(), address.length());

printf("DEVICE =%s size=%d\n", deviceAddress.c_str(), deviceAddress.length());

if(address.equals(deviceAddress)) {
printf("ADDRESS matched\n");
// NAME SKIP
file.readStringUntil('\n');
// NEUTRAL SKIP
file.readStringUntil('\n');
while(file.available()) {
configKeys.push_back(file.readStringUntil('\n'));
}
printf("configKeys.size()= %d\n",configKeys.size());
configConnected = true;
file.close();
}
else {
printf("ADDRESS mismatched\n");
}
}
}
String str;
for (int i = 0; i < size; i++) {
char tempstr[2];
sprintf(tempstr, "%02X", packet[i]);
str += tempstr;
}
printf("%s\n", str.c_str());
m5print(str.c_str());

}

}

extern "C" void m5addDevice(char* addr, char* name) {
addresses.push_back(addr);
names.push_back(name);
if (addresses.size() == 1) {
printDevice(0);
}
}

// システムメッセージ(緑)を表示する
extern "C" void m5message(char* str) {
M5.Lcd.setTextColor(GREEN, BLACK);
M5.Lcd.setTextSize(2);
M5.Lcd.setCursor(0, 0);
M5.Lcd.fillRect(0, 0, 320, 16, BLACK);
M5.Lcd.printf(str);
screenIndex++;
}

extern "C" void m5_arduino_main() {
initArduino();

xTaskCreatePinnedToCore(loopTask, "loopTask", 8192, NULL, 1, NULL,
ARDUINO_RUNNING_CORE);

}






hid_host_demo.c


hid_host_demo.c

/*

* Copyright (C) 2017 BlueKitchen GmbH
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holders nor the names of
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
* 4. Any redistribution, use, or modification is done solely for
* personal benefit and not for any commercial purpose or for
* monetary gain.
*
* THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS
* RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* Please inquire about commercial licensing options at
* contact@bluekitchen-gmbh.com
*
*/

#define BTSTACK_FILE__ "hid_host_demo.c"

/*
* hid_host_demo.c
*/

/* EXAMPLE_START(hid_host_demo): HID Host Demo
*
* @text This example implements an HID Host. For now, it connnects to a fixed device, queries the HID SDP
* record and opens the HID Control + Interrupt channels
*/

#include <inttypes.h>
#include <stdio.h>
#include <stdbool.h>

#include <string.h>

#include "btstack_config.h"
#include "btstack.h"

#define MAX_ATTRIBUTE_VALUE_SIZE 300

#define ENABLE_LOG_INFO
#define ENABLE_LOG_ERROR

// SDP
static uint8_t hid_descriptor[MAX_ATTRIBUTE_VALUE_SIZE];
static uint16_t hid_descriptor_len;

static uint16_t hid_control_psm;
static uint16_t hid_interrupt_psm;

static uint8_t attribute_value[MAX_ATTRIBUTE_VALUE_SIZE];
static const unsigned int attribute_value_buffer_size = MAX_ATTRIBUTE_VALUE_SIZE;

// L2CAP
static uint16_t l2cap_hid_control_cid;
static uint16_t l2cap_hid_interrupt_cid;

// MBP 2016
// static const char * remote_addr_string = "F4-0F-24-3B-1B-E1";
// iMpulse static const char * remote_addr_string = "64:6E:6C:C1:AA:B5";
// 8bitdo
static const char * remote_addr_string = "00:00:00:00:00:";

static bd_addr_t remote_addr;

static btstack_packet_callback_registration_t hci_event_callback_registration;

// Simplified US Keyboard with Shift modifier

#define CHAR_ILLEGAL 0xff
#define CHAR_RETURN '\n'
#define CHAR_ESCAPE 27
#define CHAR_TAB '\t'
#define CHAR_BACKSPACE 0x7f

#define MAX_DEVICES 20
enum DEVICE_STATE {
REMOTE_NAME_REQUEST, REMOTE_NAME_INQUIRED, REMOTE_NAME_FETCHED
};
struct device {
bd_addr_t address;
uint8_t pageScanRepetitionMode;
uint16_t clockOffset;
enum DEVICE_STATE state;
};

#define INQUIRY_INTERVAL 5
struct device devices[MAX_DEVICES];
int deviceCount = 0;

/* @section Main application configuration
*
* @text In the application configuration, L2CAP is initialized
*/

/* LISTING_START(PanuSetup): Panu setup */
static void packet_handler(uint8_t packet_type, uint16_t channel,
uint8_t *packet, uint16_t size);
static void handle_sdp_client_query_result(uint8_t packet_type,
uint16_t channel, uint8_t *packet, uint16_t size);

static void hid_host_setup(void) {

// Initialize L2CAP
l2cap_init();

// register for HCI events
hci_event_callback_registration.callback = &packet_handler;
hci_add_event_handler(&hci_event_callback_registration);

// Disable stdout buffering
setbuf(stdout, NULL);
}

extern void m5message(char* str);

/* LISTING_END */

/* @section SDP parser callback
*
* @text The SDP parsers retrieves the BNEP PAN UUID as explained in
* Section [on SDP BNEP Query example](#sec:sdpbnepqueryExample}.
*/

static void handle_sdp_client_query_result(uint8_t packet_type,
uint16_t channel, uint8_t *packet, uint16_t size) {

UNUSED(packet_type);
UNUSED(channel);
UNUSED(size);

des_iterator_t attribute_list_it;
des_iterator_t additional_des_it;
des_iterator_t prot_it;
uint8_t *des_element;
uint8_t *element;
uint32_t uuid;
uint8_t status;
switch (hci_event_packet_get_type(packet)) {
case SDP_EVENT_QUERY_ATTRIBUTE_VALUE:
if (sdp_event_query_attribute_byte_get_attribute_length(packet)
<= attribute_value_buffer_size) {
attribute_value[sdp_event_query_attribute_byte_get_data_offset(
packet)] = sdp_event_query_attribute_byte_get_data(
packet);
if ((uint16_t) (sdp_event_query_attribute_byte_get_data_offset(
packet) + 1)
== sdp_event_query_attribute_byte_get_attribute_length(
packet)) {
switch (sdp_event_query_attribute_byte_get_attribute_id(
packet)) {
case BLUETOOTH_ATTRIBUTE_PROTOCOL_DESCRIPTOR_LIST:
for (des_iterator_init(&attribute_list_it,
attribute_value);
des_iterator_has_more(&attribute_list_it);
des_iterator_next(&attribute_list_it)) {
if (des_iterator_get_type(&attribute_list_it)
!= DE_DES)
continue;
des_element = des_iterator_get_element(
&attribute_list_it);
des_iterator_init(&prot_it, des_element);
element = des_iterator_get_element(&prot_it);
if (!element)
continue;
if (de_get_element_type(element) != DE_UUID)
continue;
uuid = de_get_uuid32(element);
des_iterator_next(&prot_it);
switch (uuid) {
case BLUETOOTH_PROTOCOL_L2CAP:
if (!des_iterator_has_more(&prot_it))
continue;
de_element_get_uint16(
des_iterator_get_element(
&prot_it),
&hid_control_psm);
printf("HID Control PSM: 0x%04x\n",
(int) hid_control_psm);
break;
default:
break;
}
}
break;
case BLUETOOTH_ATTRIBUTE_ADDITIONAL_PROTOCOL_DESCRIPTOR_LISTS:
for (des_iterator_init(&attribute_list_it,
attribute_value);
des_iterator_has_more(&attribute_list_it);
des_iterator_next(&attribute_list_it)) {
if (des_iterator_get_type(&attribute_list_it)
!= DE_DES)
continue;
des_element = des_iterator_get_element(
&attribute_list_it);
for (des_iterator_init(&additional_des_it,
des_element);
des_iterator_has_more(
&additional_des_it);
des_iterator_next(&additional_des_it)) {
if (des_iterator_get_type(
&additional_des_it) != DE_DES)
continue;
des_element = des_iterator_get_element(
&additional_des_it);
des_iterator_init(&prot_it, des_element);
element = des_iterator_get_element(
&prot_it);
if (!element)
continue;
if (de_get_element_type(element) != DE_UUID)
continue;
uuid = de_get_uuid32(element);
des_iterator_next(&prot_it);
switch (uuid) {
case BLUETOOTH_PROTOCOL_L2CAP:
if (!des_iterator_has_more(
&prot_it))
continue;
de_element_get_uint16(
des_iterator_get_element(
&prot_it),
&hid_interrupt_psm);
printf(
"HID Interrupt PSM: 0x%04x\n",
(int) hid_interrupt_psm);
break;
default:
break;
}
}
}
break;
case BLUETOOTH_ATTRIBUTE_HID_DESCRIPTOR_LIST:
for (des_iterator_init(&attribute_list_it,
attribute_value);
des_iterator_has_more(&attribute_list_it);
des_iterator_next(&attribute_list_it)) {
if (des_iterator_get_type(&attribute_list_it)
!= DE_DES)
continue;
des_element = des_iterator_get_element(
&attribute_list_it);
for (des_iterator_init(&additional_des_it,
des_element);
des_iterator_has_more(
&additional_des_it);
des_iterator_next(&additional_des_it)) {
if (des_iterator_get_type(
&additional_des_it) != DE_STRING)
continue;
element = des_iterator_get_element(
&additional_des_it);
const uint8_t * descriptor = de_get_string(
element);
hid_descriptor_len = de_get_data_size(
element);
memcpy(hid_descriptor, descriptor,
hid_descriptor_len);
printf("HID Descriptor:\n");
printf_hexdump(hid_descriptor,
hid_descriptor_len);
}
}
break;
default:
break;
}
}
}
else {
fprintf(stderr,
"SDP attribute value buffer size exceeded: available %d, required %d\n",
attribute_value_buffer_size,
sdp_event_query_attribute_byte_get_attribute_length(
packet));
}
break;

case SDP_EVENT_QUERY_COMPLETE:
if (!hid_control_psm) {
printf("HID Control PSM missing\n");
m5message("HID Device not found");
break;
}
if (!hid_interrupt_psm) {
printf("HID Interrupt PSM missing\n");
m5message("HID Device not found");
break;
}
printf("Setup HID\n");
status = l2cap_create_channel(packet_handler, remote_addr,
hid_control_psm, 48, &l2cap_hid_control_cid);
if (status) {
printf("Connecting to HID Control failed: 0x%02x\n", status);
}
break;
}
}

enum STATE {
INIT, ACTIVE, CONNECT
};
enum STATE state = INIT;

static btstack_packet_callback_registration_t hci_event_callback_registration;

static int getDeviceIndexForAddress(bd_addr_t addr) {
int j;
for (j = 0; j < deviceCount; j++) {
if (bd_addr_cmp(addr, devices[j].address) == 0) {
return j;
}
}
return -1;
}

static void start_scan(void) {
printf("Starting inquiry scan..\n");
gap_inquiry_start(INQUIRY_INTERVAL);
}

static int has_more_remote_name_requests(void) {
int i;
for (i = 0; i < deviceCount; i++) {
if (devices[i].state == REMOTE_NAME_REQUEST)
return 1;
}
return 0;
}

static void do_next_remote_name_request(void) {
int i;
for (i = 0; i < deviceCount; i++) {
// remote name request
if (devices[i].state == REMOTE_NAME_REQUEST) {
devices[i].state = REMOTE_NAME_INQUIRED;
printf("Get remote name of %s...\n",
bd_addr_to_str(devices[i].address));
gap_remote_name_request(devices[i].address,
devices[i].pageScanRepetitionMode,
devices[i].clockOffset | 0x8000);
return;
}
}
}

static void continue_remote_names(void) {
if (has_more_remote_name_requests()) {
do_next_remote_name_request();
return;
}
start_scan();
}

extern void m5addDevice(char* addr, char* name);

extern void m5packetReceive(const void *data, int size);

void connect(char* addr) {
printf("Connect with %s", addr);
state = CONNECT;
gap_inquiry_stop();
sscanf_bd_addr(addr, remote_addr);
printf("Start SDP HID query for remote HID Device.\n");
sdp_client_query_uuid16(&handle_sdp_client_query_result, remote_addr,
BLUETOOTH_SERVICE_CLASS_HUMAN_INTERFACE_DEVICE_SERVICE);

}

char * tempAddr;
/*
* @section Packet Handler
*
* @text The packet handler responds to various HCI Events.
*/

/* LISTING_START(packetHandler): Packet Handler */
static void packet_handler(uint8_t packet_type, uint16_t channel,
uint8_t *packet, uint16_t size) {
/* LISTING_PAUSE */
uint8_t event = 0;
bd_addr_t event_addr;
uint8_t status;
uint16_t l2cap_cid;

bd_addr_t addr;
int i;
int index;
switch (packet_type) {
case HCI_EVENT_PACKET:
event = hci_event_packet_get_type(packet);
switch (state) {
/* @text In INIT, an inquiry scan is started, and the application transits to
* ACTIVE state.
*/

case INIT:
switch (event) {
case BTSTACK_EVENT_STATE:
if (btstack_event_state_get_state(packet)
== HCI_STATE_WORKING) {
start_scan();
state = ACTIVE;
}
break;
default:
break;
}
break;

/* @text In ACTIVE, the following events are processed:
* - GAP Inquiry result event: BTstack provides a unified inquiry result that contain
* Class of Device (CoD), page scan mode, clock offset. RSSI and name (from EIR) are optional.
* - Inquiry complete event: the remote name is requested for devices without a fetched
* name. The state of a remote name can be one of the following:
* REMOTE_NAME_REQUEST, REMOTE_NAME_INQUIRED, or REMOTE_NAME_FETCHED.
* - Remote name request complete event: the remote name is stored in the table and the
* state is updated to REMOTE_NAME_FETCHED. The query of remote names is continued.
*/

case ACTIVE:
switch (event) {
case GAP_EVENT_INQUIRY_RESULT:
if (deviceCount >= MAX_DEVICES)
break; // already full
gap_event_inquiry_result_get_bd_addr(packet, addr);
index = getDeviceIndexForAddress(addr);
if (index >= 0)
break; // already in our list

memcpy(devices[deviceCount].address, addr, 6);
devices[deviceCount].pageScanRepetitionMode =
gap_event_inquiry_result_get_page_scan_repetition_mode(
packet);
devices[deviceCount].clockOffset =
gap_event_inquiry_result_get_clock_offset(
packet);
// print info
printf("Device found: %s ", bd_addr_to_str(addr));
printf("with COD: 0x%06x, ",
(unsigned int) gap_event_inquiry_result_get_class_of_device(
packet));
printf("pageScan %d, ",
devices[deviceCount].pageScanRepetitionMode);
printf("clock offset 0x%04x",
devices[deviceCount].clockOffset);
if (gap_event_inquiry_result_get_rssi_available(
packet)) {
printf(", rssi %d dBm",
(int8_t) gap_event_inquiry_result_get_rssi(
packet));
}
if (gap_event_inquiry_result_get_name_available(
packet)) {
char name_buffer[240];
int name_len =
gap_event_inquiry_result_get_name_len(
packet);
memcpy(name_buffer,
gap_event_inquiry_result_get_name(
packet), name_len);
name_buffer[name_len] = 0;
printf(", name '%s'", name_buffer);

m5addDevice(bd_addr_to_str(addr), name_buffer);

devices[deviceCount].state =
REMOTE_NAME_FETCHED;
}
else {
devices[deviceCount].state =
REMOTE_NAME_REQUEST;
tempAddr = bd_addr_to_str(addr);
}
printf("\n");
deviceCount++;
break;

case GAP_EVENT_INQUIRY_COMPLETE:
for (i = 0; i < deviceCount; i++) {
// retry remote name request
if (devices[i].state == REMOTE_NAME_INQUIRED)
devices[i].state = REMOTE_NAME_REQUEST;
}
continue_remote_names();
break;

case HCI_EVENT_REMOTE_NAME_REQUEST_COMPLETE:
reverse_bd_addr(&packet[3], addr);
index = getDeviceIndexForAddress(addr);
if (index >= 0) {
if (packet[2] == 0) {
printf("Name: '%s'\n", &packet[9]);
devices[index].state = REMOTE_NAME_FETCHED;

m5addDevice(tempAddr,
(const char *) &packet[9]);

}
else {
printf(
"Failed to get name: page timeout\n");
}
}
continue_remote_names();
break;
default:
break;
}
break;
case CONNECT:
switch (event) {

case BTSTACK_EVENT_STATE:
if (btstack_event_state_get_state(packet)
== HCI_STATE_WORKING) {
printf(
"Start SDP HID query for remote HID Device.\n");
sdp_client_query_uuid16(
&handle_sdp_client_query_result,
remote_addr,
BLUETOOTH_SERVICE_CLASS_HUMAN_INTERFACE_DEVICE_SERVICE);
}
break;

/* LISTING_PAUSE */
case HCI_EVENT_PIN_CODE_REQUEST:
// inform about pin code request
printf("Pin code request - using '0000'\n");
hci_event_pin_code_request_get_bd_addr(packet,
event_addr);
gap_pin_code_response(event_addr, "0000");
break;

case HCI_EVENT_USER_CONFIRMATION_REQUEST:
// inform about user confirmation request
printf(
"SSP User Confirmation Request with numeric value '%"PRIu32"'\n",
little_endian_read_32(packet, 8));
printf("SSP User Confirmation Auto accept\n");
break;

/* LISTING_RESUME */

case L2CAP_EVENT_CHANNEL_OPENED:
status = packet[2];
if (status) {
printf("L2CAP Connection failed: 0x%02x\n",
status);
break;
}
l2cap_cid = little_endian_read_16(packet, 13);
if (!l2cap_cid)
break;
if (l2cap_cid == l2cap_hid_control_cid) {
status = l2cap_create_channel(packet_handler,
remote_addr, hid_interrupt_psm, 48,
&l2cap_hid_interrupt_cid);
if (status) {
printf(
"Connecting to HID Control failed: 0x%02x\n",
status);
break;
}
}
if (l2cap_cid == l2cap_hid_interrupt_cid) {
printf("HID Connection established\n");
m5message("HID Device connected");
}
break;
default:
break;

}
break;
default:
break;
}
break;
case L2CAP_DATA_PACKET:
printf_hexdump(packet, size);
m5packetReceive(packet, size);
break;
default:
break;

}
}

extern void m5_arduino_main();

int btstack_main(int argc, const char * argv[]);
int btstack_main(int argc, const char * argv[]) {

(void) argc;
(void) argv;

hid_host_setup();

// Turn on the device
hci_power_control(HCI_POWER_ON);

m5_arduino_main();

return 0;
}






右ボタンで接続するところまでは同じですが、

接続してからセンターボタンを押すと半自動解析モードに入ります

※HID Device connectedと表示されてから押してください

image.png

PUSH ANY KEY TO STARTと表示されたら何かボタンを押すと指示が表示されるのでそれに従ってボタンを押していきます

image.png

コントローラーのボタン構成としては最小と思われるものを選んでいますが、存在しないボタンを指示されたときは適当なボタンを押してスキップしてください

全てのボタンを押し終わると自動でSDカードのrootにCONFIG.TXTとして保存されます

image.png

存在しないキーがあった場合は表示名=コードの形式で追加できますし不要なキーは削除できます

CONFIG.TXTがある状態でBT機器を接続すると自動でCONFIG.TXTを読みに行ってアドレスが一致したらボタン名を表示するモードになります

image.png

CONFIG.TXTを見れば実際に使うときもbitの位置と値を割り出せます


BUTTON_TOP=00 00 08 00 00 00 00 00 00 00 00 00 00
.          3バイト目

const uint8_t BUTTON_TOP = 0x08; // 00001000
const uint8_t BUTTON_BOTTOM = 0x01; // 00000001

const int INDEX_BUTTON = 2; // 0スタート
static void decodeData(const uint8_t data[]) {
if (data[INDEX_BUTTON] & BUTTON_TOP) {
Serial.println("BUTTON_TOP");
}
if (data[INDEX_BUTTON] & BUTTON_BOTTOM) {
Serial.println("BUTTON_BOTTOM");
}


JoyConで動かす場合

ここから完全な余談なのですが、JoyConのレバーはコードが変なのでこのプログラムでは検出できません

普通は各方向で1,2,4,8を返して&を取れば分かるのですが、JoyConのコードは以下です


ARROW_UP=00 00 00 00 00 00 00 00 00 00 00 00 00
↑ココ
通常 08
上 00
右上 01
右 02
右下 03
下 04
左下 05
左 06
左上 07

なので通常の&では取れないので==で検出しないといけません

const uint8_t BUTTON_TOP = 0x08;

const uint8_t BUTTON_BOTTOM = 0x01;
const uint8_t BUTTON_RIGHT = 0x02;
const uint8_t BUTTON_LEFT = 0x04;

const uint8_t LEVER_UP = 0x00;
const uint8_t LEVER_UPRIGHT = 0x01;
const uint8_t LEVER_RIGHT = 0x02;
const uint8_t LEVER_DOWNRIGHT = 0x03;
const uint8_t LEVER_DOWN = 0x04;
const uint8_t LEVER_DOWNLEFT = 0x05;
const uint8_t LEVER_LEFT = 0x06;
const uint8_t LEVER_UPLEFT = 0x07;

const uint8_t L = 0x10;
const uint8_t R = 0x20;
const uint8_t START = 0x20;
const uint8_t SELECT = 0x01;

const int INDEX_BUTTON = 2;
const int INDEX_STARTSELECT = 3;
const int INDEX_LEVER = 4;

// btstack側からこんな感じで呼ばれる
// case L2CAP_DATA_PACKET:
// m5packetReceive(packet, size);
//
extern "C" void m5packetReceive(const void *data, int size) {
vector<uint8_t> packet;
const uint8_t * ptr = (const uint8_t *) data;

// uint8_tをvectorに詰める
for (int i = 0; i < size; i++) {
packet.push_back(*ptr++);
}

if (packet[INDEX_BUTTON] & L) {
printf("L");
}
if (packet[INDEX_BUTTON] & R) {
printf("R");
}
if (packet[INDEX_BUTTON] & BUTTON_TOP) {
printf("BUTTON TOP");
}
else if (packet[INDEX_BUTTON] & BUTTON_BOTTOM) {
printf("BUTTON BOTTOM");
}
else {
}

if (packet[INDEX_BUTTON] & BUTTON_RIGHT) {
printf("BUTTON RIGHT");
}
if (packet[INDEX_BUTTON] & BUTTON_LEFT) {
printf("BUTTON LEFT");
}
if (packet[INDEX_STARTSELECT] & START) {
printf("START");
}
if (packet[INDEX_STARTSELECT] & SELECT) {
printf("SELECT");
}

if (packet[INDEX_LEVER] == LEVER_UP) {
printf("LEVER UP");
}
else if (packet[INDEX_LEVER] == LEVER_UPRIGHT) {
printf("LEVER UPRIGHT");
}
else if (packet[INDEX_LEVER] == LEVER_RIGHT) {
printf("LEVER RIGHT");
}
else if (packet[INDEX_LEVER] == LEVER_DOWNRIGHT) {
printf("LEVER DOWNRIGHT");
}
else if (packet[INDEX_LEVER] == LEVER_DOWN) {
printf("LEVER DOWN");
}
else if (packet[INDEX_LEVER] == LEVER_DOWNLEFT) {
printf("LEVER DOWNLEFT");
}
else if (packet[INDEX_LEVER] == LEVER_LEFT) {
printf("LEVER LEFT");
}
else if (packet[INDEX_LEVER] == LEVER_UPLEFT) {
printf("LEVER UPLEFT");
}
}