Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions include/flash.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@
#define FLASH_VERSION 7

typedef struct FlashData {
unsigned long magic;
unsigned char version;
unsigned char current_page;
unsigned char reserved[2];
Page pages[PAGE_COUNT];
} __attribute__((packed)) FlashData;
unsigned long magic; // 4 bytes (offset 0)
unsigned char version; // 1 byte (offset 4)
unsigned char current_page; // 1 byte (offset 5)
unsigned char reserved[2]; // 2 bytes (offset 6)
// Total: 8 bytes, naturally 8-byte aligned for pages array
Page pages[PAGE_COUNT]; // offset 8, 8-byte aligned
} FlashData;

void flash_load(void);
void flash_save(void);
unsigned char flash_is_valid(void);
void flash_factory_reset(void);

#endif // FLASH_H
18 changes: 18 additions & 0 deletions include/layout.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,22 @@ void process_pending_render(void);
*/
void invalidate_led_cache(void);

/**
* Render factory reset warning pattern (slow)
* Red medium brightness on grid for first warning phase
*/
void render_factory_reset_warning_slow(void);

/**
* Render factory reset warning pattern (fast)
* Red full brightness on grid for second warning phase
*/
void render_factory_reset_warning_fast(void);

/**
* Render factory reset success pattern
* Green on grid to indicate successful reset
*/
void render_factory_reset_success(void);

#endif // LAYOUT_H
92 changes: 78 additions & 14 deletions src/app.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,14 @@
#include "surface.h"
#include "velocity.h"

static u16 g_timer_count = 0;
static volatile u16 g_timer_count = 0;

// Factory reset state
// Note: These are accessed from both timer and surface events
// volatile ensures proper memory access semantics
static volatile u16 setup_hold_start = 0;
static volatile u8 factory_reset_phase =
0; // 0=none, 1=warning1, 2=warning2, 3=success

static unsigned char is_grid_pad(u8 index) {
u8 row = index / 10;
Expand Down Expand Up @@ -404,23 +411,42 @@ static void handle_setup_mode(u8 index, u8 value) {

// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
void app_surface_event(u8 type, u8 index, u8 value) {
// Setup button: toggle setup mode
// Setup button: toggle setup mode or factory reset
if (type == TYPESETUP) {
if (value) {
if (is_in_setup_mode()) {
// Exit setup mode - save if dirty
if (is_dirty()) {
flash_save();
clear_dirty();
// Button pressed - start tracking hold time
setup_hold_start = g_timer_count;
factory_reset_phase = 0;
} else {
// Button released
// Compute time difference with wrap-around handling
// Subtraction of u16 naturally handles wrap-around in modular arithmetic
u16 held_ms = g_timer_count - setup_hold_start;

// Only toggle setup mode if held < 1s and not in warning/success phase
if (factory_reset_phase == 0 && held_ms < 1000) {
if (is_in_setup_mode()) {
// Exit setup mode - save if dirty
if (is_dirty()) {
flash_save();
clear_dirty();
}
exit_setup_mode();
} else {
// Enter setup mode
clear_all_active_notes();
clear_all_pad_notes();
enter_setup_mode();
}
exit_setup_mode();
} else {
// Enter setup mode
clear_all_active_notes();
clear_all_pad_notes();
enter_setup_mode();
refresh_display();
} else if (factory_reset_phase > 0 && factory_reset_phase < 3) {
// Button released during warning phase - clear warning display
refresh_display();
}
refresh_display();

// Reset factory reset state
setup_hold_start = 0;
factory_reset_phase = 0;
}
return;
}
Expand Down Expand Up @@ -528,6 +554,44 @@ void app_timer_event(void) {
refresh_display();
}

// Factory reset hold timer
if (setup_hold_start != 0) {
// Compute time difference with wrap-around handling
// Subtraction of u16 naturally handles wrap-around in modular arithmetic
u16 held_ms = g_timer_count - setup_hold_start;

if (held_ms >= 3000 && factory_reset_phase < 3) {
// 3 seconds - execute factory reset
flash_factory_reset();
app_state_init();
render_factory_reset_success();
factory_reset_phase = 3;
setup_hold_start = 0; // Stop timing
} else if (held_ms >= 2000 && factory_reset_phase < 2) {
// 2 seconds - show fast warning
render_factory_reset_warning_fast();
factory_reset_phase = 2;
} else if (held_ms >= 1000 && factory_reset_phase < 1) {
// 1 second - show slow warning
render_factory_reset_warning_slow();
factory_reset_phase = 1;
}
}

// Handle success pattern display
static u16 success_timer = 0;
if (factory_reset_phase == 3) {
success_timer++;
if (success_timer >= 500) {
// After 500ms, refresh display and reset
refresh_display();
factory_reset_phase = 0;
success_timer = 0;
}
} else {
success_timer = 0;
}

// Process any pending render requests (deferred rendering optimization)
process_pending_render();

Expand Down
9 changes: 9 additions & 0 deletions src/flash.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,12 @@ unsigned char flash_is_valid(void) {
hal_read_flash(0, (u8 *)&data, sizeof(FlashData));
return data.magic == FLASH_MAGIC && data.version == FLASH_VERSION;
}

void flash_factory_reset(void) {
Page pages[PAGE_COUNT];
for (unsigned char i = 0; i < PAGE_COUNT; i++) {
pages[i] = DEFAULT_PAGE;
}
page_manager_set(pages, 0);
flash_save();
}
53 changes: 53 additions & 0 deletions src/layout.c
Original file line number Diff line number Diff line change
Expand Up @@ -377,3 +377,56 @@ void invalidate_led_cache(void) {
led_cache[i] = LED_CACHE_INVALID;
}
}

void render_factory_reset_warning_slow(void) {
// Red medium brightness on all grid pads
u32 warning_color = (BRIGHT_MED << COLOR_RED_SHIFT);
for (u8 row = 1; row <= 8; row++) {
for (u8 col = 1; col <= 8; col++) {
u8 index = row * 10 + col;
set_pad_color(index, warning_color);
}
}
// Clear control buttons
for (u8 i = 0; i < GRID_SIZE; i++) {
if ((i / 10 >= 1 && i / 10 <= 8 && i % 10 >= 1 && i % 10 <= 8)) {
continue; // Skip grid pads
}
set_pad_color(i, COLOR_BLACK);
}
}

void render_factory_reset_warning_fast(void) {
// Red full brightness on all grid pads
u32 warning_color = COLOR_RED;
for (u8 row = 1; row <= 8; row++) {
for (u8 col = 1; col <= 8; col++) {
u8 index = row * 10 + col;
set_pad_color(index, warning_color);
}
}
// Clear control buttons
for (u8 i = 0; i < GRID_SIZE; i++) {
if ((i / 10 >= 1 && i / 10 <= 8 && i % 10 >= 1 && i % 10 <= 8)) {
continue; // Skip grid pads
}
set_pad_color(i, COLOR_BLACK);
}
}

void render_factory_reset_success(void) {
// Green full brightness on all grid pads
for (u8 row = 1; row <= 8; row++) {
for (u8 col = 1; col <= 8; col++) {
u8 index = row * 10 + col;
set_pad_color(index, COLOR_GREEN);
}
}
// Clear control buttons
for (u8 i = 0; i < GRID_SIZE; i++) {
if ((i / 10 >= 1 && i / 10 <= 8 && i % 10 >= 1 && i % 10 <= 8)) {
continue; // Skip grid pads
}
set_pad_color(i, COLOR_BLACK);
}
}
59 changes: 59 additions & 0 deletions tests/flash_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,64 @@ static void test_flash_all_pages_saved(void **state) {
}
}

static void test_flash_factory_reset(void **state) {
(void)state;

// Load and modify settings
flash_load();
change_page(0);
change_page_root(5);
change_page_scale_type(SCALE_BLUES);
change_page_octave(8);
set_page_midi_channels(0xFF00);
toggle_page_aftertouch_mode();
set_page_velocity_curve(VELOCITY_CURVE_HARD);
increase_page_transpose();

change_page(1);
change_page_root(7);
change_page_octave(2);

flash_save();

// Verify settings were saved
assert_int_equal(get_page(0)->root, 5);
assert_int_equal(get_page(0)->scale_type, SCALE_BLUES);
assert_int_equal(get_page(0)->octave, 8);

// Perform factory reset
flash_factory_reset();

// Verify all pages reset to defaults
for (unsigned char i = 0; i < PAGE_COUNT; i++) {
const Page *page = get_page(i);
assert_int_equal(page->root, 0);
assert_int_equal(page->scale_type, 0);
assert_int_equal(page->octave, 5);
assert_int_equal(page->interval_index, DEFAULT_INTERVAL_INDEX);
assert_int_equal(page->midi_channels, DEFAULT_MIDI_CHANNELS);
assert_int_equal(page->aftertouch_mode, DEFAULT_AFTERTOUCH_MODE);
assert_int_equal(page->velocity_curve, DEFAULT_VELOCITY_CURVE);
assert_int_equal(page->transpose, DEFAULT_TRANSPOSE);
}

// Verify current page reset to 0
assert_int_equal(get_current_page(), 0);

// Verify flash is valid after factory reset
assert_int_equal(flash_is_valid(), 1);

// Reload from flash and verify defaults persisted
Page empty_pages[PAGE_COUNT] = {0};
page_manager_set(empty_pages, 0);
flash_load();

const Page *page = get_page(0);
assert_int_equal(page->root, 0);
assert_int_equal(page->scale_type, 0);
assert_int_equal(page->octave, 5);
}

int main(void) {
const struct CMUnitTest tests[] = {
cmocka_unit_test_setup(test_flash_load_invalid_magic, setup),
Expand All @@ -278,6 +336,7 @@ int main(void) {
cmocka_unit_test_setup(test_flash_preserves_valid_aftertouch_mode, setup),
cmocka_unit_test_setup(test_flash_preserves_valid_transpose, setup),
cmocka_unit_test_setup(test_flash_all_pages_saved, setup),
cmocka_unit_test_setup(test_flash_factory_reset, setup),
};

return cmocka_run_group_tests(tests, NULL, NULL);
Expand Down