Add Profile Studio Set A enhancements
This commit is contained in:
parent
890f47ab80
commit
8818f1f6e9
22 changed files with 1745 additions and 118 deletions
|
|
@ -767,6 +767,19 @@ void test_rejections() {
|
|||
"valid device must occupy its indexed slot");
|
||||
require(platform_on_device_ready(&collision) == UNI_ERROR_NO_SLOTS,
|
||||
"different device cannot replace an occupied slot");
|
||||
g_slots[0].identity.stable = true;
|
||||
g_slots[0].identity.transport = ControllerTransport::kClassic;
|
||||
g_slots[0].identity.address[5] = 1;
|
||||
Bluepad32SlotSnapshot identity_snapshot{};
|
||||
bluepad32_input_backend_snapshot(0, &identity_snapshot);
|
||||
require(bluepad32_input_backend_identify(
|
||||
identity_snapshot.identity) &&
|
||||
g_slots[0].pending_profile_feedback_count == 1 &&
|
||||
!bluepad32_input_backend_identify(
|
||||
controller_identity_global()),
|
||||
"Identify did not target only the selected live controller");
|
||||
g_slots[0].pending_profile_feedback_count = 0;
|
||||
g_slots[0].pending_profile_feedback[0] = {};
|
||||
|
||||
uni_controller_t collision_data{};
|
||||
collision_data.klass = UNI_CONTROLLER_CLASS_GAMEPAD;
|
||||
|
|
@ -785,12 +798,15 @@ void test_rejections() {
|
|||
require(read_controller_state(0, &snapshot) &&
|
||||
snapshot.motion_sample_count == 3,
|
||||
"valid slot input must remain observable");
|
||||
slot_zero.controller.battery = 201;
|
||||
g_last_snapshot_generation[0] = 0;
|
||||
Bluepad32PlaytestSnapshot playtest{};
|
||||
bluepad32_input_backend_playtest_snapshot(0, &playtest);
|
||||
require(playtest.active && playtest.state_generation != 0 &&
|
||||
playtest.state.motion_sample_count == 3,
|
||||
"playtest snapshot did not expose current raw input");
|
||||
playtest.state.motion_sample_count == 3 &&
|
||||
playtest.battery == 201 &&
|
||||
(playtest.capabilities & 0x08u) != 0,
|
||||
"playtest snapshot did not expose input capabilities");
|
||||
bluepad32_input_backend_report_sent(0);
|
||||
require(read_controller_state(0, &snapshot) &&
|
||||
snapshot.motion_sample_count == 3,
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ struct uni_gamepad_t {
|
|||
struct uni_controller_t {
|
||||
int klass;
|
||||
uni_gamepad_t gamepad;
|
||||
uint8_t battery;
|
||||
};
|
||||
|
||||
struct uni_hid_device_t;
|
||||
|
|
@ -138,6 +139,7 @@ struct uni_hid_device_t {
|
|||
uni_bt_conn_t conn;
|
||||
int idx;
|
||||
bool gamepad;
|
||||
uni_controller_t controller;
|
||||
uni_report_parser_t report_parser;
|
||||
int rumble_calls;
|
||||
uint8_t last_high;
|
||||
|
|
|
|||
|
|
@ -188,14 +188,46 @@ void test_eight_profile_transactions_and_active_cache() {
|
|||
active.profile.strong_rumble_scale == UINT8_MAX,
|
||||
"reset-all did not restore defaults and activation");
|
||||
|
||||
require(profile_service_set_metadata(
|
||||
5, connected, CONTROLLER_PROFILE_ALL,
|
||||
"Desk pad", 8) ==
|
||||
ConfigurationTransactionStatus::kPending,
|
||||
"controller alias was not queued");
|
||||
profile_service_task_on_storage_core(6000);
|
||||
require(transaction_snapshot().transaction.status ==
|
||||
ConfigurationTransactionStatus::kCommitted,
|
||||
"controller alias did not persist");
|
||||
require(profile_service_set_metadata(
|
||||
6, connected, 6, "Desktop", 7) ==
|
||||
ConfigurationTransactionStatus::kPending,
|
||||
"profile name was not queued");
|
||||
profile_service_task_on_storage_core(7000);
|
||||
require(profile_service_select(connected, 6) ==
|
||||
ConfigurationTransactionStatus::kCommitted,
|
||||
"named profile was not selected");
|
||||
ProfileServiceMetadataSnapshot metadata{};
|
||||
profile_service_metadata_snapshot(&metadata);
|
||||
require(metadata.valid && strcmp(metadata.alias, "Desk pad") == 0 &&
|
||||
strcmp(metadata.profile_names[6], "Desktop") == 0,
|
||||
"profile metadata snapshot lost persisted values");
|
||||
|
||||
ProfileStorage reloaded;
|
||||
ControllerProfile persisted{};
|
||||
char stored_metadata[PROFILE_STORAGE_METADATA_PAYLOAD_SIZE]{};
|
||||
require(
|
||||
reloaded.initialize(fake_io()) && reloaded.find(connected) != nullptr &&
|
||||
reloaded.find(connected)->active_profile == 6 &&
|
||||
reloaded.get(connected, 6, &persisted) == ProfileStorageResult::kOk &&
|
||||
persisted.weak_rumble_scale == 61,
|
||||
"service mutations did not survive catalog reload");
|
||||
persisted.weak_rumble_scale == 61 &&
|
||||
reloaded.get_alias(connected, stored_metadata,
|
||||
sizeof(stored_metadata)) ==
|
||||
ProfileStorageResult::kOk &&
|
||||
strcmp(stored_metadata, "Desk pad") == 0 &&
|
||||
reloaded.get_profile_name(connected, 6, stored_metadata,
|
||||
sizeof(stored_metadata)) ==
|
||||
ProfileStorageResult::kOk &&
|
||||
strcmp(stored_metadata, "Desktop") == 0,
|
||||
"service mutations and metadata did not survive catalog reload");
|
||||
}
|
||||
|
||||
void test_profile_bounds_and_transaction_namespace() {
|
||||
|
|
|
|||
|
|
@ -249,12 +249,55 @@ void test_interrupted_and_corrupt_append_recovery() {
|
|||
"corrupt newest record displaced the previous record");
|
||||
}
|
||||
|
||||
void test_profile_names_and_aliases_recover() {
|
||||
erase_all();
|
||||
const ControllerIdentity stable = identity(7);
|
||||
ProfileStorage storage;
|
||||
require(storage.initialize(fake_io()), "metadata catalog did not initialize");
|
||||
require(storage.set_alias(stable, "Player one", 10) ==
|
||||
ProfileStorageResult::kOk &&
|
||||
storage.set_profile_name(stable, 0, "Zelda", 5) ==
|
||||
ProfileStorageResult::kOk &&
|
||||
storage.set_profile_name(stable, 7, "Desktop", 7) ==
|
||||
ProfileStorageResult::kOk,
|
||||
"profile metadata did not append");
|
||||
char value[PROFILE_STORAGE_METADATA_PAYLOAD_SIZE]{};
|
||||
require(storage.get_alias(stable, value, sizeof(value)) ==
|
||||
ProfileStorageResult::kOk &&
|
||||
strcmp(value, "Player one") == 0,
|
||||
"controller alias did not read back");
|
||||
|
||||
flash.fail_after_programs = flash.programs + 1;
|
||||
require(storage.set_alias(stable, "Interrupted", 11) ==
|
||||
ProfileStorageResult::kIoError,
|
||||
"interrupted alias append reported success");
|
||||
flash.fail_after_programs = -1;
|
||||
ProfileStorage reloaded;
|
||||
require(reloaded.initialize(fake_io()) &&
|
||||
reloaded.get_alias(stable, value, sizeof(value)) ==
|
||||
ProfileStorageResult::kOk &&
|
||||
strcmp(value, "Player one") == 0,
|
||||
"interrupted alias displaced the previous value");
|
||||
require(reloaded.get_profile_name(stable, 0, value, sizeof(value)) ==
|
||||
ProfileStorageResult::kOk &&
|
||||
strcmp(value, "Zelda") == 0 &&
|
||||
reloaded.get_profile_name(stable, 7, value, sizeof(value)) ==
|
||||
ProfileStorageResult::kOk &&
|
||||
strcmp(value, "Desktop") == 0,
|
||||
"profile names did not survive reload");
|
||||
}
|
||||
|
||||
void test_compaction_preserves_latest_records() {
|
||||
erase_all();
|
||||
const ControllerIdentity global = controller_identity_global();
|
||||
ProfileStorage storage;
|
||||
require(storage.initialize(fake_io()), "catalog did not initialize");
|
||||
ControllerProfile profile = controller_profile_default(global, 0);
|
||||
require(storage.set_alias(global, "Fallback", 8) ==
|
||||
ProfileStorageResult::kOk &&
|
||||
storage.set_profile_name(global, 0, "Compacted", 9) ==
|
||||
ProfileStorageResult::kOk,
|
||||
"compaction metadata did not append");
|
||||
for (uint16_t write = 1; write <= 260; ++write) {
|
||||
profile.weak_rumble_scale = static_cast<uint8_t>(write);
|
||||
require(storage.set(global, 0, profile) == ProfileStorageResult::kOk,
|
||||
|
|
@ -264,11 +307,19 @@ void test_compaction_preserves_latest_records() {
|
|||
"full arena did not compact into its peer");
|
||||
ProfileStorage reloaded;
|
||||
ControllerProfile recovered{};
|
||||
char metadata[PROFILE_STORAGE_METADATA_PAYLOAD_SIZE]{};
|
||||
require(reloaded.initialize(fake_io()) &&
|
||||
reloaded.get(global, 0, &recovered) ==
|
||||
ProfileStorageResult::kOk &&
|
||||
recovered.weak_rumble_scale == static_cast<uint8_t>(260),
|
||||
"compaction did not preserve the latest profile");
|
||||
recovered.weak_rumble_scale == static_cast<uint8_t>(260) &&
|
||||
reloaded.get_alias(global, metadata, sizeof(metadata)) ==
|
||||
ProfileStorageResult::kOk &&
|
||||
strcmp(metadata, "Fallback") == 0 &&
|
||||
reloaded.get_profile_name(global, 0, metadata,
|
||||
sizeof(metadata)) ==
|
||||
ProfileStorageResult::kOk &&
|
||||
strcmp(metadata, "Compacted") == 0,
|
||||
"compaction did not preserve profiles and metadata");
|
||||
}
|
||||
|
||||
void test_legacy_migration_is_atomic_and_complete() {
|
||||
|
|
@ -306,6 +357,7 @@ int main() {
|
|||
test_empty_catalog_and_eight_profiles();
|
||||
test_identity_capacity_and_defaults();
|
||||
test_interrupted_and_corrupt_append_recovery();
|
||||
test_profile_names_and_aliases_recover();
|
||||
test_compaction_preserves_latest_records();
|
||||
test_legacy_migration_is_atomic_and_complete();
|
||||
std::cout << "profile storage tests passed\n";
|
||||
|
|
|
|||
|
|
@ -95,6 +95,15 @@ class FakeDevice:
|
|||
for identity in self.profile_identities
|
||||
for index in range(config_manager.PROFILE_CAPACITY)
|
||||
}
|
||||
self.profile_aliases = {
|
||||
identity.to_bytes(): "" for identity in self.profile_identities
|
||||
}
|
||||
self.profile_names = {
|
||||
(identity.to_bytes(), index): ""
|
||||
for identity in self.profile_identities
|
||||
for index in range(config_manager.PROFILE_CAPACITY)
|
||||
}
|
||||
self.identified_identities: list[bytes] = []
|
||||
self.selected_profile = (self.global_identity.to_bytes(), 0)
|
||||
self.profile_generation = 7
|
||||
self.profile_transaction_id = 0
|
||||
|
|
@ -111,6 +120,8 @@ class FakeDevice:
|
|||
self.profile_chunk_sizes: list[int] = []
|
||||
self.pending_profile_mutation: tuple[int, bytes, int] | None = None
|
||||
self.profile_transaction_pending_reads = 0
|
||||
self.playtest_battery = 251
|
||||
self.playtest_capabilities = 0x0F
|
||||
self.profile_status_responses: list[tuple[int, int]] = []
|
||||
self.playtest_connected = True
|
||||
self.playtest_slot = 1
|
||||
|
|
@ -143,8 +154,29 @@ class FakeDevice:
|
|||
def _profile_list_payload(self) -> bytes:
|
||||
payload = bytearray([len(self.profile_identities)])
|
||||
for identity in self.profile_identities:
|
||||
payload.extend(identity.to_bytes())
|
||||
payload.extend((self.active_profiles[identity.to_bytes()], 0))
|
||||
identity_bytes = identity.to_bytes()
|
||||
alias = self.profile_aliases.get(identity_bytes, "").encode("utf-8")
|
||||
payload.extend(identity_bytes)
|
||||
payload.extend((self.active_profiles[identity_bytes], 0, len(alias)))
|
||||
payload.extend(alias)
|
||||
payload.extend(bytes(config_manager.PROFILE_METADATA_MAX_BYTES - len(alias)))
|
||||
return bytes(payload)
|
||||
|
||||
def _profile_metadata_payload(self) -> bytes:
|
||||
identity, _ = self.selected_profile
|
||||
values = [
|
||||
self.profile_aliases.get(identity, ""),
|
||||
*(
|
||||
self.profile_names.get((identity, index), "")
|
||||
for index in range(config_manager.PROFILE_CAPACITY)
|
||||
),
|
||||
]
|
||||
payload = bytearray()
|
||||
for value in values:
|
||||
encoded = value.encode("utf-8")
|
||||
payload.extend((len(encoded),))
|
||||
payload.extend(encoded)
|
||||
payload.extend(bytes(config_manager.PROFILE_METADATA_MAX_BYTES - len(encoded)))
|
||||
return bytes(payload)
|
||||
|
||||
def _profile_transaction_payload(self) -> bytes:
|
||||
|
|
@ -182,8 +214,10 @@ class FakeDevice:
|
|||
*self.playtest_triggers
|
||||
)
|
||||
payload[38] = 1 if self.playtest_motion is not None else 0
|
||||
payload[39] = self.playtest_battery
|
||||
payload[40] = self.playtest_capabilities
|
||||
if self.playtest_motion is not None:
|
||||
struct.pack_into("<hhhhhh", payload, 40, *self.playtest_motion)
|
||||
struct.pack_into("<hhhhhh", payload, 42, *self.playtest_motion)
|
||||
return bytes(payload), flags
|
||||
|
||||
def _queue_profile_mutation(self, operation: int, payload: bytes) -> None:
|
||||
|
|
@ -334,6 +368,13 @@ class FakeDevice:
|
|||
schema=config_manager.PROFILE_PLAYTEST_SCHEMA_VERSION,
|
||||
generation=self.playtest_state_generation,
|
||||
)
|
||||
if request == config_manager.OP_PROFILE_METADATA_READ:
|
||||
return make_response(
|
||||
request,
|
||||
self._profile_metadata_payload(),
|
||||
schema=config_manager.PROFILE_METADATA_SCHEMA_VERSION,
|
||||
generation=self.profile_generation,
|
||||
)
|
||||
if request == config_manager.OP_PROFILE_TRANSACTION_STATUS:
|
||||
if self.profile_transaction_status == config_manager.STATUS_PENDING:
|
||||
if self.profile_transaction_pending_reads:
|
||||
|
|
@ -513,6 +554,26 @@ class FakeDevice:
|
|||
<= self.profile_transaction_index
|
||||
< config_manager.PROFILE_CAPACITY
|
||||
)
|
||||
elif request == config_manager.OP_PROFILE_METADATA_SET:
|
||||
self.profile_transaction_id = struct.unpack_from("<I", payload)[0]
|
||||
identity = payload[4:18]
|
||||
profile_index = payload[18]
|
||||
value_size = payload[19]
|
||||
value = payload[20 : 20 + value_size].decode("utf-8")
|
||||
assert len(payload) == 20 + value_size
|
||||
if profile_index == config_manager.PROFILE_NONE_BUTTON:
|
||||
self.profile_aliases[identity] = value
|
||||
else:
|
||||
self.profile_names[(identity, profile_index)] = value
|
||||
self.profile_transaction_identity = identity
|
||||
self.profile_transaction_index = profile_index
|
||||
self.profile_transaction_payload = bytearray()
|
||||
self.profile_transaction_expected_size = 0
|
||||
self.profile_transaction_expected_crc = 0
|
||||
self.profile_generation += 1
|
||||
self.profile_transaction_status = config_manager.STATUS_OK
|
||||
elif request == config_manager.OP_PROFILE_IDENTIFY:
|
||||
self.identified_identities.append(payload)
|
||||
else:
|
||||
raise AssertionError(f"unexpected OUT request {request}")
|
||||
return len(encoded)
|
||||
|
|
@ -1456,6 +1517,32 @@ def test_profile_list_select_read_and_chunked_commit() -> None:
|
|||
)
|
||||
|
||||
|
||||
def test_profile_metadata_and_identify_round_trip() -> None:
|
||||
device = FakeDevice()
|
||||
identity = device.stable_identity
|
||||
alias_status = config_manager.set_profile_metadata(
|
||||
device, identity, config_manager.PROFILE_NONE_BUTTON,
|
||||
"Desk pad", 1.0,
|
||||
)
|
||||
name_status = config_manager.set_profile_metadata(
|
||||
device, identity, 7, "Desktop", 1.0,
|
||||
)
|
||||
metadata = config_manager.read_profile_metadata(device, identity, 7)
|
||||
assert alias_status.status == config_manager.STATUS_OK
|
||||
assert name_status.stored_generation == alias_status.stored_generation + 1
|
||||
assert metadata.alias == "Desk pad"
|
||||
assert metadata.profile_names[7] == "Desktop"
|
||||
assert config_manager.list_profiles(device)[1].alias == "Desk pad"
|
||||
|
||||
config_manager.identify_controller(device, identity)
|
||||
assert device.identified_identities == [identity.to_bytes()]
|
||||
with pytest.raises(
|
||||
config_manager.ConfigManagerError,
|
||||
match="no controller to identify",
|
||||
):
|
||||
config_manager.identify_controller(device, device.global_identity)
|
||||
|
||||
|
||||
def test_profile_playtest_decodes_raw_controller_state() -> None:
|
||||
device = FakeDevice()
|
||||
playtest = config_manager.read_profile_playtest(device)
|
||||
|
|
@ -1469,6 +1556,8 @@ def test_profile_playtest_decodes_raw_controller_state() -> None:
|
|||
left_stick=(-1234, 2345),
|
||||
right_stick=(-30000, 30000),
|
||||
triggers=(123, 65000),
|
||||
battery=251,
|
||||
capabilities=0x0F,
|
||||
motion=(1, -2, 3, -4, 5, -6),
|
||||
)
|
||||
assert playtest.to_json_object()["buttons"] == [
|
||||
|
|
@ -1489,6 +1578,8 @@ def test_profile_playtest_decodes_raw_controller_state() -> None:
|
|||
left_stick=(0, 0),
|
||||
right_stick=(0, 0),
|
||||
triggers=(0, 0),
|
||||
battery=0,
|
||||
capabilities=0,
|
||||
motion=None,
|
||||
)
|
||||
device.playtest_connected = True
|
||||
|
|
|
|||
|
|
@ -188,6 +188,10 @@ def test_editor_reads_writes_and_activates_profiles_atomically(
|
|||
]
|
||||
assert playtest["left_stick"] == {"x": -1234, "y": 2345}
|
||||
assert playtest["triggers"] == {"left": 123, "right": 65000}
|
||||
assert playtest["battery"] == 100
|
||||
assert playtest["capabilities"] == [
|
||||
"rumble", "lightbar", "player_leds", "motion"
|
||||
]
|
||||
|
||||
status, selected = request_json(f"{base_url}/api/profiles/1/8")
|
||||
assert status == 200
|
||||
|
|
@ -209,6 +213,53 @@ def test_editor_reads_writes_and_activates_profiles_atomically(
|
|||
== custom_profile()
|
||||
)
|
||||
assert device.profile_chunk_sizes == [40, 40, 40, 40, 40, 40, 16]
|
||||
status, renamed = request_json(
|
||||
f"{base_url}/api/profiles/1/8/name",
|
||||
method="PUT",
|
||||
value={"value": "Desktop"},
|
||||
token=token,
|
||||
)
|
||||
assert status == 200
|
||||
assert renamed["stored_generation"] == 9
|
||||
assert device.profile_names[
|
||||
(device.stable_identity.to_bytes(), 7)
|
||||
] == "Desktop"
|
||||
|
||||
status, aliased = request_json(
|
||||
f"{base_url}/api/identities/1/alias",
|
||||
method="PUT",
|
||||
value={"value": "Desk pad"},
|
||||
token=token,
|
||||
)
|
||||
assert status == 200
|
||||
assert aliased["label"] == "Desk pad"
|
||||
|
||||
status, identified = request_json(
|
||||
f"{base_url}/api/identities/1/identify",
|
||||
method="POST",
|
||||
token=token,
|
||||
)
|
||||
assert status == 200
|
||||
assert identified == {"identified": True}
|
||||
assert device.identified_identities == [
|
||||
device.stable_identity.to_bytes()
|
||||
]
|
||||
|
||||
status, copied = request_json(
|
||||
f"{base_url}/api/profiles/1/8/copy",
|
||||
method="POST",
|
||||
value={"identity_index": 1, "profile_number": 4},
|
||||
token=token,
|
||||
)
|
||||
assert status == 200
|
||||
assert copied["stored_generation"] == 12
|
||||
assert device.profiles[
|
||||
(device.stable_identity.to_bytes(), 3)
|
||||
] == custom_profile().to_bytes()
|
||||
assert device.profile_names[
|
||||
(device.stable_identity.to_bytes(), 3)
|
||||
] == "Desktop"
|
||||
|
||||
|
||||
status, activated = request_json(
|
||||
f"{base_url}/api/profiles/1/8/activate",
|
||||
|
|
@ -217,7 +268,7 @@ def test_editor_reads_writes_and_activates_profiles_atomically(
|
|||
)
|
||||
|
||||
assert status == 200
|
||||
assert activated["stored_generation"] == 9
|
||||
assert activated["stored_generation"] == 13
|
||||
assert device.active_profiles[device.stable_identity.to_bytes()] == 7
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ ConfigurationServiceSnapshot current_configuration{};
|
|||
ProfileServiceListSnapshot current_profile_list{};
|
||||
ProfileServiceSelectedSnapshot current_profile_selected{};
|
||||
ProfileServiceTransactionSnapshot current_profile_transaction{};
|
||||
ProfileServiceMetadataSnapshot current_profile_metadata{};
|
||||
Bluepad32PlaytestSnapshot current_playtest[
|
||||
BLUEPAD32_INPUT_BACKEND_SLOT_COUNT]{};
|
||||
AdapterUsbMode current_active_mode = AdapterUsbMode::kSwitchProbe;
|
||||
|
|
@ -52,6 +53,11 @@ uint32_t profile_reset_transaction_id = 0;
|
|||
uint32_t profile_commit_transaction_id = 0;
|
||||
bool profile_activate_requested = false;
|
||||
uint32_t profile_activate_transaction_id = 0;
|
||||
bool profile_metadata_requested = false;
|
||||
uint32_t profile_metadata_transaction_id = 0;
|
||||
uint8_t profile_metadata_index = 0;
|
||||
std::string profile_metadata_value;
|
||||
bool identify_requested = false;
|
||||
|
||||
void require(bool condition, const char* message) {
|
||||
if (!condition) {
|
||||
|
|
@ -454,12 +460,12 @@ void test_profile_vendor_requests() {
|
|||
Operation::kProfileList, TUSB_DIR_IN, kMaximumResponseSize);
|
||||
require(usb_configuration_management_vendor_control(
|
||||
0, CONTROL_STAGE_SETUP, &request) &&
|
||||
control_payload.size() == kResponseHeaderSize + 33 &&
|
||||
control_payload.size() == kResponseHeaderSize + 97 &&
|
||||
control_payload[5] ==
|
||||
static_cast<uint8_t>(Operation::kProfileList) &&
|
||||
control_payload[10] == CONTROLLER_PROFILE_SCHEMA_VERSION &&
|
||||
control_payload[kResponseHeaderSize] == 2 &&
|
||||
control_payload[kResponseHeaderSize + 31] == 2,
|
||||
control_payload[kResponseHeaderSize + 63] == 2,
|
||||
"profile list response was not encoded");
|
||||
|
||||
current_profile_selected = {};
|
||||
|
|
@ -502,6 +508,8 @@ void test_profile_vendor_requests() {
|
|||
current_playtest[2].state.right_stick_y = INT16_MAX;
|
||||
current_playtest[2].state.left_trigger = 123;
|
||||
current_playtest[2].state.right_trigger = 65000;
|
||||
current_playtest[2].battery = 201;
|
||||
current_playtest[2].capabilities = 0x0f;
|
||||
current_playtest[2].state.motion_sample_count = 1;
|
||||
current_playtest[2].state.motion_samples[0] =
|
||||
{1, -2, 3, -4, 5, -6};
|
||||
|
|
@ -531,7 +539,9 @@ void test_profile_vendor_requests() {
|
|||
read_u16(control_payload, kResponseHeaderSize + 36) ==
|
||||
65000 &&
|
||||
static_cast<int16_t>(read_u16(
|
||||
control_payload, kResponseHeaderSize + 50)) == -6,
|
||||
control_payload, kResponseHeaderSize + 52)) == -6 &&
|
||||
control_payload[kResponseHeaderSize + 39] == 201 &&
|
||||
control_payload[kResponseHeaderSize + 40] == 0x0f,
|
||||
"profile playtest response lost live controller state");
|
||||
current_playtest[2].active = false;
|
||||
require(usb_configuration_management_vendor_control(
|
||||
|
|
@ -539,6 +549,52 @@ void test_profile_vendor_requests() {
|
|||
control_payload[kResponseHeaderSize] == 0 &&
|
||||
control_payload[kResponseHeaderSize + 1] == 0xff,
|
||||
"disconnected profile playtest was not encoded");
|
||||
current_profile_metadata = {};
|
||||
current_profile_metadata.metadata.state = ProfileServiceState::kReady;
|
||||
current_profile_metadata.metadata.generation = 10;
|
||||
current_profile_metadata.status =
|
||||
ConfigurationTransactionStatus::kCommitted;
|
||||
current_profile_metadata.valid = true;
|
||||
memcpy(current_profile_metadata.alias, "Desk pad", 9);
|
||||
memcpy(current_profile_metadata.profile_names[7], "Desktop", 8);
|
||||
request = setup_request(
|
||||
Operation::kProfileMetadataRead, TUSB_DIR_IN,
|
||||
kMaximumResponseSize);
|
||||
require(usb_configuration_management_vendor_control(
|
||||
0, CONTROL_STAGE_SETUP, &request) &&
|
||||
control_payload.size() ==
|
||||
kResponseHeaderSize + kProfileMetadataPayloadSize &&
|
||||
control_payload[kResponseHeaderSize] == 8 &&
|
||||
memcmp(&control_payload[kResponseHeaderSize + 1],
|
||||
"Desk pad", 8) == 0 &&
|
||||
control_payload[
|
||||
kResponseHeaderSize +
|
||||
8 * (PROFILE_SERVICE_METADATA_MAX_BYTES + 1)] == 7,
|
||||
"profile metadata response was not encoded");
|
||||
|
||||
std::vector<uint8_t> metadata(27);
|
||||
write_u32(&metadata, 0, 0x12345678);
|
||||
require(controller_identity_encode(
|
||||
expected_identity, &metadata[4],
|
||||
CONTROLLER_IDENTITY_ENCODED_SIZE),
|
||||
"metadata identity did not encode");
|
||||
metadata[18] = 7;
|
||||
metadata[19] = 7;
|
||||
memcpy(&metadata[20], "Desktop", 7);
|
||||
perform_out(Operation::kProfileMetadataSet, metadata);
|
||||
require(profile_metadata_requested &&
|
||||
profile_metadata_transaction_id == 0x12345678 &&
|
||||
profile_metadata_index == 7 &&
|
||||
profile_metadata_value == "Desktop",
|
||||
"profile metadata mutation was not dispatched");
|
||||
|
||||
std::vector<uint8_t> identify(CONTROLLER_IDENTITY_ENCODED_SIZE);
|
||||
require(controller_identity_encode(
|
||||
expected_identity, identify.data(), identify.size()),
|
||||
"identify identity did not encode");
|
||||
perform_out(Operation::kProfileIdentify, identify);
|
||||
require(identify_requested,
|
||||
"controller Identify request was not dispatched");
|
||||
|
||||
current_profile_transaction = {};
|
||||
current_profile_transaction.metadata.state =
|
||||
|
|
@ -735,6 +791,17 @@ ConfigurationTransactionStatus profile_service_select(
|
|||
return ConfigurationTransactionStatus::kPending;
|
||||
}
|
||||
|
||||
ConfigurationTransactionStatus profile_service_set_metadata(
|
||||
uint32_t transaction_id, const ControllerIdentity& identity,
|
||||
uint8_t selected_profile, const char* value, size_t value_size) {
|
||||
profile_metadata_requested = true;
|
||||
profile_metadata_transaction_id = transaction_id;
|
||||
profile_identity = identity;
|
||||
profile_metadata_index = selected_profile;
|
||||
profile_metadata_value.assign(value, value + value_size);
|
||||
return ConfigurationTransactionStatus::kPending;
|
||||
}
|
||||
|
||||
ConfigurationTransactionStatus profile_service_begin(
|
||||
uint32_t transaction_id, const ControllerIdentity& identity,
|
||||
uint8_t selected_profile, uint16_t schema_version,
|
||||
|
|
@ -763,6 +830,17 @@ ConfigurationTransactionStatus profile_service_commit(
|
|||
return ConfigurationTransactionStatus::kPending;
|
||||
}
|
||||
|
||||
void profile_service_metadata_snapshot(
|
||||
ProfileServiceMetadataSnapshot* output) {
|
||||
*output = current_profile_metadata;
|
||||
}
|
||||
|
||||
bool bluepad32_input_backend_identify(
|
||||
const ControllerIdentity&) {
|
||||
identify_requested = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
ConfigurationTransactionStatus profile_service_reset(
|
||||
uint32_t transaction_id, const ControllerIdentity& identity,
|
||||
uint8_t selected_profile) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue