LoginSignup
36
29

More than 3 years have passed since last update.

M5StackにbtstackでBluetooth3.0コントローラを接続する(ESP-IDFでのM5Stack開発環境整備)

Last updated at Posted at 2019-06-15

この記事の目標は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://dl.espressif.com/dl/esp32_win32_msys2_environment_and_toolchain-20181001.zip
ここのリンクからzipをダウンロードしてください。(600MBくらいあります)
※20191220バージョンが出ていますがこちらでは動作しません(原因は追いきれてない)

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コマンドを実行します

追記:ESP-IDFのバージョンを3.1.3から3.3.1に変更

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

参考サイト: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"

(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

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

**追記:Arduino-esp32のバージョンを1.0.0から1.0.4に変更

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.4 https://github.com/espressif/arduino-esp32.git arduino
rm arduino/cores/esp32/main.cpp
cd ..
make menuconfig

追記:BluetoothとmbedTLSの設定を追加

Serial flasher config ---> Default serial port
デバイスマネージャーでポートを確認して自分の環境に合わせて変更(COM666など)
image.png

Component config ---> Bluetooth
Bluetoothにチェック
image.png

Component config --->
Bluetooth Bluetooth controller --->
Bluetooth controller mode (BR/EDR/BLE/DUALMODE) (BLE Only)
Bluetooth Dual Modeにチェック
image.png
image.png
image.png

Component config --->
mbedTLS --->
TLS Key Exchange Methods --->
[*] Enable pre-shared-key ciphersuits
[*] Enable PSK based ciphersuite modes
↑両方にチェック

image.png
image.png
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");
    }
}

36
29
14

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
36
29