Assign colors per controller slot
This commit is contained in:
parent
4a73680bd2
commit
23a3cbade5
12 changed files with 330 additions and 51 deletions
|
|
@ -4,6 +4,7 @@
|
|||
#include <string>
|
||||
|
||||
#include <uni.h>
|
||||
#include "../controller_color_config.h"
|
||||
|
||||
namespace {
|
||||
|
||||
|
|
@ -44,6 +45,19 @@ void play_rumble(uni_hid_device_t* device, uint16_t, uint16_t,
|
|||
device->last_high = high;
|
||||
device->last_low = low;
|
||||
}
|
||||
void set_lightbar(uni_hid_device_t* device, uint8_t red, uint8_t green,
|
||||
uint8_t blue) {
|
||||
++device->lightbar_calls;
|
||||
device->lightbar_red = red;
|
||||
device->lightbar_green = green;
|
||||
device->lightbar_blue = blue;
|
||||
}
|
||||
|
||||
void set_player_leds(uni_hid_device_t* device, uint8_t leds) {
|
||||
++device->player_led_calls;
|
||||
device->player_leds = leds;
|
||||
}
|
||||
|
||||
|
||||
uni_hid_device_t device(
|
||||
int idx, bool gamepad = true,
|
||||
|
|
@ -59,6 +73,7 @@ uni_hid_device_t device(
|
|||
|
||||
} // namespace
|
||||
|
||||
|
||||
bool uni_hid_device_is_gamepad(const uni_hid_device_t* device) {
|
||||
return device != nullptr && device->gamepad;
|
||||
}
|
||||
|
|
@ -149,6 +164,21 @@ uint32_t btstack_run_loop_get_time_ms() {
|
|||
|
||||
|
||||
#include "../bluepad32_input_backend.cpp"
|
||||
SwitchRgbColor switch_pro_get_slot_light_color(uint8_t instance) {
|
||||
static constexpr SwitchRgbColor grips[] = {
|
||||
{SWITCH_COLOR_SLOT_1_R, SWITCH_COLOR_SLOT_1_G,
|
||||
SWITCH_COLOR_SLOT_1_B},
|
||||
{SWITCH_COLOR_SLOT_2_R, SWITCH_COLOR_SLOT_2_G,
|
||||
SWITCH_COLOR_SLOT_2_B},
|
||||
{SWITCH_COLOR_SLOT_3_R, SWITCH_COLOR_SLOT_3_G,
|
||||
SWITCH_COLOR_SLOT_3_B},
|
||||
{SWITCH_COLOR_SLOT_4_R, SWITCH_COLOR_SLOT_4_G,
|
||||
SWITCH_COLOR_SLOT_4_B},
|
||||
};
|
||||
return instance < sizeof(grips) / sizeof(grips[0])
|
||||
? switch_pro_calibrate_light_color(grips[instance])
|
||||
: SwitchRgbColor{};
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
|
|
@ -639,6 +669,39 @@ void test_pairing_window_policy() {
|
|||
}
|
||||
|
||||
|
||||
void test_slot_lighting() {
|
||||
start_pairing_backend();
|
||||
uni_hid_device_t devices[kSlotCount] = {
|
||||
device(0), device(1), device(2), device(3)};
|
||||
|
||||
for (uint8_t slot = 0; slot < kSlotCount; ++slot) {
|
||||
devices[slot].report_parser.set_lightbar_color = set_lightbar;
|
||||
devices[slot].report_parser.set_player_leds = set_player_leds;
|
||||
require(platform_on_device_ready(&devices[slot]) == UNI_ERROR_SUCCESS,
|
||||
"color-capable controller did not become ready");
|
||||
const SwitchRgbColor expected =
|
||||
switch_pro_get_slot_light_color(slot);
|
||||
require(devices[slot].lightbar_calls == 1 &&
|
||||
devices[slot].lightbar_red == expected.red &&
|
||||
devices[slot].lightbar_green == expected.green &&
|
||||
devices[slot].lightbar_blue == expected.blue &&
|
||||
devices[slot].player_led_calls == 0,
|
||||
"slot color did not reach the controller lightbar");
|
||||
require(platform_on_device_ready(&devices[slot]) == UNI_ERROR_SUCCESS &&
|
||||
devices[slot].lightbar_calls == 1,
|
||||
"duplicate ready event rewrote controller lighting");
|
||||
}
|
||||
|
||||
platform_on_device_disconnected(&devices[2]);
|
||||
uni_hid_device_t fallback = device(2);
|
||||
fallback.report_parser.set_player_leds = set_player_leds;
|
||||
require(platform_on_device_ready(&fallback) == UNI_ERROR_SUCCESS &&
|
||||
fallback.lightbar_calls == 0 &&
|
||||
fallback.player_led_calls == 1 &&
|
||||
fallback.player_leds == (1u << 2u),
|
||||
"controller without RGB support did not receive its slot LED");
|
||||
}
|
||||
|
||||
void test_flash_core_start_contract() {
|
||||
bluepad32_input_backend_init();
|
||||
flash_core_init_result = false;
|
||||
|
|
@ -688,6 +751,8 @@ int main(int argc, char** argv) {
|
|||
test_independent_lifecycle();
|
||||
} else if (scenario == "pairing-policy") {
|
||||
test_pairing_window_policy();
|
||||
} else if (scenario == "slot-lighting") {
|
||||
test_slot_lighting();
|
||||
} else if (scenario == "flash-core-start") {
|
||||
test_flash_core_start_contract();
|
||||
} else if (scenario == "flash-core-failure") {
|
||||
|
|
|
|||
|
|
@ -82,8 +82,13 @@ struct uni_controller_t {
|
|||
struct uni_hid_device_t;
|
||||
typedef void (*uni_play_dual_rumble_t)(uni_hid_device_t*, uint16_t,
|
||||
uint16_t, uint8_t, uint8_t);
|
||||
typedef void (*uni_set_player_leds_t)(uni_hid_device_t*, uint8_t);
|
||||
typedef void (*uni_set_lightbar_color_t)(uni_hid_device_t*, uint8_t, uint8_t,
|
||||
uint8_t);
|
||||
|
||||
struct uni_report_parser_t {
|
||||
uni_set_player_leds_t set_player_leds;
|
||||
uni_set_lightbar_color_t set_lightbar_color;
|
||||
uni_play_dual_rumble_t play_dual_rumble;
|
||||
};
|
||||
|
||||
|
|
@ -107,6 +112,12 @@ struct uni_hid_device_t {
|
|||
int rumble_calls;
|
||||
uint8_t last_high;
|
||||
uint8_t last_low;
|
||||
int lightbar_calls;
|
||||
uint8_t lightbar_red;
|
||||
uint8_t lightbar_green;
|
||||
uint8_t lightbar_blue;
|
||||
int player_led_calls;
|
||||
uint8_t player_leds;
|
||||
};
|
||||
|
||||
struct uni_platform {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
#include "switch_pro_driver.h"
|
||||
#include "controller_color_config.h"
|
||||
#include "tusb.h"
|
||||
#include "pico/time.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
|
@ -139,6 +141,18 @@ void send_feature(uint8_t instance, uint8_t command, uint8_t value) {
|
|||
report[11] = value;
|
||||
tud_hid_report_received_cb(instance, 0, report.data(), report.size());
|
||||
}
|
||||
void send_spi_read(uint8_t instance, uint32_t address, uint8_t size) {
|
||||
std::array<uint8_t, SWITCH_PRO_ENDPOINT_SIZE> report{};
|
||||
report[0] = REPORT_FEATURE;
|
||||
report[10] = SPI_READ;
|
||||
report[11] = static_cast<uint8_t>(address);
|
||||
report[12] = static_cast<uint8_t>(address >> 8u);
|
||||
report[13] = static_cast<uint8_t>(address >> 16u);
|
||||
report[14] = static_cast<uint8_t>(address >> 24u);
|
||||
report[15] = size;
|
||||
tud_hid_report_received_cb(instance, 0, report.data(), report.size());
|
||||
}
|
||||
|
||||
|
||||
void send_config(uint8_t instance, uint8_t subtype) {
|
||||
const uint8_t report[] = {REPORT_CONFIGURATION, subtype};
|
||||
|
|
@ -406,6 +420,64 @@ void test_callback_send_and_imu_modes_are_isolated() {
|
|||
"instance 1 quaternion accelerometer state was overwritten");
|
||||
}
|
||||
|
||||
void test_grip_colors_are_isolated() {
|
||||
initialize_contexts();
|
||||
constexpr uint32_t grip_address =
|
||||
0x6000u + offsetof(SwitchFactoryConfig, leftGripColor);
|
||||
constexpr uint8_t grip_bytes =
|
||||
sizeof(SwitchColorDefinition) * 2u;
|
||||
constexpr SwitchRgbColor calibrated_blue =
|
||||
switch_pro_calibrate_light_color({0x00, 0x89, 0xEB});
|
||||
constexpr SwitchRgbColor calibrated_gray =
|
||||
switch_pro_calibrate_light_color({0x96, 0x96, 0x96});
|
||||
static_assert(calibrated_blue.red == 0x00 &&
|
||||
calibrated_blue.green == 0x35 &&
|
||||
calibrated_blue.blue == 0x9D);
|
||||
static_assert(calibrated_gray.red == 0x64 &&
|
||||
calibrated_gray.green == 0x64 &&
|
||||
calibrated_gray.blue == 0x64);
|
||||
|
||||
|
||||
for (uint8_t instance = 0; instance < kInstanceCount; ++instance) {
|
||||
send_spi_read(instance, grip_address, grip_bytes);
|
||||
now_ms += 6;
|
||||
expect(!switch_pro_task(instance),
|
||||
"grip color SPI reply counted as regular input");
|
||||
expect(sent_report_count == static_cast<unsigned>(instance + 1u),
|
||||
"grip color SPI reply was not sent");
|
||||
const SentReport& response = sent_reports[sent_report_count - 1u];
|
||||
const SwitchRgbColor expected =
|
||||
switch_pro_get_slot_color(instance);
|
||||
const uint8_t expected_bytes[] = {
|
||||
expected.red, expected.green, expected.blue,
|
||||
expected.red, expected.green, expected.blue,
|
||||
};
|
||||
expect(response.instance == instance &&
|
||||
response.data[13] == 0x90 &&
|
||||
response.data[14] == SPI_READ &&
|
||||
std::memcmp(response.data.data() + 20, expected_bytes,
|
||||
sizeof(expected_bytes)) == 0,
|
||||
"Switch grip color did not match its HID slot");
|
||||
const SwitchRgbColor light =
|
||||
switch_pro_get_slot_light_color(instance);
|
||||
const SwitchRgbColor calibrated =
|
||||
switch_pro_calibrate_light_color(expected);
|
||||
expect(light.red == calibrated.red &&
|
||||
light.green == calibrated.green &&
|
||||
light.blue == calibrated.blue,
|
||||
"physical controller light was not derived from its grip");
|
||||
}
|
||||
|
||||
const SwitchRgbColor invalid_grip =
|
||||
switch_pro_get_slot_color(kInvalidInstance);
|
||||
const SwitchRgbColor invalid_light =
|
||||
switch_pro_get_slot_light_color(kInvalidInstance);
|
||||
expect(invalid_grip.red == 0 && invalid_grip.green == 0 &&
|
||||
invalid_grip.blue == 0 && invalid_light.red == 0 &&
|
||||
invalid_light.green == 0 && invalid_light.blue == 0,
|
||||
"invalid HID slot returned a configured color");
|
||||
}
|
||||
|
||||
void test_rumble_callbacks_and_decoders_are_isolated() {
|
||||
initialize_contexts();
|
||||
rumble_events = {};
|
||||
|
|
@ -613,6 +685,7 @@ int main() {
|
|||
test_input_reports_and_timers_are_isolated();
|
||||
test_callback_send_and_imu_modes_are_isolated();
|
||||
test_rumble_callbacks_and_decoders_are_isolated();
|
||||
test_grip_colors_are_isolated();
|
||||
test_lifecycle_and_invalid_instances();
|
||||
test_uart_parser_is_pure();
|
||||
if (failures != 0) {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ def test_bluepad32_backend_lifecycle_native(tmp_path: Path) -> None:
|
|||
"rejections",
|
||||
"lifecycle",
|
||||
"pairing-policy",
|
||||
"slot-lighting",
|
||||
"flash-core-start",
|
||||
"flash-core-failure",
|
||||
):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue