Add physical and USB pairing management
This commit is contained in:
parent
74a62f035b
commit
41c7021813
22 changed files with 1263 additions and 72 deletions
|
|
@ -41,11 +41,14 @@ void require(bool condition, const char* message) {
|
|||
}
|
||||
}
|
||||
|
||||
int apply_pressed(BootselPairingButtonHoldFsm& fsm, int count) {
|
||||
int events = 0;
|
||||
std::vector<BootselPairingButtonEvent> apply_pressed(
|
||||
BootselPairingButtonHoldFsm& fsm, int count) {
|
||||
std::vector<BootselPairingButtonEvent> events;
|
||||
for (int sample = 0; sample < count; ++sample) {
|
||||
if (fsm.update(BootselPairingButtonSample::kPressed)) {
|
||||
++events;
|
||||
const BootselPairingButtonEvent event =
|
||||
fsm.update(BootselPairingButtonSample::kPressed);
|
||||
if (event != BootselPairingButtonEvent::kNone) {
|
||||
events.push_back(event);
|
||||
}
|
||||
}
|
||||
return events;
|
||||
|
|
@ -53,62 +56,88 @@ int apply_pressed(BootselPairingButtonHoldFsm& fsm, int count) {
|
|||
|
||||
void test_short_press() {
|
||||
BootselPairingButtonHoldFsm fsm;
|
||||
require(apply_pressed(fsm, 19) == 0,
|
||||
require(apply_pressed(fsm, 19).empty(),
|
||||
"a 19-sample press must not complete the hold");
|
||||
require(!fsm.update(BootselPairingButtonSample::kReleased),
|
||||
require(fsm.update(BootselPairingButtonSample::kReleased) ==
|
||||
BootselPairingButtonEvent::kNone,
|
||||
"a short-press release must not report a hold");
|
||||
require(apply_pressed(fsm, 19) == 0,
|
||||
require(apply_pressed(fsm, 19).empty(),
|
||||
"a release must discard the previous short press");
|
||||
}
|
||||
|
||||
void test_exact_and_long_hold_once() {
|
||||
void test_pairing_and_clear_events_once() {
|
||||
BootselPairingButtonHoldFsm fsm;
|
||||
require(apply_pressed(fsm, 19) == 0,
|
||||
"the hold must not fire before sample 20");
|
||||
require(fsm.update(BootselPairingButtonSample::kPressed),
|
||||
"the hold must fire on exactly sample 20");
|
||||
require(apply_pressed(fsm, 100) == 0,
|
||||
"a continuously held button must not repeat");
|
||||
require(apply_pressed(fsm, 19).empty(),
|
||||
"the pairing hold must not fire before sample 20");
|
||||
require(fsm.update(BootselPairingButtonSample::kPressed) ==
|
||||
BootselPairingButtonEvent::kOpenPairing,
|
||||
"pairing must fire on exactly sample 20");
|
||||
require(apply_pressed(fsm, 79).empty(),
|
||||
"a long hold must not fire between pairing and clearing");
|
||||
require(fsm.update(BootselPairingButtonSample::kPressed) ==
|
||||
BootselPairingButtonEvent::kClearPairings,
|
||||
"clearing must fire on exactly sample 100");
|
||||
require(apply_pressed(fsm, 100).empty(),
|
||||
"a continuously held button must not repeat either event");
|
||||
}
|
||||
|
||||
void test_release_and_rearm() {
|
||||
BootselPairingButtonHoldFsm fsm;
|
||||
require(apply_pressed(fsm, 20) == 1,
|
||||
"the initial hold must fire once");
|
||||
require(!fsm.update(BootselPairingButtonSample::kReleased),
|
||||
const auto first_events = apply_pressed(fsm, 100);
|
||||
require(first_events.size() == 2 &&
|
||||
first_events[0] ==
|
||||
BootselPairingButtonEvent::kOpenPairing &&
|
||||
first_events[1] ==
|
||||
BootselPairingButtonEvent::kClearPairings,
|
||||
"the initial long hold must report pairing then clearing");
|
||||
require(fsm.update(BootselPairingButtonSample::kReleased) ==
|
||||
BootselPairingButtonEvent::kNone,
|
||||
"release must rearm without reporting an event");
|
||||
require(apply_pressed(fsm, 20) == 1,
|
||||
"a valid release must permit one later hold");
|
||||
const auto second_events = apply_pressed(fsm, 20);
|
||||
require(second_events.size() == 1 &&
|
||||
second_events[0] ==
|
||||
BootselPairingButtonEvent::kOpenPairing,
|
||||
"a valid release must permit a later pairing hold");
|
||||
}
|
||||
|
||||
void test_unread_samples_do_not_transition() {
|
||||
BootselPairingButtonHoldFsm fsm;
|
||||
require(apply_pressed(fsm, 10) == 0,
|
||||
"the first half of a hold must not fire");
|
||||
require(apply_pressed(fsm, 10).empty(),
|
||||
"the first half of a pairing hold must not fire");
|
||||
for (int sample = 0; sample < 8; ++sample) {
|
||||
require(!fsm.update(BootselPairingButtonSample::kUnread),
|
||||
require(fsm.update(BootselPairingButtonSample::kUnread) ==
|
||||
BootselPairingButtonEvent::kNone,
|
||||
"unread press samples must not report or reset a hold");
|
||||
}
|
||||
require(apply_pressed(fsm, 9) == 0,
|
||||
require(apply_pressed(fsm, 9).empty(),
|
||||
"valid pressed samples must resume after unread samples");
|
||||
require(fsm.update(BootselPairingButtonSample::kPressed),
|
||||
require(fsm.update(BootselPairingButtonSample::kPressed) ==
|
||||
BootselPairingButtonEvent::kOpenPairing,
|
||||
"20 valid pressed samples must fire despite unread samples");
|
||||
|
||||
require(!fsm.update(BootselPairingButtonSample::kUnread),
|
||||
require(fsm.update(BootselPairingButtonSample::kUnread) ==
|
||||
BootselPairingButtonEvent::kNone,
|
||||
"an unread release must not rearm a completed hold");
|
||||
require(apply_pressed(fsm, 20) == 0,
|
||||
"the held state must persist until a valid release");
|
||||
require(!fsm.update(BootselPairingButtonSample::kReleased),
|
||||
require(apply_pressed(fsm, 79).empty(),
|
||||
"the long hold must continue across an unread sample");
|
||||
require(fsm.update(BootselPairingButtonSample::kPressed) ==
|
||||
BootselPairingButtonEvent::kClearPairings,
|
||||
"100 valid pressed samples must clear despite unread samples");
|
||||
require(fsm.update(BootselPairingButtonSample::kReleased) ==
|
||||
BootselPairingButtonEvent::kNone,
|
||||
"a valid release must only rearm");
|
||||
require(apply_pressed(fsm, 20) == 1,
|
||||
const auto events = apply_pressed(fsm, 20);
|
||||
require(events.size() == 1 &&
|
||||
events[0] == BootselPairingButtonEvent::kOpenPairing,
|
||||
"the FSM must fire after the eventual valid release");
|
||||
}
|
||||
|
||||
bool run_sample(uint64_t sample_time_ms, int result, bool pressed) {
|
||||
BootselPairingButtonEvent run_sample(
|
||||
uint64_t sample_time_ms, int result, bool pressed) {
|
||||
flash_responses.push_back({result, pressed});
|
||||
now_ms = sample_time_ms;
|
||||
const std::size_t expected_consumed = flash_responses.size();
|
||||
const bool event = bootsel_pairing_button_task();
|
||||
const BootselPairingButtonEvent event = bootsel_pairing_button_task();
|
||||
require(next_flash_response == expected_consumed,
|
||||
"a due poll must invoke flash_safe_execute exactly once");
|
||||
return event;
|
||||
|
|
@ -116,15 +145,18 @@ bool run_sample(uint64_t sample_time_ms, int result, bool pressed) {
|
|||
|
||||
void test_sampler_cadence_and_callback_failure() {
|
||||
now_ms = 0;
|
||||
require(!bootsel_pairing_button_task(),
|
||||
require(bootsel_pairing_button_task() ==
|
||||
BootselPairingButtonEvent::kNone,
|
||||
"the sampler must wait for its first 100 ms cadence");
|
||||
now_ms = 99;
|
||||
require(!bootsel_pairing_button_task(),
|
||||
require(bootsel_pairing_button_task() ==
|
||||
BootselPairingButtonEvent::kNone,
|
||||
"the sampler must not poll before 100 ms");
|
||||
require(flash_safe_calls == 0,
|
||||
"sub-cadence task calls must not enter flash-safe execution");
|
||||
|
||||
require(!run_sample(100, PICO_OK, true),
|
||||
require(run_sample(100, PICO_OK, true) ==
|
||||
BootselPairingButtonEvent::kNone,
|
||||
"the first valid pressed sample must only start the hold");
|
||||
require(flash_safe_calls == 1 && qspi_override_writes.size() == 2,
|
||||
"a successful sample must float and restore QSPI CSn once");
|
||||
|
|
@ -136,39 +168,49 @@ void test_sampler_cadence_and_callback_failure() {
|
|||
"the callback must restore normal QSPI CSn control");
|
||||
|
||||
now_ms = 199;
|
||||
require(!bootsel_pairing_button_task(),
|
||||
require(bootsel_pairing_button_task() ==
|
||||
BootselPairingButtonEvent::kNone,
|
||||
"the sampler must remain gated between 10 Hz polls");
|
||||
require(flash_safe_calls == 1,
|
||||
"an early task call must not sample BOOTSEL");
|
||||
|
||||
const std::size_t writes_before_failure = qspi_override_writes.size();
|
||||
require(!run_sample(200, -1, true),
|
||||
require(run_sample(200, -1, true) ==
|
||||
BootselPairingButtonEvent::kNone,
|
||||
"flash-safe failure must be treated as unread");
|
||||
require(qspi_override_writes.size() == writes_before_failure,
|
||||
"a failed flash-safe entry must not invoke the callback");
|
||||
|
||||
for (uint64_t time = 300; time < 2100; time += 100) {
|
||||
require(!run_sample(time, PICO_OK, true),
|
||||
require(run_sample(time, PICO_OK, true) ==
|
||||
BootselPairingButtonEvent::kNone,
|
||||
"the sampler must wait for 20 valid pressed samples");
|
||||
}
|
||||
require(run_sample(2100, PICO_OK, true),
|
||||
require(run_sample(2100, PICO_OK, true) ==
|
||||
BootselPairingButtonEvent::kOpenPairing,
|
||||
"a failed sample must not reset the valid pressed count");
|
||||
require(!run_sample(2200, PICO_OK, true),
|
||||
"a held button must not repeat after firing");
|
||||
require(run_sample(2200, PICO_OK, true) ==
|
||||
BootselPairingButtonEvent::kNone,
|
||||
"a held button must not repeat pairing");
|
||||
|
||||
require(!run_sample(2300, -1, false),
|
||||
require(run_sample(2300, -1, false) ==
|
||||
BootselPairingButtonEvent::kNone,
|
||||
"a failed release sample must remain unread");
|
||||
require(!run_sample(2400, PICO_OK, true),
|
||||
require(run_sample(2400, PICO_OK, true) ==
|
||||
BootselPairingButtonEvent::kNone,
|
||||
"an unread release must not rearm the sampler FSM");
|
||||
require(!run_sample(2500, PICO_OK, false),
|
||||
require(run_sample(2500, PICO_OK, false) ==
|
||||
BootselPairingButtonEvent::kNone,
|
||||
"a valid release must rearm without firing");
|
||||
|
||||
for (uint64_t time = 2600; time < 4500; time += 100) {
|
||||
require(!run_sample(time, PICO_OK, true),
|
||||
require(run_sample(time, PICO_OK, true) ==
|
||||
BootselPairingButtonEvent::kNone,
|
||||
"the rearmed sampler must count a fresh hold");
|
||||
}
|
||||
require(run_sample(4500, PICO_OK, true),
|
||||
"a valid release must permit a second completed hold");
|
||||
require(run_sample(4500, PICO_OK, true) ==
|
||||
BootselPairingButtonEvent::kOpenPairing,
|
||||
"a valid release must permit a second pairing hold");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
@ -216,7 +258,7 @@ int flash_safe_execute(void (*function)(void*), void* parameter,
|
|||
|
||||
int main() {
|
||||
test_short_press();
|
||||
test_exact_and_long_hold_once();
|
||||
test_pairing_and_clear_events_once();
|
||||
test_release_and_rearm();
|
||||
test_unread_samples_do_not_transition();
|
||||
test_sampler_cadence_and_callback_failure();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue