Support Bluepad32 gamepads as native Joy-Con pair sources
This commit is contained in:
parent
ab70536fdf
commit
a17ca603aa
33 changed files with 2529 additions and 851 deletions
|
|
@ -209,6 +209,15 @@ static inline bool uni_psmove_normalize_imu(
|
|||
|
||||
const int32_t gyro_bias_value = uni_psmove_read_calibration_value(
|
||||
calibration->data, model, gyro_bias[axis]);
|
||||
#if SWITCH2_BRIDGE_FULL_INPUT
|
||||
// Complete feature blocks alone do not make erased/degenerate factory
|
||||
// extrema calibrated. Never label the helper's zero-span fallback IMU.
|
||||
if (accel_low_value >= accel_high_value ||
|
||||
uni_psmove_read_calibration_value(calibration->data, model, gyro_high[axis]) <= gyro_bias_value ||
|
||||
(model == UNI_PSMOVE_IMU_MODEL_ZCM2 &&
|
||||
uni_psmove_read_calibration_value(calibration->data, model, zcm2_gyro_low[axis]) >= gyro_bias_value))
|
||||
return false;
|
||||
#endif
|
||||
const int32_t gyro_raw =
|
||||
(uni_psmove_decode_value(model, gyro_first[axis]) +
|
||||
uni_psmove_decode_value(model, gyro_second[axis])) /
|
||||
|
|
|
|||
131
bluepad32_config/parser/uni_hid_parser_native_motion.c
Normal file
131
bluepad32_config/parser/uni_hid_parser_native_motion.c
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include "parser/uni_hid_parser_native_motion.h"
|
||||
|
||||
#if SWITCH2_BRIDGE_FULL_INPUT
|
||||
#include <string.h>
|
||||
|
||||
#include "parser/uni_hid_parser_wii.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "uni_hid_device.h"
|
||||
|
||||
// Parser data is already close to its fixed capacity on some families. Keep
|
||||
// only native-bridge provenance here, not in every normal AIO parser instance.
|
||||
typedef struct {
|
||||
uni_hid_device_t* device;
|
||||
void (*setup)(uni_hid_device_t*);
|
||||
uint8_t address[6];
|
||||
uni_native_motion_snapshot_t sample;
|
||||
uint32_t timestamp;
|
||||
bool have_timestamp;
|
||||
} native_motion_t;
|
||||
static native_motion_t providers[CONFIG_BLUEPAD32_MAX_DEVICES];
|
||||
static uint32_t sequence;
|
||||
|
||||
uint32_t uni_hid_parser_native_motion_next_sequence(void) {
|
||||
if (++sequence == 0)
|
||||
++sequence;
|
||||
return sequence;
|
||||
}
|
||||
|
||||
static native_motion_t* provider(uni_hid_device_t* d) {
|
||||
if (!d)
|
||||
return NULL;
|
||||
for (unsigned i = 0; i < CONFIG_BLUEPAD32_MAX_DEVICES; ++i) {
|
||||
native_motion_t* p = &providers[i];
|
||||
if (p->device == d && p->setup == d->report_parser.setup &&
|
||||
memcmp(p->address, d->conn.btaddr, sizeof(p->address)) == 0)
|
||||
return p;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void uni_hid_parser_native_motion_forget(uni_hid_device_t* d) {
|
||||
for (unsigned i = 0; i < CONFIG_BLUEPAD32_MAX_DEVICES; ++i)
|
||||
if (providers[i].device == d)
|
||||
memset(&providers[i], 0, sizeof(providers[i]));
|
||||
}
|
||||
|
||||
void uni_hid_parser_native_motion_reset(uni_hid_device_t* d) {
|
||||
if (!d)
|
||||
return;
|
||||
uni_hid_parser_native_motion_forget(d);
|
||||
for (unsigned i = 0; i < CONFIG_BLUEPAD32_MAX_DEVICES; ++i) {
|
||||
native_motion_t* p = &providers[i];
|
||||
if (p->device)
|
||||
continue;
|
||||
p->device = d;
|
||||
p->setup = d->report_parser.setup;
|
||||
memcpy(p->address, d->conn.btaddr, sizeof(p->address));
|
||||
p->sample.report_tracked = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void uni_hid_parser_native_motion_begin(uni_hid_device_t* d) {
|
||||
native_motion_t* p = provider(d);
|
||||
if (p)
|
||||
p->sample.report_valid = false;
|
||||
}
|
||||
|
||||
void uni_hid_parser_native_motion_accept(uni_hid_device_t* d) {
|
||||
native_motion_t* p = provider(d);
|
||||
if (p) {
|
||||
p->sample.report_valid = true;
|
||||
p->sample.report_sequence = uni_hid_parser_native_motion_next_sequence();
|
||||
}
|
||||
}
|
||||
|
||||
void uni_hid_parser_native_motion_accel(uni_hid_device_t* d, const int32_t* value) {
|
||||
native_motion_t* p = provider(d);
|
||||
if (!p)
|
||||
return;
|
||||
p->sample.accel_valid = value != NULL;
|
||||
if (value) {
|
||||
memcpy(p->sample.accel_q13, value, sizeof(p->sample.accel_q13));
|
||||
p->sample.accel_sequence = uni_hid_parser_native_motion_next_sequence();
|
||||
}
|
||||
}
|
||||
|
||||
void uni_hid_parser_native_motion_gyro(uni_hid_device_t* d, const int32_t* value) {
|
||||
native_motion_t* p = provider(d);
|
||||
if (!p)
|
||||
return;
|
||||
p->sample.gyro_valid = value != NULL;
|
||||
if (value) {
|
||||
memcpy(p->sample.gyro_q10, value, sizeof(p->sample.gyro_q10));
|
||||
p->sample.gyro_sequence = uni_hid_parser_native_motion_next_sequence();
|
||||
}
|
||||
}
|
||||
|
||||
bool uni_hid_parser_native_motion_fresh(uni_hid_device_t* d, uint32_t timestamp, uint32_t mask) {
|
||||
native_motion_t* p = provider(d);
|
||||
if (!p)
|
||||
return false;
|
||||
uint32_t delta = (timestamp - p->timestamp) & mask;
|
||||
if (p->have_timestamp && (delta == 0 || delta > (mask >> 1)))
|
||||
return false;
|
||||
p->timestamp = timestamp;
|
||||
p->have_timestamp = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool uni_hid_parser_native_motion_snapshot(uni_hid_device_t* d, uni_native_motion_snapshot_t* out) {
|
||||
if (!out)
|
||||
return false;
|
||||
memset(out, 0, sizeof(*out));
|
||||
if (!d)
|
||||
return false;
|
||||
// Wii already owns independent, topology-aware calibration and sample IDs.
|
||||
// ACK/status/extension packets must not refresh the Remote or MotionPlus.
|
||||
if (d->report_parser.setup == uni_hid_parser_wii_setup) {
|
||||
out->accel_valid = uni_hid_parser_wii_accel_snapshot(d, out->accel_q13, &out->accel_sequence);
|
||||
out->gyro_valid = uni_hid_parser_wii_gyro_snapshot(d, out->gyro_q10, &out->gyro_sequence);
|
||||
return true;
|
||||
}
|
||||
native_motion_t* p = provider(d);
|
||||
if (!p)
|
||||
return false;
|
||||
*out = p->sample;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
48
bluepad32_config/parser/uni_hid_parser_native_motion.h
Normal file
48
bluepad32_config/parser/uni_hid_parser_native_motion.h
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct uni_hid_device_s;
|
||||
typedef struct {
|
||||
bool report_tracked;
|
||||
bool report_valid;
|
||||
uint32_t report_sequence;
|
||||
bool accel_valid;
|
||||
bool gyro_valid;
|
||||
uint32_t accel_sequence;
|
||||
uint32_t gyro_sequence;
|
||||
int32_t accel_q13[3];
|
||||
int32_t gyro_q10[3];
|
||||
} uni_native_motion_snapshot_t;
|
||||
|
||||
#if SWITCH2_BRIDGE_FULL_INPUT
|
||||
// Bluetooth owner only. Reads never advance sequences or rejuvenate samples.
|
||||
// False/zero means no motion provider, not an unsupported controls device.
|
||||
bool uni_hid_parser_native_motion_snapshot(struct uni_hid_device_s* d,
|
||||
uni_native_motion_snapshot_t* out);
|
||||
|
||||
// Parser ingress/lifecycle hooks. Only setup allocates a bounded slot; late
|
||||
// reports cannot resurrect a retired provider. NULL sensor data invalidates
|
||||
// that sensor without changing its last sequence. Samples use SDL Q13/Q10 axes.
|
||||
void uni_hid_parser_native_motion_reset(struct uni_hid_device_s* d);
|
||||
void uni_hid_parser_native_motion_forget(struct uni_hid_device_s* d);
|
||||
void uni_hid_parser_native_motion_begin(struct uni_hid_device_s* d);
|
||||
void uni_hid_parser_native_motion_accept(struct uni_hid_device_s* d);
|
||||
void uni_hid_parser_native_motion_accel(struct uni_hid_device_s* d, const int32_t* value);
|
||||
void uni_hid_parser_native_motion_gyro(struct uni_hid_device_s* d, const int32_t* value);
|
||||
// Test a complete report's wrapping hardware clock, before publishing sensors.
|
||||
// mask is UINT8_MAX/UINT16_MAX/UINT32_MAX; backwards/duplicate ticks are rejected.
|
||||
bool uni_hid_parser_native_motion_fresh(struct uni_hid_device_s* d, uint32_t timestamp, uint32_t mask);
|
||||
// Parser ingress only: process-wide nonzero IDs do not alias across reconnects.
|
||||
uint32_t uni_hid_parser_native_motion_next_sequence(void);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
@ -6,6 +6,9 @@
|
|||
// Sensor conversion independently adapted from SDL_hidapi_switch2.c (SDL/Valve).
|
||||
|
||||
#include "parser/uni_hid_parser_switch2.h"
|
||||
#if SWITCH2_BRIDGE_FULL_INPUT
|
||||
#include "parser/uni_hid_parser_native_motion.h"
|
||||
#endif
|
||||
|
||||
#include <math.h>
|
||||
#include <stdatomic.h>
|
||||
|
|
@ -125,6 +128,9 @@ typedef struct {
|
|||
uint8_t memory_length;
|
||||
sw2_stick_t sticks[2];
|
||||
int32_t gyro_bias[3];
|
||||
#if SWITCH2_BRIDGE_FULL_INPUT
|
||||
bool gyro_calibrated;
|
||||
#endif
|
||||
uint8_t extra_buttons, leds;
|
||||
bool leds_pending;
|
||||
uint8_t rumble_id, weak, strong;
|
||||
|
|
@ -216,6 +222,9 @@ static void sw2_disarm_timeout(sw2_instance_t* ins) {
|
|||
}
|
||||
|
||||
void uni_hid_parser_switch2_teardown(uni_hid_device_t* d) {
|
||||
#if SWITCH2_BRIDGE_FULL_INPUT
|
||||
uni_hid_parser_native_motion_forget(d);
|
||||
#endif
|
||||
sw2_instance_t* ins = sw2_instance(d);
|
||||
if (!ins)
|
||||
return;
|
||||
|
|
@ -643,13 +652,21 @@ static void sw2_response(sw2_instance_t* ins, const uint8_t* data, uint16_t leng
|
|||
ins->calibration_done = true;
|
||||
}
|
||||
} else if (ins->state == SW2_GYRO_CALIBRATION) {
|
||||
#if SWITCH2_BRIDGE_FULL_INPUT
|
||||
ins->gyro_calibrated = true;
|
||||
#endif
|
||||
for (unsigned i = 0; i < 3; ++i) {
|
||||
uint32_t bits = little_endian_read_32(value, 4 * i);
|
||||
float bias;
|
||||
memcpy(&bias, &bits, sizeof(bias));
|
||||
// Erased/invalid flash is not a floating-point sensor value.
|
||||
if (isfinite(bias) && bias >= -40.0f && bias <= 40.0f)
|
||||
bool valid = isfinite(bias) && bias >= -40.0f && bias <= 40.0f;
|
||||
if (valid)
|
||||
ins->gyro_bias[i] = (int32_t)(bias * (57.295779513f * UNI_IMU_GYRO_RES_PER_DEG_S));
|
||||
#if SWITCH2_BRIDGE_FULL_INPUT
|
||||
if (!valid)
|
||||
ins->gyro_calibrated = false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
} else if (ins->state == SW2_PAIR) {
|
||||
|
|
@ -1029,6 +1046,9 @@ void uni_hid_parser_switch2_setup(uni_hid_device_t* d) {
|
|||
if (!ins || ins->state != SW2_ADMITTED)
|
||||
return;
|
||||
ins->handle = d->conn.handle;
|
||||
#if SWITCH2_BRIDGE_FULL_INPUT
|
||||
uni_hid_parser_native_motion_reset(d);
|
||||
#endif
|
||||
// Standard-compliant 7.5ms minimum; negotiation failure is not setup failure.
|
||||
int status = gap_update_connection_parameters(ins->handle, 6, 6, 0, 600);
|
||||
if (status != ERROR_CODE_SUCCESS)
|
||||
|
|
@ -1053,6 +1073,9 @@ bool uni_hid_parser_switch2_identity_address_type(const uni_hid_device_t* d, uin
|
|||
}
|
||||
|
||||
void uni_hid_parser_switch2_init_report(uni_hid_device_t* d) {
|
||||
#if SWITCH2_BRIDGE_FULL_INPUT
|
||||
uni_hid_parser_native_motion_begin(d);
|
||||
#endif
|
||||
(void)d; // Full snapshots replace state only after their complete length is validated.
|
||||
}
|
||||
|
||||
|
|
@ -1066,6 +1089,16 @@ static int32_t sw2_axis(uint16_t raw, const sw2_stick_t* stick, unsigned axis, b
|
|||
|
||||
static void sw2_motion(sw2_instance_t* ins, uni_gamepad_t* gp, const uint8_t* report) {
|
||||
uint32_t timestamp = little_endian_read_32(report, 42);
|
||||
#if SWITCH2_BRIDGE_FULL_INPUT
|
||||
if (!timestamp || !uni_hid_parser_native_motion_fresh(ins->device, timestamp, UINT32_MAX))
|
||||
return;
|
||||
// Acceleration has a known fixed range and does not need gyro clock
|
||||
// detection or factory gyro bias. Keep its provenance independent.
|
||||
gp->accel[0] = uni_imu_scale((int16_t)little_endian_read_16(report, 48), 32767, 8 * UNI_IMU_ACCEL_RES_PER_G);
|
||||
gp->accel[1] = uni_imu_scale((int16_t)little_endian_read_16(report, 52), 32767, 8 * UNI_IMU_ACCEL_RES_PER_G);
|
||||
gp->accel[2] = -uni_imu_scale((int16_t)little_endian_read_16(report, 50), 32767, 8 * UNI_IMU_ACCEL_RES_PER_G);
|
||||
uni_hid_parser_native_motion_accel(ins->device, gp->accel);
|
||||
#endif
|
||||
if (!timestamp || (timestamp == ins->sensor_last && !ins->gyro_full_scale))
|
||||
return;
|
||||
ins->sensor_last = timestamp;
|
||||
|
|
@ -1099,18 +1132,30 @@ static void sw2_motion(sw2_instance_t* ins, uni_gamepad_t* gp, const uint8_t* re
|
|||
static const uint8_t axes[3] = {0, 2, 1};
|
||||
for (unsigned i = 0; i < 3; ++i) {
|
||||
unsigned axis = axes[i];
|
||||
#if !SWITCH2_BRIDGE_FULL_INPUT
|
||||
int32_t accel = (int16_t)little_endian_read_16(report, 48 + 2 * axis);
|
||||
#endif
|
||||
int32_t gyro = (int16_t)little_endian_read_16(report, 54 + 2 * axis);
|
||||
#if !SWITCH2_BRIDGE_FULL_INPUT
|
||||
gp->accel[i] = uni_imu_scale(accel, 32767, 8 * UNI_IMU_ACCEL_RES_PER_G);
|
||||
#endif
|
||||
gp->gyro[i] = uni_imu_scale(gyro, 32767, ins->gyro_full_scale) - ins->gyro_bias[axis];
|
||||
if (i == 2) {
|
||||
#if !SWITCH2_BRIDGE_FULL_INPUT
|
||||
gp->accel[i] = -gp->accel[i];
|
||||
#endif
|
||||
gp->gyro[i] = -gp->gyro[i];
|
||||
}
|
||||
}
|
||||
#if SWITCH2_BRIDGE_FULL_INPUT
|
||||
uni_hid_parser_native_motion_gyro(ins->device, ins->gyro_calibrated ? gp->gyro : NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
void uni_hid_parser_switch2_parse_input_report(uni_hid_device_t* d, const uint8_t* report, uint16_t len) {
|
||||
#if SWITCH2_BRIDGE_FULL_INPUT
|
||||
uni_hid_parser_native_motion_begin(d);
|
||||
#endif
|
||||
sw2_instance_t* ins = sw2_instance(d);
|
||||
if (!ins || ins->state != SW2_READY || !report || len != SW2_REPORT_SIZE)
|
||||
return;
|
||||
|
|
@ -1148,6 +1193,9 @@ void uni_hid_parser_switch2_parse_input_report(uni_hid_device_t* d, const uint8_
|
|||
}
|
||||
sw2_motion(ins, gp, report);
|
||||
d->controller.klass = UNI_CONTROLLER_CLASS_GAMEPAD;
|
||||
#if SWITCH2_BRIDGE_FULL_INPUT
|
||||
uni_hid_parser_native_motion_accept(d);
|
||||
#endif
|
||||
}
|
||||
|
||||
void uni_hid_parser_switch2_set_player_leds(uni_hid_device_t* d, uint8_t leds) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue