feat: add opt-in native Nintendo rumble backend
This commit is contained in:
parent
71fcf5bb94
commit
3fc28b1fa4
44 changed files with 4834 additions and 240 deletions
|
|
@ -265,10 +265,9 @@ index 0265f93..5c0f2bb 100644
|
|||
return (psmove_instance_t*)&d->parser_data[0];
|
||||
}
|
||||
diff --git a/src/components/bluepad32/parser/uni_hid_parser_switch.c b/src/components/bluepad32/parser/uni_hid_parser_switch.c
|
||||
index 599fc35..0105c53 100644
|
||||
--- a/src/components/bluepad32/parser/uni_hid_parser_switch.c
|
||||
+++ b/src/components/bluepad32/parser/uni_hid_parser_switch.c
|
||||
@@ -51,13 +51,22 @@ static const int16_t DEFAULT_ACCEL_OFFSET = 0;
|
||||
@@ -51,13 +51,22 @@
|
||||
static const int16_t DEFAULT_ACCEL_SCALE = 16384;
|
||||
static const int16_t DEFAULT_GYRO_OFFSET = 0;
|
||||
static const int16_t DEFAULT_GYRO_SCALE = 13371;
|
||||
|
|
@ -292,7 +291,7 @@ index 599fc35..0105c53 100644
|
|||
#if ENABLE_SPI_FLASH_DUMP
|
||||
static const uint32_t SWITCH_DUMP_ROM_DATA_ADDR_START = 0x20000;
|
||||
static const uint32_t SWITCH_DUMP_ROM_DATA_ADDR_END = 0x30000;
|
||||
@@ -72,6 +81,7 @@ enum switch_state {
|
||||
@@ -72,6 +81,7 @@
|
||||
STATE_READ_FACTORY_IMU_CALIBRATION, // Factory IMU calibration info
|
||||
STATE_SET_FULL_REPORT, // Request report 0x30
|
||||
STATE_ENABLE_IMU, // Enable/Disable gyro/accel
|
||||
|
|
@ -300,7 +299,7 @@ index 599fc35..0105c53 100644
|
|||
STATE_DUMP_FLASH, // Dump SPI Flash memory
|
||||
STATE_UPDATE_LED, // Update LEDs
|
||||
STATE_READY, // Gamepad setup ready!
|
||||
@@ -111,6 +121,7 @@ enum switch_subcmd {
|
||||
@@ -111,6 +121,7 @@
|
||||
SUBCMD_SPI_FLASH_READ = 0x10,
|
||||
SUBCMD_SET_PLAYER_LEDS = 0x30,
|
||||
SUBCMD_ENABLE_IMU = 0x40,
|
||||
|
|
@ -308,15 +307,20 @@ index 599fc35..0105c53 100644
|
|||
};
|
||||
|
||||
typedef enum {
|
||||
@@ -137,6 +148,7 @@ typedef struct switch_instance_s {
|
||||
@@ -137,7 +148,12 @@
|
||||
// Although technically, we can use one timer for delay and duration, easier to debug/maintain if we have two.
|
||||
btstack_timer_source_t rumble_timer_duration;
|
||||
btstack_timer_source_t rumble_timer_delayed_start;
|
||||
+ btstack_timer_source_t rumble_timer_refresh;
|
||||
switch_state_rumble_t rumble_state;
|
||||
+ bool native_owned;
|
||||
+ bool device_info_valid;
|
||||
+ uint8_t packet_num;
|
||||
+ uint8_t effective_rumble[8];
|
||||
|
||||
btstack_timer_source_t setup_timer;
|
||||
@@ -322,6 +334,7 @@ static void fsm_read_user_stick_calibration(struct uni_hid_device_s* d);
|
||||
|
||||
@@ -322,6 +338,7 @@
|
||||
static void fsm_read_factory_imu_calibration(struct uni_hid_device_s* d);
|
||||
static void fsm_set_full_report(struct uni_hid_device_s* d);
|
||||
static void fsm_enable_imu(struct uni_hid_device_s* d);
|
||||
|
|
@ -324,7 +328,7 @@ index 599fc35..0105c53 100644
|
|||
static void fsm_update_led(struct uni_hid_device_s* d);
|
||||
static void fsm_ready(struct uni_hid_device_s* d);
|
||||
static void process_reply_read_spi_dump(struct uni_hid_device_s* d, const uint8_t* data, int len);
|
||||
@@ -333,11 +346,16 @@ static void process_reply_set_report_mode(struct uni_hid_device_s* d, const stru
|
||||
@@ -333,23 +350,33 @@
|
||||
static void process_reply_spi_flash_read(struct uni_hid_device_s* d, const struct switch_report_21_s* r, int len);
|
||||
static void process_reply_set_player_leds(struct uni_hid_device_s* d, const struct switch_report_21_s* r, int len);
|
||||
static void process_reply_enable_imu(struct uni_hid_device_s* d, const struct switch_report_21_s* r, int len);
|
||||
|
|
@ -341,7 +345,24 @@ index 599fc35..0105c53 100644
|
|||
static void switch_play_dual_rumble_now(uni_hid_device_t* d,
|
||||
uint16_t duration_ms,
|
||||
uint8_t weak_magnitude,
|
||||
@@ -451,6 +469,10 @@ static void process_fsm(struct uni_hid_device_s* d) {
|
||||
uint8_t strong_magnitude);
|
||||
static void switch_setup_timeout_callback(btstack_timer_source_t* ts);
|
||||
static void parse_stick_calibration(switch_cal_stick_t* x, switch_cal_stick_t* y, const uint8_t* data, bool is_left);
|
||||
+static const uint8_t switch_neutral_rumble[8] = {
|
||||
+ 0x00, 0x01, 0x40, 0x40, 0x00, 0x01, 0x40, 0x40,
|
||||
+};
|
||||
+static void switch_cancel_rumble_timers(switch_instance_t* ins);
|
||||
|
||||
void uni_hid_parser_switch_setup(struct uni_hid_device_s* d) {
|
||||
switch_instance_t* ins = get_switch_instance(d);
|
||||
|
||||
memset(ins, 0, sizeof(*ins));
|
||||
|
||||
+ memcpy(ins->effective_rumble, switch_neutral_rumble, sizeof(ins->effective_rumble));
|
||||
ins->state = STATE_SETUP;
|
||||
ins->mode = SWITCH_MODE_NONE;
|
||||
// In case the controller doesn't answer to SUBCMD_REQ_DEV_INFO command, set a default one.
|
||||
@@ -451,6 +478,10 @@
|
||||
break;
|
||||
case STATE_ENABLE_IMU:
|
||||
logd("STATE_ENABLE_IMU\n");
|
||||
|
|
@ -352,7 +373,25 @@ index 599fc35..0105c53 100644
|
|||
fsm_dump_rom(d);
|
||||
break;
|
||||
case STATE_DUMP_FLASH:
|
||||
@@ -725,6 +747,12 @@ static void process_reply_enable_imu(struct uni_hid_device_s* d, const struct sw
|
||||
@@ -648,7 +679,8 @@
|
||||
|
||||
// Reply to SUBCMD_REQ_DEV_INFO
|
||||
static void process_reply_req_dev_info(struct uni_hid_device_s* d, const struct switch_report_21_s* r, int len) {
|
||||
- ARG_UNUSED(len);
|
||||
+ if (len < (int)sizeof(*r) + 3 || !(r->ack & 0x80))
|
||||
+ return;
|
||||
switch_instance_t* ins = get_switch_instance(d);
|
||||
if (ins->state > STATE_SETUP && ins->mode == SWITCH_MODE_NONE) {
|
||||
bool enable_imu;
|
||||
@@ -669,6 +701,7 @@
|
||||
ins->firmware_version_hi = r->data[0];
|
||||
ins->firmware_version_lo = r->data[1];
|
||||
ins->controller_type = r->data[2];
|
||||
+ ins->device_info_valid = true;
|
||||
logi("Switch: Firmware version: %d.%d. Controller type=%d\n", r->data[0], r->data[1], r->data[2]);
|
||||
}
|
||||
|
||||
@@ -725,6 +758,12 @@
|
||||
ARG_UNUSED(r);
|
||||
ARG_UNUSED(len);
|
||||
}
|
||||
|
|
@ -365,17 +404,26 @@ index 599fc35..0105c53 100644
|
|||
|
||||
// Process 0x21 input report: SWITCH_INPUT_SUBCMD_REPLY
|
||||
static void process_input_subcmd_reply(struct uni_hid_device_s* d, const uint8_t* report, int len) {
|
||||
@@ -752,6 +780,9 @@ static void process_input_subcmd_reply(struct uni_hid_device_s* d, const uint8_t
|
||||
@@ -733,6 +772,8 @@
|
||||
// 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
// 00
|
||||
const struct switch_report_21_s* r = (const struct switch_report_21_s*)report;
|
||||
+ if (len < (int)sizeof(*r))
|
||||
+ return;
|
||||
if ((r->ack & 0b10000000) == 0) {
|
||||
loge("Switch: Error, subcommand id=0x%02x was not successful.\n", r->subcmd_id);
|
||||
}
|
||||
@@ -751,6 +792,9 @@
|
||||
break;
|
||||
case SUBCMD_ENABLE_IMU:
|
||||
process_reply_enable_imu(d, r, len);
|
||||
break;
|
||||
+ break;
|
||||
+ case SUBCMD_ENABLE_RUMBLE:
|
||||
+ process_reply_enable_rumble(d, r, len);
|
||||
+ break;
|
||||
break;
|
||||
default:
|
||||
loge("Switch: Error, unexpected subcmd_id=0x%02x in report 0x21\n", r->subcmd_id);
|
||||
break;
|
||||
@@ -823,19 +854,26 @@ static void parse_imu(uni_hid_device_t* d, const struct switch_imu_data_s* r) {
|
||||
@@ -823,19 +867,26 @@
|
||||
switch_instance_t* ins = get_switch_instance(d);
|
||||
uni_controller_t* ctl = &d->controller;
|
||||
|
||||
|
|
@ -391,6 +439,9 @@ index 599fc35..0105c53 100644
|
|||
- accel[i] = (r->accel[i] * ins->cal_accel.scale[i]) / ins->imu_cal_accel_divisor[i];
|
||||
- gyro[i] = mult_frac((SWITCH_IMU_PREC_RANGE_SCALE * (r->gyro[i] - ins->cal_gyro.offset[i])),
|
||||
- ins->cal_gyro.scale[i], ins->imu_cal_gyro_divisor[i]);
|
||||
- }
|
||||
-
|
||||
- // Right joycon has Y and Z axes negated.
|
||||
+ if (ins->imu_cal_accel_divisor[i] == 0) {
|
||||
+ accel[i] = r->accel[i] * 2;
|
||||
+ } else {
|
||||
|
|
@ -404,14 +455,13 @@ index 599fc35..0105c53 100644
|
|||
+ 936 * SWITCH_IMU_GYRO_RES_PER_DEG_S,
|
||||
+ ins->imu_cal_gyro_divisor[i]);
|
||||
+ }
|
||||
}
|
||||
|
||||
- // Right joycon has Y and Z axes negated.
|
||||
+ }
|
||||
+
|
||||
+ // Right Joy-Con has native Y and Z axes negated.
|
||||
if (ins->controller_type == SWITCH_CONTROLLER_TYPE_JCR) {
|
||||
accel[1] = -accel[1];
|
||||
accel[2] = -accel[2];
|
||||
@@ -843,10 +881,13 @@ static void parse_imu(uni_hid_device_t* d, const struct switch_imu_data_s* r) {
|
||||
@@ -843,10 +894,13 @@
|
||||
gyro[2] = -gyro[2];
|
||||
}
|
||||
|
||||
|
|
@ -429,7 +479,7 @@ index 599fc35..0105c53 100644
|
|||
}
|
||||
|
||||
// Process 0x30 input report: SWITCH_INPUT_IMU_DATA
|
||||
@@ -1172,6 +1213,18 @@ static void fsm_enable_imu(struct uni_hid_device_s* d) {
|
||||
@@ -1172,6 +1226,18 @@
|
||||
req->data[0] = (ins->mode == SWITCH_MODE_IMU);
|
||||
send_subcmd(d, req, sizeof(out));
|
||||
}
|
||||
|
|
@ -448,7 +498,7 @@ index 599fc35..0105c53 100644
|
|||
|
||||
static void fsm_update_led(struct uni_hid_device_s* d) {
|
||||
switch_instance_t* ins = get_switch_instance(d);
|
||||
@@ -1203,6 +1256,13 @@ static struct switch_rumble_freq_data find_rumble_freq(uint16_t freq) {
|
||||
@@ -1203,6 +1269,13 @@
|
||||
return rumble_freqs[i];
|
||||
}
|
||||
|
||||
|
|
@ -462,7 +512,15 @@ index 599fc35..0105c53 100644
|
|||
static struct switch_rumble_amp_data find_rumble_amp(uint16_t amp) {
|
||||
unsigned int i = 0;
|
||||
if (amp > rumble_amps[0].amp) {
|
||||
@@ -1259,6 +1319,7 @@ void uni_hid_parser_switch_play_dual_rumble(struct uni_hid_device_s* d,
|
||||
@@ -1253,12 +1326,15 @@
|
||||
}
|
||||
|
||||
switch_instance_t* ins = get_switch_instance(d);
|
||||
+ if (ins->native_owned || ins->state == STATE_UNINIT)
|
||||
+ return;
|
||||
switch (ins->rumble_state) {
|
||||
case SWITCH_STATE_RUMBLE_DELAYED:
|
||||
btstack_run_loop_remove_timer(&ins->rumble_timer_delayed_start);
|
||||
break;
|
||||
case SWITCH_STATE_RUMBLE_IN_PROGRESS:
|
||||
btstack_run_loop_remove_timer(&ins->rumble_timer_duration);
|
||||
|
|
@ -470,15 +528,181 @@ index 599fc35..0105c53 100644
|
|||
break;
|
||||
default:
|
||||
// Do nothing
|
||||
@@ -1366,6 +1427,7 @@ static void switch_stop_rumble_now(uni_hid_device_t* d) {
|
||||
@@ -1338,12 +1414,157 @@
|
||||
}
|
||||
|
||||
// No need to protect it with a mutex since it runs in the same main thread
|
||||
assert(ins->rumble_state == SWITCH_STATE_RUMBLE_IN_PROGRESS);
|
||||
static void send_subcmd(uni_hid_device_t* d, struct switch_subcmd_request* r, int len) {
|
||||
- static uint8_t packet_num = 0;
|
||||
- r->packet_num = packet_num++;
|
||||
- if (packet_num > 0x0f)
|
||||
- packet_num = 0;
|
||||
+ // Counter and effective rumble are refreshed by the device-send hook at
|
||||
+ // actual submission, not here: this report may spend time in the queue.
|
||||
r->transaction_type = (HID_MESSAGE_TYPE_DATA << 4) | HID_REPORT_TYPE_OUTPUT;
|
||||
uni_hid_device_send_intr_report(d, (const uint8_t*)r, len);
|
||||
+}
|
||||
+
|
||||
+static void switch_cancel_rumble_timers(switch_instance_t* ins) {
|
||||
+ btstack_run_loop_remove_timer(&ins->rumble_timer_duration);
|
||||
+ btstack_run_loop_remove_timer(&ins->rumble_timer_delayed_start);
|
||||
+ btstack_run_loop_remove_timer(&ins->rumble_timer_refresh);
|
||||
+ ins->rumble_timer_duration.context = NULL;
|
||||
+ ins->rumble_timer_delayed_start.context = NULL;
|
||||
+ ins->rumble_timer_refresh.context = NULL;
|
||||
+ ins->rumble_state = SWITCH_STATE_RUMBLE_DISABLED;
|
||||
+}
|
||||
+
|
||||
+// Preserve queued setup/LED subcommands, but retire every old rumble writer
|
||||
+// across ownership changes. Compact in place with a fixed queue-size bound.
|
||||
+static void switch_discard_queued_rumble(uni_hid_device_t* d) {
|
||||
+ uni_circular_buffer_t* queue = &d->outgoing_buffer;
|
||||
+ int read_idx = queue->head_idx;
|
||||
+ int write_idx = read_idx;
|
||||
+ for (int n = 0; n < UNI_CIRCULAR_BUFFER_SIZE && read_idx != queue->tail_idx; ++n) {
|
||||
+ const uni_circular_buffer_data_t* entry = &queue->buffer[read_idx];
|
||||
+ const bool rumble = entry->cid == d->conn.interrupt_cid &&
|
||||
+ entry->data_len >= 2 && entry->data[0] == 0xa2 &&
|
||||
+ entry->data[1] == OUTPUT_RUMBLE_ONLY;
|
||||
+ if (!rumble) {
|
||||
+ if (write_idx != read_idx)
|
||||
+ queue->buffer[write_idx] = *entry;
|
||||
+ write_idx = (write_idx + 1) % UNI_CIRCULAR_BUFFER_SIZE;
|
||||
+ }
|
||||
+ read_idx = (read_idx + 1) % UNI_CIRCULAR_BUFFER_SIZE;
|
||||
+ }
|
||||
+ queue->tail_idx = write_idx;
|
||||
+}
|
||||
+
|
||||
+// Both writers use this at the actual L2CAP boundary. Failed sends neither
|
||||
+// consume a counter nor change the effective actuator-word cache.
|
||||
+static int switch_submit_report(uni_hid_device_t* d, uint16_t cid, uint8_t* report, uint16_t len) {
|
||||
+ switch_instance_t* ins = get_switch_instance(d);
|
||||
+ report[2] = ins->packet_num;
|
||||
+ const int status = l2cap_send(cid, report, len);
|
||||
+ if (status == ERROR_CODE_SUCCESS) {
|
||||
+ ins->packet_num = (ins->packet_num + 1) & 0x0f;
|
||||
+ memcpy(ins->effective_rumble, &report[3], sizeof(ins->effective_rumble));
|
||||
+ }
|
||||
+ return status;
|
||||
+}
|
||||
+
|
||||
+int uni_hid_parser_switch_send_report(uni_hid_device_t* d, uint16_t cid,
|
||||
+ const uint8_t* report, uint16_t len) {
|
||||
+ switch_instance_t* ins = get_switch_instance(d);
|
||||
+ if (ins->state == STATE_UNINIT)
|
||||
+ return ERROR_CODE_SUCCESS; // A retired device must not enqueue work.
|
||||
+ if (cid != d->conn.interrupt_cid || len < 11 || report[0] != 0xa2 ||
|
||||
+ (report[1] != OUTPUT_RUMBLE_ONLY && report[1] != OUTPUT_RUMBLE_AND_SUBCMD))
|
||||
+ return l2cap_send(cid, (uint8_t*)report, len);
|
||||
+ if (ins->native_owned && report[1] == OUTPUT_RUMBLE_ONLY)
|
||||
+ return ERROR_CODE_SUCCESS; // Only native_send may write this owner.
|
||||
+ if (len > UNI_CIRCULAR_BUFFER_DATA_SIZE)
|
||||
+ return ERROR_CODE_COMMAND_DISALLOWED;
|
||||
+
|
||||
+ uint8_t outgoing[UNI_CIRCULAR_BUFFER_DATA_SIZE];
|
||||
+ memcpy(outgoing, report, len);
|
||||
+ if (report[1] == OUTPUT_RUMBLE_AND_SUBCMD) {
|
||||
+ // Repeating the SAME last accepted actuator word is Nintendo's hold
|
||||
+ // behavior, including compressed words. Never use the queued copy:
|
||||
+ // replaying an earlier word after intervening audio reapplies deltas.
|
||||
+ // Do not synthesize an absolute endpoint here either: it changes the
|
||||
+ // repeated-word history. Physical acceptance is model-qualified.
|
||||
+ memcpy(&outgoing[3], ins->effective_rumble, sizeof(ins->effective_rumble));
|
||||
+ if (len >= 13 && outgoing[11] == SUBCMD_SET_PLAYER_LEDS)
|
||||
+ outgoing[12] = ins->gamepad_seat & 0x0f;
|
||||
+ }
|
||||
+ return switch_submit_report(d, cid, outgoing, len);
|
||||
+}
|
||||
+
|
||||
+bool uni_hid_parser_switch_native_info(uni_hid_device_t* d,
|
||||
+ uint8_t* type, uint8_t* firmware_hi,
|
||||
+ uint8_t* firmware_lo) {
|
||||
+ if (!d || d->report_parser.setup != uni_hid_parser_switch_setup)
|
||||
+ return false;
|
||||
+ switch_instance_t* ins = get_switch_instance(d);
|
||||
+ if (ins->state != STATE_READY || !ins->device_info_valid)
|
||||
+ return false;
|
||||
+ if (type)
|
||||
+ *type = ins->controller_type;
|
||||
+ if (firmware_hi)
|
||||
+ *firmware_hi = ins->firmware_version_hi;
|
||||
+ if (firmware_lo)
|
||||
+ *firmware_lo = ins->firmware_version_lo;
|
||||
+ return true;
|
||||
+}
|
||||
+
|
||||
+bool uni_hid_parser_switch_native_acquire(uni_hid_device_t* d) {
|
||||
+ uint8_t type;
|
||||
+ if (!uni_hid_parser_switch_native_info(d, &type, NULL, NULL) ||
|
||||
+ type < SWITCH_CONTROLLER_TYPE_JCL || type > SWITCH_CONTROLLER_TYPE_PRO ||
|
||||
+ !d->conn.connected || !d->conn.interrupt_cid)
|
||||
+ return false;
|
||||
+ switch_instance_t* ins = get_switch_instance(d);
|
||||
+ switch_cancel_rumble_timers(ins);
|
||||
+ switch_discard_queued_rumble(d);
|
||||
+ ins->native_owned = true;
|
||||
+ return true;
|
||||
+}
|
||||
+
|
||||
+bool uni_hid_parser_switch_native_send(uni_hid_device_t* d, const uint8_t rumble[8]) {
|
||||
+ if (!d || !rumble || d->report_parser.setup != uni_hid_parser_switch_setup)
|
||||
+ return false;
|
||||
+ switch_instance_t* ins = get_switch_instance(d);
|
||||
+ if (!ins->native_owned || ins->state != STATE_READY || !d->conn.connected ||
|
||||
+ !d->conn.interrupt_cid || !l2cap_can_send_packet_now(d->conn.interrupt_cid))
|
||||
+ return false;
|
||||
+ uint8_t report[11] = {0xa2, OUTPUT_RUMBLE_ONLY, 0};
|
||||
+ memcpy(&report[3], rumble, 8);
|
||||
+ return switch_submit_report(d, d->conn.interrupt_cid, report, sizeof(report)) == ERROR_CODE_SUCCESS;
|
||||
+}
|
||||
+
|
||||
+void uni_hid_parser_switch_native_release(uni_hid_device_t* d) {
|
||||
+ if (!d || d->report_parser.setup != uni_hid_parser_switch_setup)
|
||||
+ return;
|
||||
+ switch_instance_t* ins = get_switch_instance(d);
|
||||
+ if (!ins->native_owned)
|
||||
+ return;
|
||||
+ switch_cancel_rumble_timers(ins);
|
||||
+ switch_discard_queued_rumble(d);
|
||||
+ ins->native_owned = false;
|
||||
+ if (ins->state == STATE_READY && d->conn.connected && d->conn.interrupt_cid &&
|
||||
+ memcmp(ins->effective_rumble, switch_neutral_rumble, sizeof(ins->effective_rumble)) != 0) {
|
||||
+ // A neutral stop may queue under congestion, never an audio command.
|
||||
+ // The owner normally stops first; never queue a redundant delayed stop.
|
||||
+ struct switch_subcmd_request req = {.report_id = OUTPUT_RUMBLE_ONLY};
|
||||
+ memcpy(req.rumble_left, switch_neutral_rumble, 4);
|
||||
+ memcpy(req.rumble_right, &switch_neutral_rumble[4], 4);
|
||||
+ send_subcmd(d, &req, 11);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+void uni_hid_parser_switch_teardown(uni_hid_device_t* d) {
|
||||
+ if (!d || d->report_parser.setup != uni_hid_parser_switch_setup)
|
||||
+ return;
|
||||
+ switch_instance_t* ins = get_switch_instance(d);
|
||||
+ switch_cancel_rumble_timers(ins);
|
||||
+ btstack_run_loop_remove_timer(&ins->setup_timer);
|
||||
+ ins->setup_timer.context = NULL;
|
||||
+ ins->native_owned = false;
|
||||
+ ins->device_info_valid = false;
|
||||
+ ins->state = STATE_UNINIT;
|
||||
+ uni_circular_buffer_reset(&d->outgoing_buffer);
|
||||
}
|
||||
|
||||
static int32_t calibrate_axis(int32_t v, switch_cal_stick_t cal) {
|
||||
@@ -1364,8 +1585,9 @@
|
||||
static void switch_stop_rumble_now(uni_hid_device_t* d) {
|
||||
switch_instance_t* ins = get_switch_instance(d);
|
||||
|
||||
- // No need to protect it with a mutex since it runs in the same main thread
|
||||
- assert(ins->rumble_state == SWITCH_STATE_RUMBLE_IN_PROGRESS);
|
||||
+ if (ins->native_owned || ins->state == STATE_UNINIT)
|
||||
+ return;
|
||||
+ switch_cancel_rumble_timers(ins);
|
||||
ins->rumble_state = SWITCH_STATE_RUMBLE_DISABLED;
|
||||
|
||||
struct switch_subcmd_request req = {0};
|
||||
@@ -1379,6 +1441,24 @@ static void switch_stop_rumble_now(uni_hid_device_t* d) {
|
||||
@@ -1379,11 +1601,31 @@
|
||||
send_subcmd(d, (struct switch_subcmd_request*)&req, sizeof(req) - 1);
|
||||
}
|
||||
|
||||
|
|
@ -503,7 +727,14 @@ index 599fc35..0105c53 100644
|
|||
static void switch_play_dual_rumble_now(uni_hid_device_t* d,
|
||||
uint16_t duration_ms,
|
||||
uint8_t weak_magnitude,
|
||||
@@ -1391,14 +1471,17 @@ static void switch_play_dual_rumble_now(uni_hid_device_t* d,
|
||||
uint8_t strong_magnitude) {
|
||||
switch_instance_t* ins = get_switch_instance(d);
|
||||
+ if (ins->native_owned || ins->state == STATE_UNINIT)
|
||||
+ return;
|
||||
|
||||
if (duration_ms == 0) {
|
||||
if (ins->rumble_state != SWITCH_STATE_RUMBLE_DISABLED)
|
||||
@@ -1391,14 +1633,17 @@
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -512,12 +743,13 @@ index 599fc35..0105c53 100644
|
|||
- };
|
||||
- switch_encode_rumble(req.rumble_left, weak_magnitude << 2, weak_magnitude, 500);
|
||||
- switch_encode_rumble(req.rumble_right, strong_magnitude << 2, strong_magnitude, 500);
|
||||
-
|
||||
- // Rumble request don't include the last byte of "switch_subcmd_request": subcmd_id
|
||||
- send_subcmd(d, &req, sizeof(req) - 1);
|
||||
+ ins->rumble_weak_magnitude = weak_magnitude;
|
||||
+ ins->rumble_strong_magnitude = strong_magnitude;
|
||||
+ switch_send_dual_rumble_now(d, weak_magnitude, strong_magnitude);
|
||||
|
||||
- // Rumble request don't include the last byte of "switch_subcmd_request": subcmd_id
|
||||
- send_subcmd(d, &req, sizeof(req) - 1);
|
||||
+
|
||||
+ // Refresh active rumble for Switch-compatible controllers that do not
|
||||
+ // retain a single output packet, including 8BitDo Switch mode.
|
||||
+ ins->rumble_timer_refresh.process = &on_switch_refresh_rumble;
|
||||
|
|
@ -528,14 +760,28 @@ index 599fc35..0105c53 100644
|
|||
|
||||
// Set timer to turn off rumble
|
||||
ins->rumble_timer_duration.process = &on_switch_set_rumble_off;
|
||||
@@ -1414,6 +1497,20 @@ static void on_switch_set_rumble_on(btstack_timer_source_t* ts) {
|
||||
@@ -1410,19 +1655,52 @@
|
||||
|
||||
static void on_switch_set_rumble_on(btstack_timer_source_t* ts) {
|
||||
uni_hid_device_t* d = ts->context;
|
||||
- switch_instance_t* ins = get_switch_instance(d);
|
||||
+ if (!d || d->report_parser.setup != uni_hid_parser_switch_setup)
|
||||
+ return;
|
||||
+ switch_instance_t* ins = get_switch_instance(d);
|
||||
+ if (ts != &ins->rumble_timer_delayed_start || ins->native_owned ||
|
||||
+ ins->rumble_state != SWITCH_STATE_RUMBLE_DELAYED)
|
||||
+ return;
|
||||
+ ts->context = NULL;
|
||||
|
||||
switch_play_dual_rumble_now(d, ins->rumble_duration_ms, ins->rumble_weak_magnitude, ins->rumble_strong_magnitude);
|
||||
}
|
||||
+static void on_switch_refresh_rumble(btstack_timer_source_t* ts) {
|
||||
+ uni_hid_device_t* d = btstack_run_loop_get_timer_context(ts);
|
||||
+ if (!d || d->report_parser.setup != uni_hid_parser_switch_setup)
|
||||
+ return;
|
||||
+ switch_instance_t* ins = get_switch_instance(d);
|
||||
+ if (ins->rumble_state != SWITCH_STATE_RUMBLE_IN_PROGRESS) {
|
||||
+ if (ts != &ins->rumble_timer_refresh || ins->native_owned ||
|
||||
+ ins->rumble_state != SWITCH_STATE_RUMBLE_IN_PROGRESS) {
|
||||
+ return;
|
||||
+ }
|
||||
+ switch_send_dual_rumble_now(
|
||||
|
|
@ -549,6 +795,26 @@ index 599fc35..0105c53 100644
|
|||
|
||||
static void on_switch_set_rumble_off(btstack_timer_source_t* ts) {
|
||||
uni_hid_device_t* d = btstack_run_loop_get_timer_context(ts);
|
||||
+ if (!d || d->report_parser.setup != uni_hid_parser_switch_setup)
|
||||
+ return;
|
||||
+ switch_instance_t* ins = get_switch_instance(d);
|
||||
+ if (ts != &ins->rumble_timer_duration || ins->native_owned ||
|
||||
+ ins->rumble_state != SWITCH_STATE_RUMBLE_IN_PROGRESS)
|
||||
+ return;
|
||||
switch_stop_rumble_now(d);
|
||||
}
|
||||
|
||||
void switch_setup_timeout_callback(btstack_timer_source_t* ts) {
|
||||
uni_hid_device_t* d = btstack_run_loop_get_timer_context(ts);
|
||||
- switch_instance_t* ins = get_switch_instance(d);
|
||||
+ if (!d || d->report_parser.setup != uni_hid_parser_switch_setup)
|
||||
+ return;
|
||||
+ switch_instance_t* ins = get_switch_instance(d);
|
||||
+ if (ts != &ins->setup_timer || ins->state == STATE_UNINIT)
|
||||
+ return;
|
||||
logi("Switch: setup timer timeout, failed state: 0x%02x\n", ins->state);
|
||||
process_fsm(d);
|
||||
}
|
||||
diff --git a/src/components/bluepad32/parser/uni_hid_parser_wii.c b/src/components/bluepad32/parser/uni_hid_parser_wii.c
|
||||
index be2103e..4819639 100644
|
||||
--- a/src/components/bluepad32/parser/uni_hid_parser_wii.c
|
||||
|
|
@ -573,10 +839,29 @@ index be2103e..4819639 100644
|
|||
// Dpad works as dpad, useful to navigate menus.
|
||||
ctl->gamepad.dpad |= (report[1] & 0x01) ? DPAD_DOWN : 0;
|
||||
diff --git a/src/components/bluepad32/uni_hid_device.c b/src/components/bluepad32/uni_hid_device.c
|
||||
index 67841e8..9fe7134 100644
|
||||
--- a/src/components/bluepad32/uni_hid_device.c
|
||||
+++ b/src/components/bluepad32/uni_hid_device.c
|
||||
@@ -655,6 +655,7 @@ void uni_hid_device_guess_controller_type_from_pid_vid(uni_hid_device_t* d) {
|
||||
@@ -447,6 +447,9 @@
|
||||
return;
|
||||
}
|
||||
|
||||
+ if (d->report_parser.setup == uni_hid_parser_switch_setup)
|
||||
+ uni_hid_parser_switch_teardown(d);
|
||||
+
|
||||
// Disconnect child first
|
||||
if (d->child)
|
||||
uni_hid_device_disconnect(d->child);
|
||||
@@ -490,6 +493,9 @@
|
||||
loge("uni_hid_device_delete: invalid hid device: NULL\n");
|
||||
return;
|
||||
}
|
||||
+ if (d->report_parser.setup == uni_hid_parser_switch_setup)
|
||||
+ uni_hid_parser_switch_teardown(d);
|
||||
+
|
||||
|
||||
// Delete child first
|
||||
if (d->child)
|
||||
@@ -655,6 +661,7 @@
|
||||
d->report_parser.setup = uni_hid_parser_psmove_setup;
|
||||
d->report_parser.init_report = uni_hid_parser_psmove_init_report;
|
||||
d->report_parser.parse_input_report = uni_hid_parser_psmove_parse_input_report;
|
||||
|
|
@ -584,6 +869,19 @@ index 67841e8..9fe7134 100644
|
|||
d->report_parser.set_lightbar_color = uni_hid_parser_psmove_set_lightbar_color;
|
||||
d->report_parser.play_dual_rumble = uni_hid_parser_psmove_play_dual_rumble;
|
||||
logi("Device detected as PS Move: 0x%02x\n", type);
|
||||
@@ -809,7 +816,11 @@
|
||||
return;
|
||||
}
|
||||
|
||||
- int err = l2cap_send(cid, (uint8_t*)report, len);
|
||||
+ int err;
|
||||
+ if (d->report_parser.setup == uni_hid_parser_switch_setup)
|
||||
+ err = uni_hid_parser_switch_send_report(d, cid, report, len);
|
||||
+ else
|
||||
+ err = l2cap_send(cid, (uint8_t*)report, len);
|
||||
if (err != 0) {
|
||||
logd("Could not send report (error=0x%04x). Adding it to queue\n", err);
|
||||
if (uni_circular_buffer_put(&d->outgoing_buffer, cid, report, len) != 0) {
|
||||
diff --git a/src/components/bluepad32/bt/uni_bt.c b/src/components/bluepad32/bt/uni_bt.c
|
||||
--- a/src/components/bluepad32/bt/uni_bt.c
|
||||
+++ b/src/components/bluepad32/bt/uni_bt.c
|
||||
|
|
@ -611,3 +909,29 @@ diff --git a/src/components/bluepad32/bt/uni_bt.c b/src/components/bluepad32/bt/
|
|||
uni_hid_device_send_queued_reports(device);
|
||||
}
|
||||
break;
|
||||
diff --git a/src/components/bluepad32/include/parser/uni_hid_parser_switch.h b/src/components/bluepad32/include/parser/uni_hid_parser_switch.h
|
||||
--- a/src/components/bluepad32/include/parser/uni_hid_parser_switch.h
|
||||
+++ b/src/components/bluepad32/include/parser/uni_hid_parser_switch.h
|
||||
@@ -23,4 +23,22 @@
|
||||
bool uni_hid_parser_switch_does_name_match(struct uni_hid_device_s* d, const char* name);
|
||||
void uni_hid_parser_switch_device_dump(struct uni_hid_device_s* d);
|
||||
|
||||
+// BTstack-thread-only native owner. Qualification belongs to the caller:
|
||||
+// type and firmware are unverified bytes from a successful device-info reply.
|
||||
+bool uni_hid_parser_switch_native_info(struct uni_hid_device_s* d,
|
||||
+ uint8_t* type, uint8_t* firmware_hi,
|
||||
+ uint8_t* firmware_lo);
|
||||
+bool uni_hid_parser_switch_native_acquire(struct uni_hid_device_s* d);
|
||||
+// Retires compatibility timers and requests neutral; no audio is queued.
|
||||
+void uni_hid_parser_switch_native_release(struct uni_hid_device_s* d);
|
||||
+// Exactly 11 bytes on interrupt L2CAP. False means NOT submitted, never queued.
|
||||
+// Caller must retry/resynchronize; true is transport acceptance, not playback.
|
||||
+bool uni_hid_parser_switch_native_send(struct uni_hid_device_s* d, const uint8_t rumble[8]);
|
||||
+
|
||||
+// Internal device-send/lifecycle hooks, including queued subcommand retries.
|
||||
+// Returns the L2CAP status, or success when discarding an obsolete writer.
|
||||
+int uni_hid_parser_switch_send_report(struct uni_hid_device_s* d, uint16_t cid,
|
||||
+ const uint8_t* report, uint16_t len);
|
||||
+void uni_hid_parser_switch_teardown(struct uni_hid_device_s* d);
|
||||
+
|
||||
#endif // UNI_HID_PARSER_SWITCH_H
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue