diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index f67002c..fb1272e 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -4,8 +4,9 @@ set(srcs "adb.c" "gpio.c" "led.c" "main.c" - "quad.c") - + "quad.c" + "wii.c") + set(include_dirs ".") idf_component_register(SRCS "${srcs}" diff --git a/main/adb.c b/main/adb.c index 3855a0a..d223db5 100644 --- a/main/adb.c +++ b/main/adb.c @@ -3,7 +3,7 @@ * quack * * Created by Michel DEPEIGE on 7/01/2020. - * Copyright (c) 2020 Michel DEPEIGE. + * Copyright (c) 2020-2024 Michel DEPEIGE. * * This program is free software; you can redistribute it and/or modify * it under the terms of the Apache License, Version 2.0 (the "License"); diff --git a/main/blue.c b/main/blue.c index ea0f60a..baf38ee 100644 --- a/main/blue.c +++ b/main/blue.c @@ -41,6 +41,7 @@ #endif #include "esp_bt_main.h" #include "esp_bt_device.h" +#include "esp_gap_bt_api.h" #include "esp_timer.h" #include "esp_hid_common.h" @@ -53,6 +54,7 @@ #include "gpio.h" #include "led.h" #include "m4848.h" +#include "wii.h" /* debug tag */ static const char *TAG = "blue"; @@ -230,158 +232,36 @@ void blue_adb2hid(void *pvParameters) { } } -/* Host specific functions blue_h_* */ -void blue_h_callback(void *handler_args, esp_event_base_t base, int32_t id, void *event_data) -{ - esp_hidh_event_t event = (esp_hidh_event_t)id; - esp_hidh_event_data_t *param = (esp_hidh_event_data_t *)event_data; - - const uint8_t *bda = NULL; - - /* union described in include/esp_hidh.h */ - switch (event) { - case ESP_HIDH_OPEN_EVENT: { - esp_hidh_dev_dump(param->open.dev, stdout); - blue_h_open(param); - break; - } - case ESP_HIDH_BATTERY_EVENT: { - bda = esp_hidh_dev_bda_get(param->battery.dev); - ESP_LOGI(TAG, ESP_BD_ADDR_STR " battery: %d%%", ESP_BD_ADDR_HEX(bda), param->battery.level); - break; - } - case ESP_HIDH_INPUT_EVENT: { - //ESP_LOG_BUFFER_HEX(TAG, param->input.data, param->input.length); - xTaskNotify(t_yellow, LED_ONCE, eSetValueWithOverwrite); - blue_h_input(param->input.dev, param->input.data, param->input.length); - break; - } - case ESP_HIDH_FEATURE_EVENT: { - bda = esp_hidh_dev_bda_get(param->feature.dev); - ESP_LOGI(TAG, ESP_BD_ADDR_STR " FEATURE: %8s, MAP: %2u, ID: %3u, Len: %d", ESP_BD_ADDR_HEX(bda), esp_hid_usage_str(param->feature.usage), param->feature.map_index, param->feature.report_id, param->feature.length); - ESP_LOG_BUFFER_HEX(TAG, param->feature.data, param->feature.length); - break; - } - case ESP_HIDH_CLOSE_EVENT: { - blue_h_close(param); - break; - } - default: - ESP_LOGI(TAG, "Unknwown event: %d", event); - } -} - -void blue_h_close(esp_hidh_event_data_t *p) { - const uint8_t *bda = NULL; - - configASSERT(p != NULL); - bda = esp_hidh_dev_bda_get(p->close.dev); - ESP_LOGI(TAG, "closed connection with device " ESP_BD_ADDR_STR, ESP_BD_ADDR_HEX(bda)); - esp_hidh_dev_free(p->close.dev); - - blue_pointers--; - if (! blue_pointers) - xTaskNotify(t_blue, LED_SLOW, eSetValueWithOverwrite); -} - -static void blue_handle_button(uint8_t buttons) { - static bool locked = false; - static bool releasable = true; - static bool status = BLUE_BUTTON_N; // Keep state - - buttons = buttons & (BLUE_BUTTON_1 | BLUE_BUTTON_2 | BLUE_BUTTON_3); - - if (status && buttons) - return ; - if (status == BLUE_BUTTON_N && buttons == BLUE_BUTTON_N && !locked) - return ; - if (buttons == BLUE_BUTTON_2 && locked && !releasable) - return ; - if (status == BLUE_BUTTON_N && buttons == BLUE_BUTTON_N && locked) { - releasable = true; - return ; - } - - /* release button */ - if (status && buttons == BLUE_BUTTON_N && !locked) { - xTaskNotify(t_click, 0, eSetValueWithOverwrite); - status = 0; - ESP_LOGD(TAG, "button released"); - return ; - } - - /* stick button on right click */ - if (status == BLUE_BUTTON_N && buttons == BLUE_BUTTON_2 && !locked) { - xTaskNotify(t_click, 1, eSetValueWithOverwrite); - locked = true; - releasable = false; - ESP_LOGD(TAG, "button locked"); - return ; - } - - /* press button (simple click) */ - if (status == BLUE_BUTTON_N && buttons) { - xTaskNotify(t_click, 1, eSetValueWithOverwrite); - locked = false; - status = 1; - ESP_LOGD(TAG, "button pressed"); - return ; - } -} - -static void blue_h_init(void) { - esp_hidh_config_t config = { - .callback = blue_h_callback, - .event_stack_size = 4096 - }; - - ESP_ERROR_CHECK(esp_hidh_init(&config)); - - blue_pointers = 0; - xTaskCreatePinnedToCore(blue_scan, "blue_scan", 6 * 1024, NULL, 2, NULL, 0); -} - /* - * Bluetooth common init: init module and various stuff - * Host or Device specific inits go in blue_d_init() or blue_h_init() + * this function try to guess wich pin code to use when asked for one + * it just harass the different drivers and get a pin code from the driver */ -void blue_init(void) +uint8_t blue_get_pin_code(esp_bd_addr_t bda, esp_bt_pin_code_t pin) { - esp_err_t ret; - - ret = nvs_flash_init(); - if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { - ESP_ERROR_CHECK(nvs_flash_erase()); - ret = nvs_flash_init(); + if (wii_is_nintendo_bda(bda)) { + wii_generate_pin(pin); + ESP_LOGD(TAG, "Nintendo device asking for pin, trying with pin " WII_PIN_STR, + WII_PIN_HEX(pin)); + return 6; } - ESP_LOGD(TAG, "Starting Bluetooth init on core %d", xPortGetCoreID()); - ESP_ERROR_CHECK(ret); - ESP_ERROR_CHECK(esp_hid_gap_init(ESP_BT_MODE_CLASSIC_BT)); + /* let's try with default pin */ + ESP_LOGI(TAG, "no match found, trying with default pin code: 1234"); + pin[0] = '1'; + pin[0] = '2'; + pin[0] = '3'; + pin[0] = '4'; - esp_log_level_set("event", ESP_LOG_INFO); - /* complains about wrong data len on BOOT mode and CCONTROL */ - esp_log_level_set("BT_HIDH", ESP_LOG_ERROR); - - /* - * at this point, everything but bluetooth is started. - * put green steady, start blinking blue and keep scanning until a device is found - * - * starting IDF 5.2.2, sometimes the first xTaskNotify() call may be lost. why ? - */ - - xTaskNotify(t_green, LED_ON, eSetValueWithOverwrite); - xTaskNotify(t_green, LED_ON, eSetValueWithOverwrite); - xTaskNotify(t_blue, LED_FAST, eSetValueWithOverwrite); - - if (adb_is_host()) - blue_d_init(); - else - blue_h_init(); + return 4; } +/************************************ + * Host specific functions blue_h_* * + ************************************/ + /* - * data follows HID Mouse boot protocol format + * this function will decode a boot protocol frame and convert it for quadrature tasks + * data arg follows HID Mouse boot protocol format * * Byte | Bits | Description * -----+------+--------------------------------------------------------------- @@ -394,12 +274,11 @@ void blue_init(void) * 3+ | 0-7 | Device specific, usually 3 is scroll wheel, usually unused (0) */ -void blue_h_input(esp_hidh_dev_t *dev, uint8_t *data, uint16_t length) { +void blue_h_boot(uint8_t *data, uint16_t length) { uint8_t buttons; uint8_t i; int8_t x, y; - //ESP_LOG_BUFFER_HEX(TAG, data, length); buttons = data[0]; /* @@ -486,19 +365,198 @@ void blue_h_input(esp_hidh_dev_t *dev, uint8_t *data, uint16_t length) { } } -void blue_h_open(esp_hidh_event_data_t *p) { +void blue_h_callback(void *handler_args, esp_event_base_t base, int32_t id, void *event_data) +{ + esp_hidh_event_t event = (esp_hidh_event_t)id; + esp_hidh_event_data_t *param = (esp_hidh_event_data_t *)event_data; + + const uint8_t *bda = NULL; + + /* union described in include/esp_hidh.h */ + switch (event) { + case ESP_HIDH_OPEN_EVENT: { + esp_hidh_dev_dump(param->open.dev, stdout); + blue_h_open(param); + break; + } + case ESP_HIDH_BATTERY_EVENT: { + bda = esp_hidh_dev_bda_get(param->battery.dev); + ESP_LOGI(TAG, ESP_BD_ADDR_STR " battery: %d%%", ESP_BD_ADDR_HEX(bda), param->battery.level); + break; + } + case ESP_HIDH_INPUT_EVENT: { + //ESP_LOG_BUFFER_HEX(TAG, param->input.data, param->input.length); + xTaskNotify(t_yellow, LED_ONCE, eSetValueWithOverwrite); + blue_h_input(param->input.dev, param->input.report_id, param->input.data, param->input.length); + break; + } + case ESP_HIDH_FEATURE_EVENT: { + bda = esp_hidh_dev_bda_get(param->feature.dev); + ESP_LOGI(TAG, ESP_BD_ADDR_STR " FEATURE: %8s, MAP: %2u, ID: %3u, Len: %d", ESP_BD_ADDR_HEX(bda), esp_hid_usage_str(param->feature.usage), param->feature.map_index, param->feature.report_id, param->feature.length); + ESP_LOG_BUFFER_HEX(TAG, param->feature.data, param->feature.length); + break; + } + case ESP_HIDH_CLOSE_EVENT: { + blue_h_close(param); + break; + } + default: + ESP_LOGI(TAG, "Unknwown event: %d", event); + } +} + +void blue_h_close(esp_hidh_event_data_t *p) { const uint8_t *bda = NULL; configASSERT(p != NULL); + bda = esp_hidh_dev_bda_get(p->close.dev); + ESP_LOGI(TAG, "closed connection with device " ESP_BD_ADDR_STR, ESP_BD_ADDR_HEX(bda)); + esp_hidh_dev_free(p->close.dev); + + blue_pointers--; + if (! blue_pointers) + xTaskNotify(t_blue, LED_SLOW, eSetValueWithOverwrite); +} + +/* + * this function dispatch the input message to the correct driver + * the detection is based on Product ID + Vendor ID lists that each driver provides + * if no specific driver claims that combinaison, try with the generic boot driver + */ +void blue_h_input(esp_hidh_dev_t *dev, uint16_t id, uint8_t *data, uint16_t length) { + uint16_t vid = esp_hidh_dev_vendor_id_get(dev); + uint16_t pid = esp_hidh_dev_product_id_get(dev); + + if (wii_is_wiimote(vid, pid)) { + wii_input(dev, id, data, length); + return; + } + + blue_h_boot(data, length); +} + +static void blue_h_init(void) { + esp_hidh_config_t config = { + .callback = blue_h_callback, + .event_stack_size = 4096 + }; + + ESP_ERROR_CHECK(esp_hidh_init(&config)); + + blue_pointers = 0; + xTaskCreatePinnedToCore(blue_scan, "blue_scan", 6 * 1024, NULL, 2, NULL, 0); + + /* bluetooth stack ready at this point, initialise wii stuff there */ + wii_init(); +} + +static void blue_handle_button(uint8_t buttons) { + static bool locked = false; + static bool releasable = true; + static bool status = BLUE_BUTTON_N; // Keep state + + buttons = buttons & (BLUE_BUTTON_1 | BLUE_BUTTON_2 | BLUE_BUTTON_3); + + if (status && buttons) + return ; + if (status == BLUE_BUTTON_N && buttons == BLUE_BUTTON_N && !locked) + return ; + if (buttons == BLUE_BUTTON_2 && locked && !releasable) + return ; + if (status == BLUE_BUTTON_N && buttons == BLUE_BUTTON_N && locked) { + releasable = true; + return ; + } + + /* release button */ + if (status && buttons == BLUE_BUTTON_N && !locked) { + xTaskNotify(t_click, 0, eSetValueWithOverwrite); + status = 0; + ESP_LOGD(TAG, "button released"); + return ; + } + + /* stick button on right click */ + if (status == BLUE_BUTTON_N && buttons == BLUE_BUTTON_2 && !locked) { + xTaskNotify(t_click, 1, eSetValueWithOverwrite); + locked = true; + releasable = false; + ESP_LOGD(TAG, "button locked"); + return ; + } + + /* press button (simple click) */ + if (status == BLUE_BUTTON_N && buttons) { + xTaskNotify(t_click, 1, eSetValueWithOverwrite); + locked = false; + status = 1; + ESP_LOGD(TAG, "button pressed"); + return ; + } +} + +/* + * Bluetooth common init: init module and various stuff + * Host or Device specific inits go in blue_d_init() or blue_h_init() + */ +void blue_init(void) +{ + esp_err_t ret; + + ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + + ESP_LOGD(TAG, "Starting Bluetooth init on core %d", xPortGetCoreID()); + ESP_ERROR_CHECK(ret); + ESP_ERROR_CHECK(esp_hid_gap_init(ESP_BT_MODE_CLASSIC_BT)); + + esp_log_level_set("event", ESP_LOG_INFO); + /* complains about wrong data len on BOOT mode and CCONTROL */ + esp_log_level_set("BT_HIDH", ESP_LOG_VERBOSE); + + /* + * at this point, everything but bluetooth is started. + * put green steady, start blinking blue and keep scanning until a device is found + * + * starting IDF 5.2.2, sometimes the first xTaskNotify() call may be lost. why ? + */ + + xTaskNotify(t_green, LED_ON, eSetValueWithOverwrite); + xTaskNotify(t_green, LED_ON, eSetValueWithOverwrite); + xTaskNotify(t_blue, LED_FAST, eSetValueWithOverwrite); + + if (adb_is_host()) + blue_d_init(); + else + blue_h_init(); +} + +void blue_h_open(esp_hidh_event_data_t *p) { + const uint8_t *bda = NULL; + bool generic = true; + + configASSERT(p != NULL); + uint16_t vid = esp_hidh_dev_vendor_id_get(p->open.dev); + uint16_t pid = esp_hidh_dev_product_id_get(p->open.dev); bda = esp_hidh_dev_bda_get(p->open.dev); ESP_LOGI(TAG, "opened connection with " ESP_BD_ADDR_STR, ESP_BD_ADDR_HEX(bda)); - /* Dump various info on console */ - blue_hid_rm_get(p->open.dev); - blue_set_boot_protocol(p->open.dev); - if (blue_support_boot(p->open.dev) == false) { - xTaskNotify(t_red, LED_ONCE, eSetValueWithOverwrite); - ESP_LOGE(TAG, "Mouse does not support boot protocol !"); + if (wii_is_wiimote(vid, pid)) { + wii_open(bda); + generic = false; + } + + /* Dump various info on console for generic devices */ + if (generic) { + blue_hid_rm_get(p->open.dev); + blue_set_boot_protocol(p->open.dev); + if (blue_support_boot(p->open.dev) == false) { + xTaskNotify(t_red, LED_ONCE, eSetValueWithOverwrite); + ESP_LOGE(TAG, "Mouse does not support boot protocol !"); + } } blue_pointers++; @@ -520,7 +578,7 @@ static esp_hid_report_item_t blue_h_ri_find(esp_hidh_dev_t *d, esp_hid_usage_t u memset(&search, 0, sizeof(esp_hid_report_item_t)); for (uint8_t i = 0; i < num_reports; i++) { - if (reports[i].protocol_mode == p && reports[i].report_type == t && reports[i].usage == u) { + if (reports[i].protocol_mode == p && reports[i].report_type == t && reports[i].usage == u) { memcpy(&search, &reports[i], sizeof(esp_hid_report_item_t)); break; } @@ -562,49 +620,61 @@ static esp_hid_raw_report_map_t *blue_hid_rm_get(esp_hidh_dev_t *dev) { } void blue_scan(void *pvParameters) { - size_t len = 0; - esp_hid_scan_result_t *mouse = NULL; - esp_hid_scan_result_t *results = NULL; + size_t len = 0; + esp_hid_scan_result_t *mouse = NULL; + esp_hid_scan_result_t *results = NULL; + esp_hid_scan_result_t *wii = NULL; - ESP_LOGI(TAG, "starting scan on core %d…", xPortGetCoreID()); - esp_hid_scan(BLUE_SCAN_DURATION, &len, &results); - ESP_LOGI(TAG, "scan returned %u result(s)", len); + ESP_LOGI(TAG, "starting scan on core %d…", xPortGetCoreID()); + esp_hid_scan(BLUE_SCAN_DURATION, &len, &results); + ESP_LOGI(TAG, "scan returned %u result(s)", len); /* don't put the slow blink is a device reconnected while scanning */ if (blue_pointers == 0) xTaskNotify(t_blue, LED_SLOW, eSetValueWithOverwrite); - if (len) { - esp_hid_scan_result_t *r = results; + if (len) { + esp_hid_scan_result_t *r = results; - while (r) { - /* - * as of v1.4.5, esp_hid_gap will print detected devices in console (handle_bt_device_result()) - * just look for something that looks like Bluetooth Classic mouse - */ + /* + * as of v1.4.5, esp_hid_gap will print detected devices in console (handle_bt_device_result()) + * just look for bluetooth classic devices that may be supported + */ + while (r) { + if (r->transport == ESP_HID_TRANSPORT_BT && + strcmp("PERIPHERAL", esp_hid_cod_major_str(r->bt.cod.major)) == 0) { - if (r->transport == ESP_HID_TRANSPORT_BT && - strcmp("PERIPHERAL", esp_hid_cod_major_str(r->bt.cod.major)) == 0 - && (r->bt.cod.minor & ESP_HID_COD_MIN_MOUSE)) { - mouse = r; - } - r = r->next; - } + /* look for something that look like a mouse… */ + if (r->bt.cod.minor & ESP_HID_COD_MIN_MOUSE) + mouse = r; - /* try to connect to the last candidate found */ - if (mouse) + /* … or a wiimote (COD minor 1 is JOYSTICK) */ + if (r->bt.cod.minor == 1 && wii_is_nintendo_bda(r->bda)) + wii = r; + } + r = r->next; + } + } + + /* try to connect to the last candidate found */ + if (mouse) #if CONFIG_BT_BLE_ENABLED - esp_hidh_dev_open(mouse->bda, mouse->transport, mouse->ble.addr_type); + esp_hidh_dev_open(mouse->bda, mouse->transport, mouse->ble.addr_type); #else - esp_hidh_dev_open(mouse->bda, mouse->transport, NULL); + esp_hidh_dev_open(mouse->bda, mouse->transport, 0); #endif - else - ESP_LOGI(TAG, "devices found but no mouse detected"); + if (wii) + esp_hidh_dev_open(wii->bda, wii->transport, 0); - esp_hid_scan_results_free(results); - } + if (len && (!mouse && !wii)) { + ESP_LOGI(TAG, "%i devices found, but no mouse or wiimote detected", len); + esp_hid_scan_results_free(results); + } + if (len == 0) { + ESP_LOGI(TAG, "no new mouse or wiimote detected"); + } - vTaskDelete(NULL); + vTaskDelete(NULL); } void blue_set_boot_protocol(esp_hidh_dev_t *dev) { diff --git a/main/blue.h b/main/blue.h index 1ac7975..b98fe8c 100644 --- a/main/blue.h +++ b/main/blue.h @@ -31,15 +31,17 @@ #define BLUE_BUTTON_1 (1 << 0) #define BLUE_BUTTON_2 (1 << 1) #define BLUE_BUTTON_3 (1 << 2) -#define BLUE_BUTTON_E 0xF8 /* shouldn't happen */ +#define BLUE_BUTTON_E 0xF8 /* shouldn't happen */ /* prototypes */ void blue_adb2hid(void *pvParameters); void blue_init(void); -void blue_h_input(esp_hidh_dev_t *dev, uint8_t *data, uint16_t length); -void blue_h_close(esp_hidh_event_data_t *p); -void blue_h_open(esp_hidh_event_data_t *p); -void blue_scan(void *pvParameters); +uint8_t blue_get_pin_code(esp_bd_addr_t bda, esp_bt_pin_code_t pin); +void blue_h_boot(uint8_t *data, uint16_t length); +void blue_h_input(esp_hidh_dev_t *dev, uint16_t id, uint8_t *data, uint16_t length); +void blue_h_close(esp_hidh_event_data_t *p); +void blue_h_open(esp_hidh_event_data_t *p); +void blue_scan(void *pvParameters); void blue_set_boot_protocol(esp_hidh_dev_t *dev); /* global variables for tasks handles */ diff --git a/main/esp_hid_gap.c b/main/esp_hid_gap.c index 1e0c396..e62c313 100644 --- a/main/esp_hid_gap.c +++ b/main/esp_hid_gap.c @@ -243,7 +243,7 @@ void print_uuid(esp_bt_uuid_t *uuid) } #if CONFIG_BT_HID_HOST_ENABLED -static void handle_bt_device_result(struct disc_res_param *disc_res) +void handle_bt_device_result(struct disc_res_param *disc_res) { GAP_DBG_PRINTF("BT : " ESP_BD_ADDR_STR, ESP_BD_ADDR_HEX(disc_res->bda)); uint32_t codv = 0; @@ -381,16 +381,17 @@ static void handle_ble_device_result(struct ble_scan_result_evt_param *scan_rst) } #endif /* CONFIG_BT_BLE_ENABLED */ -#warning here1 #if CONFIG_BT_HID_HOST_ENABLED /* * BT GAP * */ -#warning here2 +/* quack patch */ +uint8_t blue_get_pin_code(esp_bd_addr_t bda, esp_bt_pin_code_t pin); + void bt_gap_event_handler(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) { - switch (event) { + switch (event) { case ESP_BT_GAP_DISC_STATE_CHANGED_EVT: { ESP_LOGV(TAG, "BT GAP DISC_STATE %s", (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STARTED) ? "START" : "STOP"); if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STOPPED) { @@ -402,9 +403,36 @@ void bt_gap_event_handler(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *pa handle_bt_device_result(¶m->disc_res); break; } +#if (CONFIG_EXAMPLE_SSP_ENABLED) case ESP_BT_GAP_KEY_NOTIF_EVT: ESP_LOGI(TAG, "BT GAP KEY_NOTIF passkey:%"PRIu32, param->key_notif.passkey); break; + case ESP_BT_GAP_CFM_REQ_EVT: { + ESP_LOGI(TAG, "BT GAP CFM_REQ_EVT Please compare the numeric value: %"PRIu32, param->cfm_req.num_val); + esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true); + break; + } + case ESP_BT_GAP_KEY_REQ_EVT: + ESP_LOGI(TAG, "BT GAP KEY_REQ_EVT Please enter passkey!"); + break; +#endif + case ESP_BT_GAP_MODE_CHG_EVT: + ESP_LOGI(TAG, "BT GAP MODE_CHG_EVT mode:%d", param->mode_chg.mode); + break; + case ESP_BT_GAP_PIN_REQ_EVT: { + ESP_LOGI(TAG, "BT GAP PIN_REQ_EVT min_16_digit:%d", param->pin_req.min_16_digit); + if (param->pin_req.min_16_digit) { + ESP_LOGI(TAG, "Input pin code: 0000 0000 0000 0000"); + esp_bt_pin_code_t pin_code = {0}; + esp_bt_gap_pin_reply(param->pin_req.bda, true, 16, pin_code); + } else { + esp_bt_pin_code_t pin_code; + uint8_t pin_len; + pin_len = blue_get_pin_code(param->pin_req.bda, pin_code); + esp_bt_gap_pin_reply(param->pin_req.bda, true, pin_len, pin_code); + } + break; + } default: ESP_LOGW(TAG, "BT GAP EVENT %s", bt_gap_evt_str(event)); break; @@ -422,15 +450,11 @@ static esp_err_t init_bt_gap(void) #endif /* * Set default parameters for Legacy Pairing - * Use fixed pin code + * Use variable pin, input pin code when pairing */ - esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_FIXED; + esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE; esp_bt_pin_code_t pin_code; - pin_code[0] = '0'; - pin_code[1] = '0'; - pin_code[2] = '0'; - pin_code[3] = '0'; - esp_bt_gap_set_pin(pin_type, 4, pin_code); + esp_bt_gap_set_pin(pin_type, 0, pin_code); if ((ret = esp_bt_gap_register_callback(bt_gap_event_handler)) != ESP_OK) { ESP_LOGE(TAG, "esp_bt_gap_register_callback failed: %d", ret); diff --git a/main/main.c b/main/main.c index e0ae226..c4cf11f 100644 --- a/main/main.c +++ b/main/main.c @@ -29,6 +29,7 @@ #include "esp_log.h" #include "esp_system.h" #include "esp_chip_info.h" +#include "esp_gap_bt_api.h" #include "adb.h" #include "blue.h" diff --git a/main/wii.aiff b/main/wii.aiff new file mode 100644 index 0000000..c3c0715 Binary files /dev/null and b/main/wii.aiff differ diff --git a/main/wii.c b/main/wii.c new file mode 100644 index 0000000..a06b34d --- /dev/null +++ b/main/wii.c @@ -0,0 +1,868 @@ +/* + * wii.c + * quack + * + * Created by Michel DEPEIGE on 11/08/2024. + * Copyright (c) 2020-2024 Michel DEPEIGE. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the Apache License, Version 2.0 (the "License"); + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include "driver/gpio.h" +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "esp_system.h" +#include "esp_chip_info.h" +#if !CONFIG_BT_NIMBLE_ENABLED +#include "esp_bt_defs.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" +#include "esp_gap_bt_api.h" +#endif +#include "esp_hid_common.h" +#include "esp_hidh.h" +#include "esp_hidh_api.h" + +#include "wii.h" +#include "led.h" +#include "gpio.h" + +/* defines */ +#define TAG "WII" + +/* globals */ +TaskHandle_t t_wii_dpad; +extern TaskHandle_t t_click; +extern TaskHandle_t t_yellow, t_red; +extern QueueHandle_t q_qx, q_qy; + +QueueHandle_t q_wii; + +uint8_t wii_speed; +wii_accel_t wii_calibration_0g; +wii_accel_t wii_calibration_1g; + +/* static defines */ +static void wii_accelerometer(esp_hidh_dev_t *dev, uint8_t *data); +static void wii_change_mode(esp_hidh_dev_t *dev); +static void wii_change_speed(esp_hidh_dev_t *dev, uint16_t buttons); +static void wii_handle_button(uint16_t buttons); +static void wii_mute_state(const esp_bd_addr_t bda, bool state); +static void wii_rumble_state(const esp_bd_addr_t bda, bool state); +static void wii_speaker_state(const esp_bd_addr_t bda, bool state); + +/* functions */ +static void wii_accelerometer(esp_hidh_dev_t *dev, uint8_t *data) { + const uint8_t *bda = NULL; + static uint8_t p = 0; /* used for calibration pass */ + uint8_t report[3] = {0}; + esp_err_t ret; + + float x, y, z; + static float average_x, average_y, average_z; + static uint16_t sum_x, sum_y, sum_z; + static int8_t out; /* used for quadrature messages */ + wii_accel_t a = {0, 0, 0}; + + configASSERT(data != NULL); + configASSERT(dev != NULL); + bda = esp_hidh_dev_bda_get(dev); + if (bda == NULL) + return; + /* extract accelerator data. see https://wiibrew.org/wiki/Wiimote#Accelerometer */ + a.x = (data[2] << 2) | ((data[0] >> 5) & 0x3); + a.y = (data[3] << 2) | ((data[1] >> 4) & 0x2); + a.z = (data[4] << 2) | ((data[1] >> 5) & 0x2); + + /* generate acceleration values */ + x = ((float)a.x - (float)wii_calibration_0g.x) / (float)wii_calibration_1g.x; + y = ((float)a.y - (float)wii_calibration_0g.y) / (float)wii_calibration_1g.y; + z = ((float)a.z - (float)wii_calibration_0g.z) / (float)wii_calibration_1g.z; + + /* + * at this point it's 2AM and the values read from the test Wiimote don't match + * the calibration values. we are limited since no IR / lightbar anyway so can't + * get yaw + * + * dunno if there is something wrong in the code or the Wiimote is decalibrated + * + * so from no on, we will mangle the values to do a Arkanoid or Space Invaders + * mode and call it a day (or a night) + * + * also ignore Y and Z from now on. Sorry Y and Z + * inspiration from https://www.nxp.com/docs/en/application-note/AN3397.pdf + */ + + if (p == 0) { + ESP_LOGD(TAG, "starting calibration…"); + + sum_x = 0; + sum_y = 0; + sum_z = 0; + } + if (p < 255) { + /* + * store MSB X, Y and Z values so the averages gets updated + * this will override values read from EEPROM but they don't seem useable + * for now (see above) + */ + + /* store MSB X and Y values so the average can get calculated later */ + sum_x += a.x >> 2; + sum_y += a.y >> 2; + sum_z += a.z >> 2; + p++; + + /* got enough samples, let's find averages */ + if (p == 255) { + average_x = ((float)((sum_x << 2) / 255) - (float)wii_calibration_0g.x); + average_x /= (float)wii_calibration_1g.x; + average_y = ((float)((sum_y << 2) / 255) - (float)wii_calibration_0g.y); + average_y /= (float)wii_calibration_1g.y; + average_z = ((float)((sum_z << 2) / 255) - (float)wii_calibration_0g.z); + average_z /= (float)wii_calibration_1g.z; + + /* do a quick rumble as user notification */ + wii_rumble_state(bda, true); + vTaskDelay(100 / portTICK_PERIOD_MS); + wii_rumble_state(bda, false); + report[0] = WII_REPORT_LEDS; + report[1] = WII_LED4; + ret = esp_bt_hid_host_send_data((uint8_t *)bda, report, 2); + if (ret) + ESP_LOGE(TAG, "cannot set LEDs state, send error: %x", ret); + vTaskDelay(WII_THROTTLE / portTICK_PERIOD_MS); + ESP_LOGD(TAG, "\t … X: %f, Y: %f, Z: %f", average_x, average_y, average_z); + + /* set to events only after calibration */ + report[0] = WII_REPORT_MODE; + report[1] = 0x00; /* events only report mode */ + report[2] = 0x31; /* core buttons + accelerometer */ + ret = esp_bt_hid_host_send_data((uint8_t *)bda, report, 3); + if (ret) + ESP_LOGE(TAG, "cannot set mode, send error: %x", ret); + } + return; + } + + /* just avoid may be used uninitialized messages */ + (void)y; + (void)z; + + /* + * do a mechanical filtering window, assuming the Wiimote is doing the low pass + * filtering itself since main report rate is about 30 Hz + * count idle passes to avoid drift, disregard half of what we did + * previously. this is a crap. Now I understand why Motion Plus exists + * this will move the mouse on the x axis, speed proportional to the x axis tilt + */ + if ((average_x - x) < -WII_THRESHOLD || (average_x - x) > WII_THRESHOLD) { + /* cap value to not overflow 8 bit */ + out = (average_x - x) * WII_ACCELERATION * wii_speed; + if (((average_x - x) * WII_ACCELERATION * wii_speed) < -120) + out = -120; + if (((average_x - x) * WII_ACCELERATION * wii_speed) > 120) + out = 120; + } + else { + out = out >> 1; + } + //ESP_LOGD(TAG, "X %f %f :: %i", (average_x - x), (average_x - x) * WII_ACCELERATION, out); + + if (out) + xQueueSendToBack(q_qx, &out, 0); +} + +/* + * this will switch between the default report (buttons only) and the more advanced + * mode with accelerometer data. We don't handle others advanced modes nor extensions + */ +static void wii_change_mode(esp_hidh_dev_t *dev) { + static bool accel = 0; + const uint8_t *bda = NULL; + uint8_t report[3] = {0}; + esp_err_t ret; + + configASSERT(dev != NULL); + bda = esp_hidh_dev_bda_get(dev); + if (bda == NULL) + return; + + accel = !accel; + ESP_LOGD(TAG, "changing mode, setting accelerometer status to %x", accel); + report[0] = WII_REPORT_MODE; + if (accel) { + report[1] = 0x04; /* continuous report mode */ + report[2] = 0x31; /* core buttons + accelerometer */ + } + else { + report[1] = 0x00; /* events only report mode */ + report[2] = 0x30; /* core buttons only */ + } + ret = esp_bt_hid_host_send_data((uint8_t *)bda, report, 3); + if (ret) + ESP_LOGE(TAG, "cannot set mode, send error: %x", ret); + + wii_rumble_state(bda, true); + vTaskDelay(100 / portTICK_PERIOD_MS); + wii_rumble_state(bda, false); + + /* disable speed LEDs on accelerator mode (or restore in default mode) */ + report[0] = WII_REPORT_LEDS; + if (accel) + report[1] = WII_LED4; + else + report[1] = wii_speed | WII_LED4; + ret = esp_bt_hid_host_send_data((uint8_t *)bda, report, 2); + if (ret) + ESP_LOGE(TAG, "cannot set LEDs state, send error: %x", ret); +} + +static void wii_change_speed(esp_hidh_dev_t *dev, uint16_t buttons) { + const uint8_t *bda = NULL; + uint8_t report[2] = {0}; + esp_err_t ret; + + configASSERT(dev != NULL); + bda = esp_hidh_dev_bda_get(dev); + if (bda == NULL) + return; + + /* user facerolling the wiimote, ignore */ + if ((buttons & WII_BUTTON_PLUS) && (buttons & WII_BUTTON_MINUS)) + return; + + if (buttons & WII_BUTTON_MINUS) { + switch (wii_speed) { + case WII_SPEED_NONE: + case WII_SPEED_LOW: + return; + case WII_SPEED_MEDIUM: + wii_speed = WII_SPEED_LOW; + break; + case WII_SPEED_HIGH: + wii_speed = WII_SPEED_MEDIUM; + default: + break; + } + } + + if (buttons & WII_BUTTON_PLUS) { + switch (wii_speed) { + case WII_SPEED_NONE: + return; + case WII_SPEED_LOW: + wii_speed = WII_SPEED_MEDIUM; + break; + case WII_SPEED_MEDIUM: + wii_speed = WII_SPEED_HIGH; + break; + case WII_SPEED_HIGH: + return; + default: + break; + } + } + + /* set LEDs for new speed */ + report[0] = WII_REPORT_LEDS; + report[1] = wii_speed | WII_LED4; + ret = esp_bt_hid_host_send_data((uint8_t *)bda, report, 2); + if (ret) + ESP_LOGE(TAG, "cannot set LEDs state, send error: %x", ret); +} + +/* + * this will enable the speaker, play the boot chime and then disable the speaker + * this is blocking for about 400-600ms + */ +void wii_chime(const esp_bd_addr_t bda) { + uint8_t report[22]; + esp_err_t ret; + uint8_t *pos; + + /* dual note bleepbleepX2 extracted from wii.aiff */ + const unsigned char chime[] = { + 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, + 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x01, 0xfc, + 0xfd, 0xff, 0xfd, 0xf6, 0x09, 0x18, 0x02, 0x05, 0x17, 0x02, + 0xcb, 0xd8, 0x18, 0x3c, 0x13, 0xcf, 0xc8, 0x05, 0x40, 0x29, + 0xe0, 0xbe, 0xf0, 0x32, 0x34, 0xf0, 0xc1, 0xe1, 0x2a, 0x3c, + 0x02, 0xc3, 0xd0, 0x19, 0x42, 0x18, 0xcf, 0xc3, 0x04, 0x40, + 0x29, 0xde, 0xbb, 0xef, 0x3a, 0x3c, 0xf0, 0xb8, 0xda, 0x2b, + 0x45, 0x09, 0xc1, 0xc8, 0x15, 0x40, 0x1c, 0xd7, 0xc2, 0xff, + 0x43, 0x1d, 0xdc, 0xdd, 0xe2, 0x09, 0x49, 0x16, 0xb6, 0xdf, + 0x39, 0x1c, 0xcb, 0xeb, 0x35, 0x0f, 0xc9, 0xf1, 0x3b, 0x0c, + 0xc3, 0xf6, 0x3c, 0x05, 0xc3, 0xfd, 0x3b, 0x00, 0xc5, 0x03, + 0x3b, 0xf8, 0xc2, 0x0a, 0x3e, 0xf2, 0xc0, 0x10, 0x3f, 0xed, + 0xc1, 0x15, 0x3d, 0xe8, 0xc3, 0x1a, 0x3a, 0xe2, 0xc6, 0x20, + 0x38, 0xdc, 0xc9, 0x25, 0x34, 0xd8, 0xcd, 0x2a, 0x30, 0xd3, + 0xd1, 0x2e, 0x2c, 0xcf, 0xd5, 0x32, 0x27, 0xcb, 0xda, 0x36, + 0x23, 0xc8, 0xdf, 0x39, 0x1d, 0xc5, 0xe5, 0x3c, 0x18, 0xc2, + 0xea, 0x3e, 0x12, 0xc1, 0xf0, 0x3f, 0x0c, 0xbf, 0xf6, 0x40, + 0x06, 0xbe, 0xfc, 0x41, 0x00, 0xbe, 0x02, 0x41, 0xfa, 0xbf, + 0x08, 0x40, 0xf4, 0xbf, 0x0e, 0x3f, 0xee, 0xc1, 0x14, 0x3d, + 0xe8, 0xc3, 0x1a, 0x3b, 0xe3, 0xc5, 0x1f, 0x38, 0xde, 0xc9, + 0x24, 0x35, 0xd8, 0xcc, 0x29, 0x31, 0xd4, 0xd0, 0x2d, 0x2d, + 0xcf, 0xd4, 0x32, 0x28, 0xcc, 0xd9, 0x35, 0x23, 0xc8, 0xde, + 0x39, 0x1e, 0xc5, 0xe4, 0x3b, 0x19, 0xc3, 0xe9, 0x3d, 0x12, + 0xc1, 0xef, 0x3e, 0x0d, 0xc0, 0xf5, 0x3f, 0x07, 0xbf, 0xfb, + 0x40, 0x01, 0xc0, 0x01, 0x3e, 0xfb, 0xc1, 0x06, 0x3b, 0xf6, + 0xc5, 0x0b, 0x37, 0xf2, 0xcc, 0x12, 0x2e, 0xf1, 0xda, 0x0b, + 0x0c, 0xf3, 0x12, 0x13, 0x00, 0x09, 0x18, 0xf5, 0xc6, 0xe5, + 0x25, 0x3a, 0x02, 0xc7, 0xd1, 0x17, 0x43, 0x1a, 0xd1, 0xc3, + 0x01, 0x3b, 0x28, 0xe0, 0xc1, 0xf2, 0x35, 0x34, 0xf0, 0xbe, + 0xde, 0x28, 0x3f, 0x06, 0xc4, 0xcd, 0x16, 0x42, 0x1a, 0xcf, + 0xc0, 0x02, 0x43, 0x2f, 0xdd, 0xb8, 0xec, 0x39, 0x3e, 0xf6, + 0xba, 0xd7, 0x26, 0x3f, 0x0c, 0xcc, 0xca, 0x13, 0x45, 0x0a, + 0xd8, 0xdf, 0xe5, 0x1b, 0x4a, 0xfc, 0xb2, 0xf7, 0x3f, 0x07, + 0xc6, 0x00, 0x37, 0xfb, 0xc6, 0x06, 0x3c, 0xf5, 0xc3, 0x0c, + 0x3b, 0xef, 0xc6, 0x12, 0x38, 0xeb, 0xc9, 0x18, 0x35, 0xe3, + 0xc9, 0x1f, 0x36, 0xdd, 0xc9, 0x25, 0x34, 0xd8, 0xcd, 0x29, + 0x31, 0xd3, 0xd0, 0x2e, 0x2c, 0xcf, 0xd5, 0x32, 0x28, 0xcb, + 0xda, 0x36, 0x23, 0xc8, 0xdf, 0x39, 0x1e, 0xc5, 0xe4, 0x3b, + 0x18, 0xc2, 0xea, 0x3d, 0x12, 0xc1, 0xf0, 0x3f, 0x0d, 0xbf, + 0xf5, 0x40, 0x07, 0xbe, 0xfc, 0x41, 0x00, 0xbe, 0x01, 0x41, + 0xfa, 0xbf, 0x08, 0x40, 0xf4, 0xbf, 0x0d, 0x3f, 0xef, 0xc1, + 0x13, 0x3d, 0xe9, 0xc3, 0x19, 0x3b, 0xe3, 0xc5, 0x1f, 0x38, + 0xde, 0xc8, 0x24, 0x35, 0xd9, 0xcc, 0x29, 0x31, 0xd4, 0xd0, + 0x2d, 0x2d, 0xd0, 0xd4, 0x31, 0x29, 0xcc, 0xd9, 0x35, 0x24, + 0xc8, 0xde, 0x38, 0x1f, 0xc5, 0xe3, 0x3b, 0x19, 0xc3, 0xe9, + 0x3d, 0x13, 0xc1, 0xef, 0x3f, 0x0d, 0xbf, 0xf4, 0x40, 0x08, + 0xbe, 0xfb, 0x41, 0x01, 0xbe, 0x01, 0x40, 0xfb, 0xbf, 0x07, + 0x3f, 0xf5, 0xc0, 0x0d, 0x3e, 0xef, 0xc1, 0x13, 0x3c, 0xea, + 0xc5, 0x18, 0x39, 0xe5, 0xc8, 0x1b, 0x35, 0xe2, 0xcd, 0x1f, + 0x2f, 0xdf, 0xd7, 0x22, 0x25, 0xe4, 0xe3, 0x11, 0x18, 0xfa, + 0xef, 0x04, 0x07, 0xfe, 0xfd, 0x01, 0x00, 0xff, 0xff, 0xff, + 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff + }; + + /* + * do the initialisation sequence for the speaker: + * - enable speaker + * - mute speaker + * - setup registers & voodoo + * - unmute speaker + */ + configASSERT(bda != NULL); + wii_speaker_state(bda, true); + wii_mute_state(bda, true); + + vTaskDelay(WII_THROTTLE / portTICK_PERIOD_MS); + memset(report, 0, 22); + report[0] = WII_REPORT_WRITE_REG; + report[1] = 0x04; + report[2] = 0xa2; + report[3] = 0x00; + report[4] = 0x09; + report[5] = 1; + report[6] = 0x01; + ret = esp_bt_hid_host_send_data((uint8_t *)bda, report, 22); + if (ret) + ESP_LOGE(TAG, "cannot set register, send error: %x", ret); + + vTaskDelay(WII_THROTTLE / portTICK_PERIOD_MS); + memset(report, 0, 22); + report[0] = WII_REPORT_WRITE_REG; + report[1] = 0x04; + report[2] = 0xa2; + report[3] = 0x00; + report[4] = 0x01; + report[5] = 1; + report[6] = 0x08; + ret = esp_bt_hid_host_send_data((uint8_t *)bda, report, 22); + if (ret) + ESP_LOGE(TAG, "cannot set register, send error: %x", ret); + + vTaskDelay(WII_THROTTLE / portTICK_PERIOD_MS); + memset(report, 0, 22); + report[0] = WII_REPORT_WRITE_REG; + report[1] = 0x04; + report[2] = 0xa2; + report[3] = 0x00; + report[4] = 0x01; + report[5] = 7; + report[6] = 0x00; + report[7] = 0x40; /* set PCM mode */ + report[8] = 0xe0; /* set 1 kHz sample rate */ + report[9] = 0x2e; /* set 1 kHz sample rate */ + report[10] = 0xe0; /* increase default volume */ + report[11] = 0x00; + report[12] = 0x00; + ret = esp_bt_hid_host_send_data((uint8_t *)bda, report, 22); + if (ret) + ESP_LOGE(TAG, "cannot set register, send error: %x", ret); + + vTaskDelay(WII_THROTTLE / portTICK_PERIOD_MS); + memset(report, 0, 22); + report[0] = WII_REPORT_WRITE_REG; + report[1] = 0x04; + report[2] = 0xa2; + report[3] = 0x00; + report[4] = 0x08; + report[5] = 1; + report[6] = 0x01; + ret = esp_bt_hid_host_send_data((uint8_t *)bda, report, 22); + if (ret) + ESP_LOGE(TAG, "cannot set register, send error: %x", ret); + + vTaskDelay(WII_THROTTLE / portTICK_PERIOD_MS); + wii_mute_state(bda, false); + vTaskDelay(WII_THROTTLE / portTICK_PERIOD_MS); + memset(report, 0, 22); + report[0] = WII_REPORT_SPEAKER_D; + report[1] = 20 << 3; /* 20 bytes each round (20 8bits samples) */ + for (pos = (uint8_t *)chime ; pos < (chime + sizeof(chime)) ; pos += 20) { + memcpy(report + 2 * sizeof(uint8_t), pos, 20); + ret = esp_bt_hid_host_send_data((uint8_t *)bda, report, 22); + if (ret) + ESP_LOGE(TAG, "cannot stream PCM data, send error: %x", ret); + /* max 1kHz 8bit PCM is at best what we can stream */ + vTaskDelay(WII_THROTTLE / portTICK_PERIOD_MS); + } + wii_speaker_state(bda, false); +} + +/* this function will generate movement notifications while a button is keep pressed */ +void wii_dpad(void *pvParameters) { + uint16_t buttons = 0; + uint32_t notify = 0; + int8_t x, y; + TickType_t delay = portMAX_DELAY; + + (void)pvParameters; + + x = 0; + y = 0; + + while (true) { + if (xTaskNotifyWait(0, 0, ¬ify, delay) == pdTRUE) { + /* we received a status change */ + buttons = (uint16_t)notify; + + if (! buttons) { + delay = portMAX_DELAY; + continue; + } + + delay = WII_INTERVAL / portTICK_PERIOD_MS; + } + + /* ignore facerolling */ + if ((buttons & WII_BUTTON_UP) && (buttons & WII_BUTTON_DOWN)) + continue; + if ((buttons & WII_BUTTON_LEFT) && (buttons & WII_BUTTON_RIGHT)) + continue; + + if (buttons & WII_BUTTON_LEFT) { + x = -((wii_speed >> 4) * 4); + xQueueSendToBack(q_qx, &x, 0); + } + if (buttons & WII_BUTTON_RIGHT) { + x = ((wii_speed >> 4) * 4); + xQueueSendToBack(q_qx, &x, 0); + } + if (buttons & WII_BUTTON_UP) { + y = -((wii_speed >> 4) * 4); + xQueueSendToBack(q_qy, &y, 0); + } + if (buttons & WII_BUTTON_DOWN) { + y = ((wii_speed >> 4) * 4); + xQueueSendToBack(q_qy, &y, 0); + } + } +} + +/* + * generate the PIN code needed for pairing. Info from https://wiibrew.org/wiki/Wiimote + * If connecting by holding down the 1+2 buttons, the PIN is the bluetooth address of + * the wiimote backwards, if connecting by pressing the "sync" button on the back of + * the wiimote, then the PIN is the bluetooth address of the host backwards. + * + * we will implement the last option + */ +uint8_t *wii_generate_pin(esp_bt_pin_code_t pin) { + uint8_t *mac; + + configASSERT(pin != NULL); + mac = (uint8_t *)esp_bt_dev_get_address(); + if (mac == NULL) + return NULL; + + pin[0] = mac[5]; + pin[1] = mac[4]; + pin[2] = mac[3]; + pin[3] = mac[2]; + pin[4] = mac[1]; + pin[5] = mac[0]; + ESP_LOGV(TAG, "Bluetooth MAC: " ESP_BD_ADDR_STR ", PIN code: " WII_PIN_STR, + ESP_BD_ADDR_HEX(mac), WII_PIN_HEX(pin)); + + return mac; +} + +/* + * similar to blue_handle_button + * this will only handle buttons that are used for clicks + * buttons that are used for movement are handled by the wii_dpad task + */ + +static void wii_handle_button(uint16_t buttons) { + static bool locked = false; + static bool releasable = true; + static bool status = WII_BUTTON_NONE; // Keep state + + buttons = buttons & (WII_BUTTON_A | WII_BUTTON_B); + + if (status && buttons) + return ; + if (status == WII_BUTTON_NONE && buttons == WII_BUTTON_NONE && !locked) + return ; + if (buttons == WII_BUTTON_A && locked && !releasable) + return ; + if (status == WII_BUTTON_NONE && buttons == WII_BUTTON_NONE && locked) { + releasable = true; + return ; + } + + /* release button */ + if (status && buttons == WII_BUTTON_NONE && !locked) { + xTaskNotify(t_click, 0, eSetValueWithOverwrite); + status = 0; + ESP_LOGD(TAG, "button released"); + return ; + } + + /* stick button on right click */ + if (status == WII_BUTTON_NONE && buttons == WII_BUTTON_A && !locked) { + xTaskNotify(t_click, 1, eSetValueWithOverwrite); + locked = true; + releasable = false; + ESP_LOGD(TAG, "button locked"); + return ; + } + + /* press button (simple click) */ + if (status == WII_BUTTON_NONE && buttons) { + xTaskNotify(t_click, 1, eSetValueWithOverwrite); + locked = false; + status = 1; + ESP_LOGD(TAG, "button pressed"); + return ; + } +} + +void wii_init(void) { + /* put default values into calibration data */ + memset(&wii_calibration_0g, 0, sizeof(wii_accel_t)); + memset(&wii_calibration_1g, 0, sizeof(wii_accel_t)); + + xTaskCreate(wii_dpad, "WII_DPAD", 1024, NULL, tskIDLE_PRIORITY + 1, &t_wii_dpad); +} + +void wii_input(esp_hidh_dev_t *dev, uint16_t id, uint8_t *data, uint16_t length) { + uint16_t buttons = 0; + static bool changed = 0; + + /* do some basic sanity checks then ignore every report we cannot handle */ + if (id == WII_REPORT_CORE_ONLY && length != 2) { + xTaskNotify(t_red, LED_ONCE, eSetValueWithOverwrite); + return ; + } + if (id == WII_REPORT_ACK && length != 4) { + xTaskNotify(t_red, LED_ONCE, eSetValueWithOverwrite); + return ; + } + if (id == WII_REPORT_CORE_ACCEL && length != 5) { + xTaskNotify(t_red, LED_ONCE, eSetValueWithOverwrite); + return ; + } + if (id == WII_REPORT_STATUS && length != 6) { + xTaskNotify(t_red, LED_ONCE, eSetValueWithOverwrite); + return ; + } + if (id == WII_REPORT_READ_INPUT && length != 21) { + xTaskNotify(t_red, LED_ONCE, eSetValueWithOverwrite); + return ; + } + + if (id != WII_REPORT_ACK && id != WII_REPORT_CORE_ONLY && + id != WII_REPORT_STATUS && id != WII_REPORT_CORE_ACCEL && + id != WII_REPORT_READ_INPUT) { + ESP_LOGW(TAG, "Unknown report id 0x%x received (size: %i)", id, length); + return; + } + + /* + * handle error codes or specific stuff from the wiimote + * we can continue to handle core buttons as usual later + */ + switch (id) { + case WII_REPORT_STATUS: + /* format is BB BB LF 00 00 VV (buttons, flags, battery) */ + ESP_LOGD(TAG, "status: 0x%x%x 0x%x %i", data[0], data[1], data[2], data[5]); + break; + case WII_REPORT_READ_INPUT: + /* assume a calibration read, since it's the only read request done */ + wii_show_calibration(data); + break; + case WII_REPORT_ACK: + /* format is BB BB RR EE (buttons, report number, error code) */ + if (data[3]) { + xTaskNotify(t_red, LED_ONCE, eSetValueWithOverwrite); + ESP_LOGE(TAG, "error with report id 0x%x, code %x", data[2], data[3]); + } + break; + case WII_REPORT_CORE_ACCEL: + wii_accelerometer(dev, data); + break; + default: + break; + } + + buttons = (data[1] << 8) | data[0]; + /* changing speed is disabled in accelerometer mode */ + if ((buttons & WII_BUTTON_PLUS || buttons & WII_BUTTON_MINUS) && length == 2) + wii_change_speed(dev, buttons); + + /* swap modes (enable / disable accelerometer) */ + if (buttons & WII_BUTTON_HOME) { + if (! changed) { + wii_change_mode(dev); + changed = true; + } + } + if (changed && !(buttons & WII_BUTTON_HOME)) + changed = false; + + /* do the buttons things */ + wii_handle_button(buttons); + xTaskNotify(t_wii_dpad, (uint32_t)buttons, eSetValueWithOverwrite); +} + +/* + * this will compare the provided BDA to a list of known Nintendo list + * return true if the provided BDA is a known prefix or fakse otherwise + */ +bool wii_is_nintendo_bda(esp_bd_addr_t bda) { + uint8_t i = 0; + + /* known "Nintendo Co., Ltd." BDA prefixes as of 08/2024 */ + const esp_bd_addr_t nintendo_bda_list[] = { + {0x00, 0x09, 0xbf, 0x00, 0x00, 0x00}, + {0x00, 0x16, 0x56, 0x00, 0x00, 0x00}, + {0x00, 0x17, 0xab, 0x00, 0x00, 0x00}, + {0x00, 0x19, 0x1d, 0x00, 0x00, 0x00}, + {0x00, 0x19, 0xfd, 0x00, 0x00, 0x00}, + {0x00, 0x1a, 0xe9, 0x00, 0x00, 0x00}, + {0x00, 0x1b, 0x7a, 0x00, 0x00, 0x00}, + {0x00, 0x1b, 0xea, 0x00, 0x00, 0x00}, + {0x00, 0x1c, 0xbe, 0x00, 0x00, 0x00}, + {0x00, 0x1d, 0xbc, 0x00, 0x00, 0x00}, + {0x00, 0x1e, 0x35, 0x00, 0x00, 0x00}, + {0x00, 0x1e, 0xa9, 0x00, 0x00, 0x00}, + {0x00, 0x1f, 0x32, 0x00, 0x00, 0x00}, + {0x00, 0x1f, 0xc5, 0x00, 0x00, 0x00}, + {0x00, 0x21, 0x47, 0x00, 0x00, 0x00}, + {0x00, 0x21, 0xbd, 0x00, 0x00, 0x00}, + {0x00, 0x22, 0x4c, 0x00, 0x00, 0x00}, + {0x00, 0x22, 0xaa, 0x00, 0x00, 0x00}, + {0x00, 0x22, 0xd7, 0x00, 0x00, 0x00}, + {0x00, 0x23, 0x31, 0x00, 0x00, 0x00}, + {0x00, 0x23, 0xcc, 0x00, 0x00, 0x00}, + {0x00, 0x24, 0x1e, 0x00, 0x00, 0x00}, + {0x00, 0x24, 0x44, 0x00, 0x00, 0x00}, + {0x00, 0x24, 0xf3, 0x00, 0x00, 0x00}, + {0x00, 0x25, 0xa0, 0x00, 0x00, 0x00}, + {0x00, 0x26, 0x59, 0x00, 0x00, 0x00}, + {0x00, 0x27, 0x09, 0x00, 0x00, 0x00}, + {0x18, 0x2a, 0x7b, 0x00, 0x00, 0x00}, + {0x2c, 0x10, 0xc1, 0x00, 0x00, 0x00}, + {0x34, 0xaf, 0x2c, 0x00, 0x00, 0x00}, + {0x40, 0xd2, 0x8a, 0x00, 0x00, 0x00}, + {0x40, 0xf4, 0x07, 0x00, 0x00, 0x00}, + {0x58, 0xbd, 0xa3, 0x00, 0x00, 0x00}, + {0x78, 0xa2, 0xa0, 0x00, 0x00, 0x00}, + {0x7c, 0xbb, 0x8a, 0x00, 0x00, 0x00}, + {0x8c, 0x56, 0xc5, 0x00, 0x00, 0x00}, + {0x8c, 0xcd, 0xe8, 0x00, 0x00, 0x00}, + {0x9c, 0xe6, 0x35, 0x00, 0x00, 0x00}, + {0xa4, 0x5c, 0x27, 0x00, 0x00, 0x00}, + {0xa4, 0xc0, 0xe1, 0x00, 0x00, 0x00}, + {0xb8, 0xae, 0x6e, 0x00, 0x00, 0x00}, + {0xcc, 0x9e, 0x00, 0x00, 0x00, 0x00}, + {0xcc, 0xfb, 0x65, 0x00, 0x00, 0x00}, + {0xd8, 0x6b, 0xf7, 0x00, 0x00, 0x00}, + {0xe0, 0x0c, 0x7f, 0x00, 0x00, 0x00}, + {0xe0, 0xe7, 0x51, 0x00, 0x00, 0x00}, + {0xe8, 0x4e, 0xce, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + }; + + while (nintendo_bda_list[i][0] || nintendo_bda_list[i][1] || nintendo_bda_list[i][2]) { + if (nintendo_bda_list[i][0] == bda[0] && + nintendo_bda_list[i][1] == bda[1] && + nintendo_bda_list[i][2] == bda[2]) { + return true; + } + + i++; + } + + return false; +} + +/* this will tell us if the device is a Wiimote. PID + VID provided by the HID stack */ +bool wii_is_wiimote(uint16_t vid, uint16_t pid) { + if (vid != WII_VID) + return false; + if (pid != WII_PID_OLD && pid != WII_PID_NEW) + return false; + + return true; +} + +/* this is called every time a Wiimote is connected */ +void wii_open(const esp_bd_addr_t bda) { + uint8_t report[7] = {0}; + esp_err_t ret; + + configASSERT(bda != NULL); + /* enable LED4 on connect */ + report[0] = WII_REPORT_LEDS; + report[1] = WII_LED4; + ret = esp_bt_hid_host_send_data((uint8_t *)bda, report, 2); + if (ret) + ESP_LOGE(TAG, "cannot set LED4 state, send error: %x", ret); + + /* set initial LEDs state */ + wii_speed = 0; + + /* stream the boot chime to the wii mote. this will also wake up the rumble */ + wii_chime(bda); + wii_rumble_state(bda, true); + vTaskDelay(400 / portTICK_PERIOD_MS); + wii_speed = WII_SPEED_MEDIUM; + wii_rumble_state(bda, false); + + /* do a read to fetch accelerometer calibration data from both spaces */ + report[0] = WII_REPORT_READ_REG; + report[1] = 0x00; /* EEPROM space */ + report[2] = 0x00; /* read from 0x000016 */ + report[3] = 0x00; /* read from 0x000016 */ + report[4] = 0x16; /* read from 0x000016 */ + report[5] = 0x00; /* read 10 bytes */ + report[6] = 0x0a; /* read 10 bytes */ + ret = esp_bt_hid_host_send_data((uint8_t *)bda, report, 7); + if (ret) + ESP_LOGE(TAG, "cannot read accelerometer calibration, send error: %x", ret); + report[0] = WII_REPORT_READ_REG; + report[1] = 0x00; /* EEPROM space */ + report[2] = 0x00; /* read from 0x000020 */ + report[3] = 0x00; /* read from 0x000020 */ + report[4] = 0x20; /* read from 0x000020 */ + report[5] = 0x00; /* read 10 bytes */ + report[6] = 0x0a; /* read 10 bytes */ + ret = esp_bt_hid_host_send_data((uint8_t *)bda, report, 7); + if (ret) + ESP_LOGE(TAG, "cannot read accelerometer calibration, send error: %x", ret); +} + +/* mute the speaker on the Wiimote. this will clear the rumble ! */ +static void wii_mute_state(const esp_bd_addr_t bda, bool state) { + uint8_t report[2] = {0}; + esp_err_t ret; + + configASSERT(bda != NULL); + ESP_LOGV(TAG, "setting mute to %x", state); + report[0] = WII_REPORT_SPEAKER_M; + if (state) + report[1] = 0x04; + else + report[1] = 0x0; + ret = esp_bt_hid_host_send_data((uint8_t *)bda, report, 2); + if (ret) + ESP_LOGE(TAG, "cannot set mute state, send error: %x", ret); +} + +/* enable the rumble on the Wiimote. this will clear the LEDs so keep the LEDs state */ +static void wii_rumble_state(const esp_bd_addr_t bda, bool state) { + uint8_t report[2] = {0}; + esp_err_t ret; + + if (state) { + report[0] = WII_REPORT_RUMBLE; + report[1] = 0x1 | WII_LED4; + } else { + report[0] = WII_REPORT_LEDS; + report[1] = wii_speed | WII_LED4; + } + ret = esp_bt_hid_host_send_data((uint8_t *)bda, report, 2); + if (ret) + ESP_LOGE(TAG, "cannot set rumble state, send error: %x", ret); +} + +/* enable the speaker on the Wiimote. this will clear the rumble ! */ +static void wii_speaker_state(const esp_bd_addr_t bda, bool state) { + uint8_t report[2] = {0}; + esp_err_t ret; + + configASSERT(bda != NULL); + ESP_LOGV(TAG, "setting speaker state to %x", state); + report[0] = WII_REPORT_SPEAKER_E; + if (state) + report[1] = 0x04; + else + report[1] = 0x0; + ret = esp_bt_hid_host_send_data((uint8_t *)bda, report, 2); + if (ret) + ESP_LOGE(TAG, "cannot set speaker state, send error: %x", ret); +} + +/* display calibration data. do not save it for now since we are calibrating ourselves */ +void wii_show_calibration(uint8_t *data) { + uint8_t i; + uint8_t checksum; + + configASSERT(data != NULL); + + ESP_LOGD(TAG, "EEPROM read from 0x%02x%02x", data[3], data[4]); + /* skip the header */ + data += 5; + wii_calibration_0g.x = ((data[0] << 2) | ((data[3] >> 4) & 3)); + wii_calibration_0g.y = ((data[1] << 2) | ((data[3] >> 2) & 3)); + wii_calibration_0g.z = ((data[2] << 2) | (data[3] & 3)); + + wii_calibration_1g.x = (((data[4] << 2) | ((data[7] >> 4) & 3)) - wii_calibration_0g.x); + wii_calibration_1g.y = (((data[5] << 2) | ((data[7] >> 2) & 3)) - wii_calibration_0g.y); + wii_calibration_1g.z = (((data[6] << 2) | (data[7] & 3)) - wii_calibration_0g.z); + + ESP_LOGD(TAG, "Wiimote calibration data: 0G (%i %i %i) 1G (%i %i %i)…", + wii_calibration_0g.x, wii_calibration_0g.y, wii_calibration_0g.z, + wii_calibration_1g.x, wii_calibration_1g.y, wii_calibration_1g.z); + + checksum = 0; + for (i = 0 ; i < 9 ; i++) + checksum += data[i]; + checksum += 0x55; + ESP_LOGD(TAG, "\t… checksum read: %02X, calculated: %02X", data[9], checksum); + if (data[9] != checksum) + xTaskNotify(t_red, LED_ONCE, eSetValueWithOverwrite); +} \ No newline at end of file diff --git a/main/wii.h b/main/wii.h new file mode 100644 index 0000000..0b96d66 --- /dev/null +++ b/main/wii.h @@ -0,0 +1,98 @@ +/* + * wii.h + * quack + * + * Created by Michel DEPEIGE on 11/08/2024. + * Copyright (c) 2024 Michel DEPEIGE. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the Apache License, Version 2.0 (the "License"); + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef WII_H +#define WII_H + +/* prototypes */ +uint8_t *wii_generate_pin(esp_bt_pin_code_t pin); +void wii_init(void); +void wii_input(esp_hidh_dev_t *dev, uint16_t id, uint8_t *data, uint16_t length); +bool wii_is_nintendo_bda(esp_bd_addr_t bda); +bool wii_is_wiimote(uint16_t vid, uint16_t pid); +void wii_open(const esp_bd_addr_t bda); +void wii_show_calibration(uint8_t *data); + +/* defines */ +#define WII_VID 0x057e +#define WII_PID_OLD 0x0306 +#define WII_PID_NEW 0x0330 + +#define WII_PIN_STR "0x%02x%02x%02x%02x%02x%02x" +#define WII_PIN_HEX(pin) pin[0], pin[1], pin[2], pin[3], pin[4], pin[5] + +#define WII_THRESHOLD 0.1f +#define WII_ACCELERATION -10.0f /* coonvert gravity to pixels */ +#define WII_INTERVAL (1000 / 60) /* pad update interval, about 60 Hz */ +#define WII_THROTTLE 20 + +/* core buttons. LX6 is wrong endian */ +#define WII_BUTTON_NONE 0x0000 +#define WII_BUTTON_LEFT 0x0001 +#define WII_BUTTON_RIGHT 0x0002 +#define WII_BUTTON_DOWN 0x0004 +#define WII_BUTTON_UP 0x0008 +#define WII_BUTTON_PLUS 0x0010 +#define WII_BUTTON_TWO 0x0100 +#define WII_BUTTON_ONE 0x0200 +#define WII_BUTTON_B 0x0400 +#define WII_BUTTON_A 0x0800 +#define WII_BUTTON_MINUS 0x1000 +#define WII_BUTTON_HOME 0x8000 + +/* LEDs + Rumble */ +#define WII_LED1 0x10 +#define WII_LED2 0x20 +#define WII_LED3 0x40 +#define WII_LED4 0x80 + +/* for quadrature speed feedback */ +#define WII_SPEED_NONE 0x00 +#define WII_SPEED_LOW WII_LED1 +#define WII_SPEED_MEDIUM WII_LED2 +#define WII_SPEED_HIGH WII_LED3 + +/* handled reports */ +#define WII_REPORT_RUMBLE 0x10 +#define WII_REPORT_LEDS 0x11 +#define WII_REPORT_MODE 0x12 +#define WII_REPORT_SPEAKER_E 0x14 +#define WII_REPORT_REQUEST 0x15 +#define WII_REPORT_WRITE_REG 0x16 +#define WII_REPORT_READ_REG 0x17 +#define WII_REPORT_SPEAKER_D 0x18 +#define WII_REPORT_SPEAKER_M 0x19 +#define WII_REPORT_STATUS 0x20 +#define WII_REPORT_READ_INPUT 0x21 +#define WII_REPORT_ACK 0x22 +#define WII_REPORT_CORE_ONLY 0x30 +#define WII_REPORT_CORE_ACCEL 0x31 + +#define WII_REPORT_INPUT WII_REPORT_ACK|WII_REPORT_CORE_ONLY + +/* structures */ +typedef struct wii_accel_s { + uint16_t x; + uint16_t y; + uint16_t z; +} wii_accel_t; + +#endif