Add Switch 2 wake capture and replay
This commit is contained in:
parent
8818f1f6e9
commit
6f58d345e0
22 changed files with 1436 additions and 3 deletions
19
tools/switch2_wake_capture/CMakeLists.txt
Normal file
19
tools/switch2_wake_capture/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
cmake_minimum_required(VERSION 3.13)
|
||||
set(PICO_BOARD pico2_w CACHE STRING "Target board")
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../../pico_sdk_import.cmake)
|
||||
project(switch2_wake_capture C CXX ASM)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
pico_sdk_init()
|
||||
add_executable(switch2-wake-capture main.c)
|
||||
target_include_directories(switch2-wake-capture PRIVATE ${CMAKE_CURRENT_LIST_DIR})
|
||||
target_link_libraries(switch2-wake-capture PRIVATE
|
||||
pico_stdlib
|
||||
pico_cyw43_arch_none
|
||||
pico_btstack_cyw43
|
||||
pico_btstack_ble
|
||||
)
|
||||
pico_enable_stdio_usb(switch2-wake-capture 1)
|
||||
pico_enable_stdio_uart(switch2-wake-capture 0)
|
||||
pico_set_program_name(switch2-wake-capture "Switch 2 wake capture")
|
||||
pico_set_program_version(switch2-wake-capture "1.0")
|
||||
pico_add_extra_outputs(switch2-wake-capture)
|
||||
30
tools/switch2_wake_capture/btstack_config.h
Normal file
30
tools/switch2_wake_capture/btstack_config.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#ifndef SWITCH2_WAKE_CAPTURE_BTSTACK_CONFIG_H
|
||||
#define SWITCH2_WAKE_CAPTURE_BTSTACK_CONFIG_H
|
||||
|
||||
#define ENABLE_LE_CENTRAL
|
||||
#define ENABLE_LE_PERIPHERAL
|
||||
#define ENABLE_LOG_ERROR
|
||||
#define ENABLE_PRINTF_HEXDUMP
|
||||
#define HAVE_EMBEDDED_TIME_MS
|
||||
#define HAVE_ASSERT
|
||||
#define HCI_OUTGOING_PRE_BUFFER_SIZE 4
|
||||
#define HCI_ACL_PAYLOAD_SIZE (1691 + 4)
|
||||
#define HCI_ACL_CHUNK_SIZE_ALIGNMENT 4
|
||||
#define MAX_NR_HCI_CONNECTIONS 1
|
||||
#define MAX_NR_GATT_CLIENTS 1
|
||||
#define MAX_NR_L2CAP_CHANNELS 2
|
||||
#define MAX_NR_L2CAP_SERVICES 2
|
||||
#define MAX_NR_SM_LOOKUP_ENTRIES 1
|
||||
#define MAX_NR_WHITELIST_ENTRIES 1
|
||||
#define MAX_NR_LE_DEVICE_DB_ENTRIES 16
|
||||
#define NVM_NUM_DEVICE_DB_ENTRIES 16
|
||||
#define MAX_ATT_DB_SIZE 128
|
||||
#define MAX_NR_CONTROLLER_ACL_BUFFERS 3
|
||||
#define ENABLE_HCI_CONTROLLER_TO_HOST_FLOW_CONTROL
|
||||
#define HCI_HOST_ACL_PACKET_LEN 1024
|
||||
#define HCI_HOST_ACL_PACKET_NUM 3
|
||||
#define HCI_HOST_SCO_PACKET_LEN 120
|
||||
#define HCI_HOST_SCO_PACKET_NUM 3
|
||||
#define HCI_RESET_RESEND_TIMEOUT_MS 1000
|
||||
|
||||
#endif
|
||||
152
tools/switch2_wake_capture/main.c
Normal file
152
tools/switch2_wake_capture/main.c
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include "pico/stdio_usb.h"
|
||||
#include "pico/stdlib.h"
|
||||
#include "btstack.h"
|
||||
#include "nintendo_wake.h"
|
||||
|
||||
typedef struct {
|
||||
bd_addr_t advertiser;
|
||||
uint8_t address_type;
|
||||
uint8_t event_type;
|
||||
int8_t rssi;
|
||||
uint8_t data[31];
|
||||
} wake_capture_t;
|
||||
|
||||
static wake_capture_t capture;
|
||||
static bool captured;
|
||||
static bool scan_started;
|
||||
static bool usb_was_connected;
|
||||
static uint32_t last_print_ms;
|
||||
static btstack_packet_callback_registration_t registration;
|
||||
static btstack_timer_source_t output_timer;
|
||||
|
||||
static void to_hex(const uint8_t* bytes, size_t size, char* output) {
|
||||
static const char digits[] = "0123456789ABCDEF";
|
||||
for (size_t index = 0; index < size; ++index) {
|
||||
output[index * 2] = digits[bytes[index] >> 4];
|
||||
output[index * 2 + 1] = digits[bytes[index] & 0x0f];
|
||||
}
|
||||
output[size * 2] = 0;
|
||||
}
|
||||
|
||||
static void print_capture(void) {
|
||||
const uint8_t* manufacturer = &capture.data[5];
|
||||
char raw_hex[63];
|
||||
char payload_hex[49];
|
||||
to_hex(capture.data, sizeof(capture.data), raw_hex);
|
||||
// ESPHome-style payload excludes the two-byte Nintendo company ID.
|
||||
to_hex(&manufacturer[2], 24, payload_hex);
|
||||
printf("SWITCH2_WAKE_CAPTURE {\"version\":1,\"advertiser\":"
|
||||
"\"%02X:%02X:%02X:%02X:%02X:%02X\",\"address_type\":%u,"
|
||||
"\"event_type\":%u,\"rssi\":%d,\"pid\":\"%04X\","
|
||||
"\"console\":\"%02X:%02X:%02X:%02X:%02X:%02X\","
|
||||
"\"raw_hex\":\"%s\",\"esphome_payload_hex\":\"%s\"}\n",
|
||||
capture.advertiser[0], capture.advertiser[1],
|
||||
capture.advertiser[2], capture.advertiser[3],
|
||||
capture.advertiser[4], capture.advertiser[5],
|
||||
capture.address_type, capture.event_type, (int)capture.rssi,
|
||||
(unsigned)(manufacturer[7] | (manufacturer[8] << 8)),
|
||||
manufacturer[17], manufacturer[16], manufacturer[15],
|
||||
manufacturer[14], manufacturer[13], manufacturer[12],
|
||||
raw_hex, payload_hex);
|
||||
}
|
||||
|
||||
static void output_task(btstack_timer_source_t* timer) {
|
||||
const uint32_t now_ms = btstack_run_loop_get_time_ms();
|
||||
const bool usb_connected = stdio_usb_connected();
|
||||
if (usb_connected && !usb_was_connected) {
|
||||
puts("Switch 2 wake capture ready. Put the console to sleep, then wake it with a paired Joy-Con 2 HOME button.");
|
||||
puts("Scanning stops automatically after one valid public ADV_IND wake packet.");
|
||||
if (captured) {
|
||||
print_capture();
|
||||
last_print_ms = now_ms;
|
||||
}
|
||||
}
|
||||
if (usb_connected) {
|
||||
int key;
|
||||
while ((key = getchar_timeout_us(0)) != PICO_ERROR_TIMEOUT) {
|
||||
if ((key == 'p' || key == 'P') && captured) {
|
||||
print_capture();
|
||||
last_print_ms = now_ms;
|
||||
}
|
||||
}
|
||||
// Repeat for a generator attached after capture; scanning remains stopped.
|
||||
if (captured && now_ms - last_print_ms >= 1000) {
|
||||
print_capture();
|
||||
last_print_ms = now_ms;
|
||||
}
|
||||
}
|
||||
usb_was_connected = usb_connected;
|
||||
static bool blink;
|
||||
blink = !blink;
|
||||
cyw43_arch_gpio_put(
|
||||
CYW43_WL_GPIO_LED_PIN, captured || (scan_started && blink));
|
||||
btstack_run_loop_set_timer(timer, 250);
|
||||
btstack_run_loop_add_timer(timer);
|
||||
}
|
||||
|
||||
static void handle_packet(uint8_t packet_type, uint16_t,
|
||||
uint8_t* packet, uint16_t size) {
|
||||
if (packet_type != HCI_EVENT_PACKET || size < 2 || captured) {
|
||||
return;
|
||||
}
|
||||
const uint8_t event_type = hci_event_packet_get_type(packet);
|
||||
if (event_type == BTSTACK_EVENT_STATE && size >= 3 &&
|
||||
btstack_event_state_get_state(packet) == HCI_STATE_WORKING) {
|
||||
gap_set_scan_params(0, 96, 96, 0); // Passive, continuous, accept all.
|
||||
gap_set_scan_duplicate_filter(false);
|
||||
gap_start_scan();
|
||||
scan_started = true;
|
||||
return;
|
||||
}
|
||||
if (event_type != GAP_EVENT_ADVERTISING_REPORT || size < 12) {
|
||||
return;
|
||||
}
|
||||
const uint8_t length =
|
||||
gap_event_advertising_report_get_data_length(packet);
|
||||
if (length != sizeof(capture.data) || size < 12u + length ||
|
||||
gap_event_advertising_report_get_address_type(packet) !=
|
||||
BD_ADDR_TYPE_LE_PUBLIC ||
|
||||
gap_event_advertising_report_get_advertising_event_type(packet) != 0) {
|
||||
return;
|
||||
}
|
||||
const uint8_t* data =
|
||||
gap_event_advertising_report_get_data(packet);
|
||||
if (switch2_wake_manufacturer(data, length) == NULL) {
|
||||
return;
|
||||
}
|
||||
gap_event_advertising_report_get_address(packet, capture.advertiser);
|
||||
capture.address_type = BD_ADDR_TYPE_LE_PUBLIC;
|
||||
capture.event_type = 0;
|
||||
capture.rssi =
|
||||
(int8_t)gap_event_advertising_report_get_rssi(packet);
|
||||
memcpy(capture.data, data, sizeof(capture.data));
|
||||
captured = true;
|
||||
scan_started = false;
|
||||
gap_stop_scan();
|
||||
if (stdio_usb_connected()) {
|
||||
print_capture();
|
||||
last_print_ms = btstack_run_loop_get_time_ms();
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
stdio_init_all();
|
||||
if (cyw43_arch_init() != 0) {
|
||||
while (true) {
|
||||
puts("ERROR: CYW43 initialization failed");
|
||||
sleep_ms(1000);
|
||||
}
|
||||
}
|
||||
registration.callback = handle_packet;
|
||||
hci_add_event_handler(®istration);
|
||||
btstack_run_loop_set_timer_handler(&output_timer, output_task);
|
||||
btstack_run_loop_set_timer(&output_timer, 250);
|
||||
btstack_run_loop_add_timer(&output_timer);
|
||||
hci_power_control(HCI_POWER_ON);
|
||||
btstack_run_loop_execute();
|
||||
return 0;
|
||||
}
|
||||
34
tools/switch2_wake_capture/nintendo_wake.h
Normal file
34
tools/switch2_wake_capture/nintendo_wake.h
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#ifndef SWITCH2_WAKE_CAPTURE_NINTENDO_WAKE_H
|
||||
#define SWITCH2_WAKE_CAPTURE_NINTENDO_WAKE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
static inline const uint8_t* switch2_wake_manufacturer(
|
||||
const uint8_t* data, size_t size) {
|
||||
static const uint8_t prefix[] = {
|
||||
0x53, 0x05, 0x01, 0x00, 0x03, 0x7e, 0x05,
|
||||
};
|
||||
static const uint8_t suffix[] = {
|
||||
0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
if (size != 31 || memcmp(data, "\x02\x01\x06\x1b\xff", 5) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
const uint8_t* manufacturer = &data[5];
|
||||
if (memcmp(manufacturer, prefix, sizeof(prefix)) != 0 ||
|
||||
manufacturer[9] != 0x00 || manufacturer[10] != 0x01 ||
|
||||
manufacturer[11] != 0x81 ||
|
||||
memcmp(&manufacturer[18], suffix, sizeof(suffix)) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
uint8_t target_any = 0;
|
||||
for (size_t index = 12; index < 18; ++index) {
|
||||
target_any |= manufacturer[index];
|
||||
}
|
||||
return target_any == 0 ? NULL : manufacturer;
|
||||
}
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue