From 4f04137f67059f4b2f58f4bbb4dfb5150a7768c8 Mon Sep 17 00:00:00 2001 From: Adam Wonak Date: Mon, 21 Jul 2025 00:27:32 +0000 Subject: [PATCH] Add global/hardware settings to metadata EEPROM (#15) Settings like Encoder Direction and Display Orientation should persist when resetting channel state. Fixes https://github.com/awonak/alt-gravity/issues/7 Reviewed-on: https://git.pinkduck.xyz/awonak/libGravity/pulls/15 --- firmware/Gravity/Gravity.ino | 4 ++-- firmware/Gravity/save_state.cpp | 42 ++++++++++++++++++++++++++------- firmware/Gravity/save_state.h | 5 +++- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/firmware/Gravity/Gravity.ino b/firmware/Gravity/Gravity.ino index b76a61b..3b388d4 100644 --- a/firmware/Gravity/Gravity.ino +++ b/firmware/Gravity/Gravity.ino @@ -196,8 +196,8 @@ void HandleEncoderPressed() { if (app.selected_channel == 0) { // main page // TODO: rewrite as switch if (app.selected_param == PARAM_MAIN_ENCODER_DIR) { - bool reversed = app.selected_sub_param == 1; - gravity.encoder.SetReverseDirection(reversed); + app.encoder_reversed = app.selected_sub_param == 1; + gravity.encoder.SetReverseDirection(app.encoder_reversed); } if (app.selected_param == PARAM_MAIN_SAVE_DATA) { if (app.selected_sub_param < MAX_SAVE_SLOTS) { diff --git a/firmware/Gravity/save_state.cpp b/firmware/Gravity/save_state.cpp index 16ef342..8aa1f50 100644 --- a/firmware/Gravity/save_state.cpp +++ b/firmware/Gravity/save_state.cpp @@ -16,6 +16,7 @@ #include "app_state.h" // Calculate the starting address for EepromData, leaving space for metadata. +static const int METADATA_START_ADDR = 0; static const int EEPROM_DATA_START_ADDR = sizeof(StateManager::Metadata); StateManager::StateManager() : _isDirty(false), _lastChangeTime(0) {} @@ -24,12 +25,14 @@ bool StateManager::initialize(AppState& app) { if (_isDataValid()) { // Load data from the transient slot. return loadData(app, MAX_SAVE_SLOTS); - } else { - // EEPROM does not contain save data for this firmware & version. + } + // EEPROM does not contain save data for this firmware & version. + else { // Erase EEPROM and initialize state. Save default pattern to all save slots. factoryReset(); + // Initialize eeprom and save default patter to all save slots. + _saveMetadata(app); reset(app); - _saveMetadata(); // MAX_SAVE_SLOTS slot is reserved for transient state. for (int i = 0; i <= MAX_SAVE_SLOTS; i++) { app.selected_save_slot = i; @@ -43,10 +46,12 @@ bool StateManager::loadData(AppState& app, byte slot_index) { if (slot_index >= MAX_SAVE_SLOTS) return false; _loadState(app, slot_index); + _loadMetadata(app); return true; } +// Save app state to user specified save slot. void StateManager::saveData(const AppState& app) { if (app.selected_save_slot >= MAX_SAVE_SLOTS) return; @@ -54,17 +59,18 @@ void StateManager::saveData(const AppState& app) { _isDirty = false; } +// Save transient state if it has changed and enough time has passed since last save. void StateManager::update(const AppState& app) { if (_isDirty && (millis() - _lastChangeTime > SAVE_DELAY_MS)) { // MAX_SAVE_SLOTS slot is reserved for transient state. _saveState(app, MAX_SAVE_SLOTS); + _saveMetadata(app); _isDirty = false; } } void StateManager::reset(AppState& app) { app.tempo = Clock::DEFAULT_TEMPO; - app.encoder_reversed = false; app.selected_param = 0; app.selected_channel = 0; app.selected_source = Clock::SOURCE_INTERNAL; @@ -75,6 +81,9 @@ void StateManager::reset(AppState& app) { app.channel[i].Init(); } + // Load global settings from Metadata + _loadMetadata(app); + _isDirty = false; } @@ -86,7 +95,7 @@ void StateManager::markDirty() { // Erases all data in the EEPROM by writing 0 to every address. void StateManager::factoryReset() { noInterrupts(); - for (unsigned int i = 0 ; i < EEPROM.length() ; i++) { + for (unsigned int i = 0; i < EEPROM.length(); i++) { EEPROM.write(i, 0); } interrupts(); @@ -94,7 +103,7 @@ void StateManager::factoryReset() { bool StateManager::_isDataValid() { Metadata load_meta; - EEPROM.get(0, load_meta); + EEPROM.get(METADATA_START_ADDR, load_meta); bool name_match = (strcmp(load_meta.sketch_name, SKETCH_NAME) == 0); bool version_match = (load_meta.version == SKETCH_VERSION); return name_match && version_match; @@ -114,6 +123,10 @@ void StateManager::_saveState(const AppState& app, byte slot_index) { save_data.selected_pulse = static_cast(app.selected_pulse); save_data.selected_save_slot = app.selected_save_slot; + // TODO: break this out into a separate function. Save State should be + // broken out into global / per-channel save methods. When saving via + // "update" only save state for the current channel since other channels + // will not have changed when saving user edits. for (int i = 0; i < Gravity::OUTPUT_COUNT; i++) { const auto& ch = app.channel[i]; auto& save_ch = save_data.channel_data[i]; @@ -141,7 +154,6 @@ void StateManager::_loadState(AppState& app, byte slot_index) { // Restore app state from loaded data. app.tempo = load_data.tempo; - app.encoder_reversed = load_data.encoder_reversed; app.selected_param = load_data.selected_param; app.selected_channel = load_data.selected_channel; app.selected_source = static_cast(load_data.selected_source); @@ -165,11 +177,23 @@ void StateManager::_loadState(AppState& app, byte slot_index) { interrupts(); } -void StateManager::_saveMetadata() { +void StateManager::_saveMetadata(const AppState& app) { noInterrupts(); Metadata current_meta; strcpy(current_meta.sketch_name, SKETCH_NAME); current_meta.version = SKETCH_VERSION; - EEPROM.put(0, current_meta); + + // Global user settings + current_meta.encoder_reversed = app.encoder_reversed; + + EEPROM.put(METADATA_START_ADDR, current_meta); interrupts(); } + +void StateManager::_loadMetadata(AppState& app) { + noInterrupts(); + Metadata metadata; + EEPROM.get(METADATA_START_ADDR, metadata); + app.encoder_reversed = metadata.encoder_reversed; + interrupts(); +} \ No newline at end of file diff --git a/firmware/Gravity/save_state.h b/firmware/Gravity/save_state.h index 29ae845..bda7c4f 100644 --- a/firmware/Gravity/save_state.h +++ b/firmware/Gravity/save_state.h @@ -59,6 +59,8 @@ class StateManager { struct Metadata { byte version; char sketch_name[16]; + // Additional global/hardware settings + bool encoder_reversed; }; struct ChannelState { byte base_clock_mod_index; @@ -85,7 +87,8 @@ class StateManager { private: bool _isDataValid(); - void _saveMetadata(); + void _saveMetadata(const AppState& app); + void _loadMetadata(AppState& app); void _saveState(const AppState& app, byte slot_index); void _loadState(AppState& app, byte slot_index);