From c9c371a74244f50b3b11ab9b5f91bfe5a2013277 Mon Sep 17 00:00:00 2001
From: jonny_jr9 <jonny@wwad.de>
Date: Tue, 20 Feb 2024 17:27:40 +0100
Subject: [PATCH] Add default-value to Menu, Optimize value-screen, Add Beeping

menu:
    - optimize set-value page: send static content only once
      so only update value -> significant performance boost
    - formatting
    - reset control timeout to prevent unintended exit and bugged display
    - menu item: add option to show a default value (function ptr)
    - add default value to adjust fading
    - add beeping

motorctl:
    - dont write to nvs when value is unchanged
---
 board_single/main/menu.cpp | 260 +++++++++++++++++++++++--------------
 board_single/main/menu.hpp |   1 +
 common/motorctl.cpp        |  12 +-
 3 files changed, 176 insertions(+), 97 deletions(-)

diff --git a/board_single/main/menu.cpp b/board_single/main/menu.cpp
index 622b675..ba64bf2 100644
--- a/board_single/main/menu.cpp
+++ b/board_single/main/menu.cpp
@@ -25,7 +25,9 @@ static int value = 0;
 //================================
 //===== CONFIGURE MENU ITEMS =====
 //================================
-// note: when line4 * and line5 * are empty the value is printed large
+// Instructions / Behavior:
+// - when line4 * and line5 * are empty the value is printed large
+// - when 3rd element is not NULL (pointer to defaultValue function) that return value is shown in line 2
 
 //#########################
 //#### center Joystick ####
@@ -33,9 +35,8 @@ static int value = 0;
 void item_centerJoystick_action(display_task_parameters_t * objects, SSD1306_t * display, int value){
     if (!value) return;
     ESP_LOGW(TAG, "defining joystick center");
-    (*objects).joystick->defineCenter();
-    //objects->joystick->defineCenter();
-    //joystick->defineCenter();
+    objects->joystick->defineCenter();
+    objects->buzzer->beep(3, 60, 40);
 }
 int item_centerJoystick_value(display_task_parameters_t * objects){
     return 1;
@@ -43,17 +44,18 @@ int item_centerJoystick_value(display_task_parameters_t * objects){
 
 menuItem_t item_centerJoystick = {
     item_centerJoystick_action, // function action
-    item_centerJoystick_value,
-    0,                      // valueMin
-    1,                      // valueMAx
-    1,                      // valueIncrement
-    "Center Joystick",      // title
-    "Center Joystick", // line1 (above value)
-    "click to confirm",   // line2 (above value)
-    "defines current",                     // line4 * (below value)
-    "pos as center",                     // line5 *
-    "click to confirm",     // line6
-    "set 0 to cancel",  // line7
+    item_centerJoystick_value,  // function get initial value
+    NULL,                       // function get default value or NULL
+    0,                          // valueMin
+    1,                          // valueMAx
+    1,                          // valueIncrement
+    "Center Joystick",          // title
+    "Center Joystick",          // line1 (above value)
+    "click to confirm",         // line2 (above value)
+    "defines current",          // line4 * (below value)
+    "pos as center",            // line5 *
+    "click to confirm",         // line6
+    "set 0 to cancel",          // line7
 };
 
 
@@ -61,6 +63,7 @@ menuItem_t item_centerJoystick = {
 //#### debug Joystick ####
 //########################
 //continously show/update joystick data on display
+#define DEBUG_JOYSTICK_UPDATE_INTERVAL 50
 void item_debugJoystick_action(display_task_parameters_t * objects, SSD1306_t * display, int value)
 {
     //--- variables ---
@@ -90,8 +93,9 @@ void item_debugJoystick_action(display_task_parameters_t * objects, SSD1306_t *
         displayTextLine(display, 5, false, false, "pos=%-12s ", joystickPosStr[(int)data.position]);
 
         // exit when button pressed
-        if (xQueueReceive(objects->encoderQueue, &event, 20 / portTICK_PERIOD_MS))
+        if (xQueueReceive(objects->encoderQueue, &event, DEBUG_JOYSTICK_UPDATE_INTERVAL / portTICK_PERIOD_MS))
         {
+            objects->control->resetTimeout();
             switch (event.type)
             {
             case RE_ET_BTN_CLICKED:
@@ -113,17 +117,18 @@ int item_debugJoystick_value(display_task_parameters_t * objects){
 
 menuItem_t item_debugJoystick = {
     item_debugJoystick_action, // function action
-    item_debugJoystick_value,
-    0,                      // valueMin
-    1,                      // valueMAx
-    1,                      // valueIncrement
-    "Debug joystick",      // title
-    "Debug joystick", // line1 (above value)
-    "",   // line2 (above value)
-    "click to enter",                     // line4 * (below value)
-    "debug screen",                     // line5 *
-    "prints values",     // line6
-    "set 0 to cancel",  // line7
+    item_debugJoystick_value,  // function get initial value
+    NULL,                      // function get default value or NULL
+    0,                         // valueMin
+    1,                         // valueMAx
+    1,                         // valueIncrement
+    "Debug joystick",          // title
+    "Debug joystick",          // line1 (above value)
+    "",                        // line2 (above value)
+    "click to enter",          // line4 * (below value)
+    "debug screen",            // line5 *
+    "prints values",           // line6
+    "set 0 to cancel",         // line7
 };
 
 
@@ -141,20 +146,22 @@ int maxDuty_currentValue(display_task_parameters_t * objects)
     return 84;
 }
 menuItem_t item_maxDuty = {
-    maxDuty_action, // function action
-    maxDuty_currentValue,
-    1,                  // valueMin
-    99,                 // valueMAx
-    1,                  // valueIncrement
-    "max duty",     // title
-    "",                 // line1 (above value)
-    "  set max-duty: ", // line2 (above value)
-    "",                 // line4 * (below value)
-    "",                 // line5 *
-    "      1-99      ", // line6
-    "     percent    ", // line7
+    maxDuty_action,       // function action
+    maxDuty_currentValue, // function get initial value
+    NULL,                 // function get default value or NULL
+    1,                    // valueMin
+    99,                   // valueMAx
+    1,                    // valueIncrement
+    "max duty",           // title
+    "",                   // line1 (above value)
+    "  set max-duty: ",   // line2 (above value)
+    "",                   // line4 * (below value)
+    "",                   // line5 *
+    "      1-99      ",   // line6
+    "     percent    ",   // line7
 };
 
+
 //######################
 //##### accelLimit #####
 //######################
@@ -167,21 +174,27 @@ int item_accelLimit_value(display_task_parameters_t * objects)
 {
     return objects->motorLeft->getFade(fadeType_t::ACCEL);
 }
+int item_accelLimit_default(display_task_parameters_t * objects)
+{
+    return objects->motorLeft->getFadeDefault(fadeType_t::ACCEL);
+}
 menuItem_t item_accelLimit = {
-    item_accelLimit_action, // function action
-    item_accelLimit_value,
-    0,                // valueMin
-    10000,            // valueMAx
-    100,              // valueIncrement
-    "Accel limit",    // title
-    "Accel limit /",  // line1 (above value)
-    "Fade up time",   // line2 (above value)
-    "",               // line4 * (below value)
-    "",               // line5 *
-    "milliseconds",   // line6
-    "from 0 to 100%", // line7
+    item_accelLimit_action,  // function action
+    item_accelLimit_value,   // function get initial value
+    item_accelLimit_default, // function get default value or NULL
+    0,                       // valueMin
+    10000,                   // valueMAx
+    100,                     // valueIncrement
+    "Accel limit",           // title
+    " Fade up time",       // line1 (above value)
+    "",                      // line2 <= showing "default = %d"
+    "",                      // line4 * (below value)
+    "",                      // line5 *
+    "milliseconds",          // line6
+    "from 0 to 100%",        // line7
 };
 
+
 // ######################
 // ##### decelLimit #####
 // ######################
@@ -194,21 +207,27 @@ int item_decelLimit_value(display_task_parameters_t * objects)
 {
     return objects->motorLeft->getFade(fadeType_t::DECEL);
 }
+int item_decelLimit_default(display_task_parameters_t * objects)
+{
+    return objects->motorLeft->getFadeDefault(fadeType_t::DECEL);
+}
 menuItem_t item_decelLimit = {
-    item_decelLimit_action, // function action
-    item_decelLimit_value,
-    0,                // valueMin
-    10000,            // valueMAx
-    100,              // valueIncrement
-    "Decel limit",    // title
-    "Decel limit /",  // line1 (above value)
-    "Fade down time", // line2 (above value)
-    "",               // line4 * (below value)
-    "",               // line5 *
-    "milliseconds",   // line6
-    "from 100 to 0%", // line7
+    item_decelLimit_action,  // function action
+    item_decelLimit_value,   // function get initial value
+    item_decelLimit_default, // function get default value or NULL
+    0,                       // valueMin
+    10000,                   // valueMAx
+    100,                     // valueIncrement
+    "Decel limit",           // title
+    " Fade down time",      // line1 (above value)
+    "",                      // line2 <= showing "default = %d"
+    "",                      // line4 * (below value)
+    "",                      // line5 *
+    "milliseconds",          // line6
+    "from 100 to 0%",        // line7
 };
 
+
 //#####################
 //###### example ######
 //#####################
@@ -217,42 +236,47 @@ void item_example_action(display_task_parameters_t * objects, SSD1306_t * displa
     return;
 }
 int item_example_value(display_task_parameters_t * objects){
-    return 53;
+    return 53; //initial value shown / changed from
+}
+int item_example_valueDefault(display_task_parameters_t * objects){
+    return 931; // optionally shown in line 2 as "default = %d"
 }
 menuItem_t item_example = {
     item_example_action, // function action
-    item_example_value,
-    -255,             // valueMin
-    255,              // valueMAx
-    2,                // valueIncrement
-    "example-item-max",     // title
-    "line 1 - above", // line1 (above value)
-    "line 2 - above", // line2 (above value)
-    "line 4 - below", // line4 * (below value)
-    "line 5 - below", // line5 *
-    "line 6 - below", // line6
-    "line 7 - last",  // line7
+    item_example_value,  // function get initial value
+    NULL,                // function get default value or NULL
+    -255,                // valueMin
+    255,                 // valueMAx
+    2,                   // valueIncrement
+    "example-item-max",  // title
+    "line 1 - above",    // line1 (above value)
+    "line 2 - above",    // line2 (above value)
+    "line 4 - below",    // line4 * (below value)
+    "line 5 - below",    // line5 *
+    "line 6 - below",    // line6
+    "line 7 - last",     // line7
 };
 
 menuItem_t item_last = {
     item_example_action, // function action
-    item_example_value,
-    -500,               // valueMin
-    4500,               // valueMAx
-    50,                 // valueIncrement
-    "set large number", // title
-    "line 1 - above",   // line1 (above value)
-    "line 2 - above",   // line2 (above value)
-    "",                 // line4 * (below value)
-    "",                 // line5 *
-    "line 6 - below",   // line6
-    "line 7 - last",    // line7
+    item_example_value,  // function get initial value
+    item_example_valueDefault, // function get default value or NULL
+    -500,                // valueMin
+    4500,                // valueMAx
+    50,                  // valueIncrement
+    "set large number",  // title
+    "line 1 - above",    // line1 (above value)
+    "line 2 - above",    // line2 (above value)
+    "",                  // line4 * (below value)
+    "",                  // line5 *
+    "line 6 - below",    // line6
+    "line 7 - last",     // line7
 
 };
 
 //store all configured menu items in one array
-menuItem_t menuItems[] = {item_centerJoystick, item_debugJoystick, item_accelLimit, item_decelLimit, item_example, item_last};
-int itemCount = 6;
+const menuItem_t menuItems[] = {item_centerJoystick, item_debugJoystick, item_accelLimit, item_decelLimit, item_example, item_last};
+const int itemCount = 6;
 
 
 
@@ -298,21 +322,28 @@ void showItemList(SSD1306_t *display, int selectedItem)
     }
 }
 
-//---------------------------
-//----- showValueSelect -----
-//---------------------------
+//-----------------------------
+//--- showValueSelectStatic ---
+//-----------------------------
 // function that renders value-select screen to display (one update)
 // shows configured text of selected item and currently selected value
 // TODO show previous value in one line?
-// TODO update changed line only (value)
-void showValueSelect(SSD1306_t *display, int selectedItem)
+void showValueSelectStatic(display_task_parameters_t * objects, SSD1306_t *display, int selectedItem)
 {
     //-- show title line --
     displayTextLine(display, 0, false, true, " -- set value -- "); // inverted
 
     //-- show text above value --
     displayTextLine(display, 1, false, false, "%-16s", menuItems[selectedItem].line1);
+
+//-- show line 2 or default value ---
+    if (menuItems[selectedItem].defaultValue != NULL){
+    displayTextLineCentered(display, 2, false, false, "default = %d", menuItems[selectedItem].defaultValue(objects));
+    } 
+    else{
+    //displayTextLine(display, 2, false, false, "previous=%d", menuItems[selectedItem].currentValue(objects)); // <= show previous value
     displayTextLine(display, 2, false, false, "%-16s", menuItems[selectedItem].line2);
+}
 
     //-- show value and other configured lines --
     // print value large, if 2 description lines are empty
@@ -334,6 +365,24 @@ void showValueSelect(SSD1306_t *display, int selectedItem)
     }
 }
 
+//-----------------------------
+//----- updateValueSelect -----
+//-----------------------------
+// update line with currently set value only (increses performance significantly)
+void updateValueSelect(SSD1306_t *display, int selectedItem)
+{
+    // print value large, if 2 description lines are empty
+    if (strlen(menuItems[selectedItem].line4) == 0 && strlen(menuItems[selectedItem].line5) == 0)
+    {
+        // print large value + line5 and line6
+        displayTextLineCentered(display, 3, true, false, "%d", value); //large centered
+    }
+    else
+    {
+        displayTextLineCentered(display, 3, false, false, "%d", value); //centered
+    }
+}
+
 
 
 
@@ -343,11 +392,12 @@ void showValueSelect(SSD1306_t *display, int selectedItem)
 //controls menu with encoder input and displays the text on oled display
 //function is repeatedly called by display task when in menu state
 #define QUEUE_TIMEOUT 3000 //timeout no encoder event - to handle timeout and not block the display loop
-#define MENU_TIMEOUT 60000 //inactivity timeout (switch to IDLE mode)
+#define MENU_TIMEOUT 60000 //inactivity timeout (switch to IDLE mode) note: should be smaller than IDLE timeout in control task
 void handleMenu(display_task_parameters_t * objects, SSD1306_t *display)
 {
     static uint32_t lastActivity = 0;
     static int selectedItem = 0;
+    static bool staticContentUpdated = false;
     rotary_encoder_event_t event; // store event data
 
     //--- handle different menu states ---
@@ -363,10 +413,12 @@ void handleMenu(display_task_parameters_t * objects, SSD1306_t *display)
         if (xQueueReceive(objects->encoderQueue, &event, QUEUE_TIMEOUT / portTICK_PERIOD_MS))
         {
             lastActivity = esp_log_timestamp();
+            objects->control->resetTimeout();
             switch (event.type)
             {
             case RE_ET_CHANGED:
                 //--- scroll in list ---
+                objects->buzzer->beep(1, 10, 5);
                 if (event.diff < 0)
                 {
                     if (selectedItem != itemCount - 1)
@@ -385,9 +437,11 @@ void handleMenu(display_task_parameters_t * objects, SSD1306_t *display)
 
             case RE_ET_BTN_CLICKED:
                 //--- switch to edit value page ---
+                objects->buzzer->beep(1, 50, 10);
                 ESP_LOGI(TAG, "Button pressed - switching to state SET_VALUE");
                 // change state (menu to set value)
                 menuState = SET_VALUE;
+                staticContentUpdated = false;
                 // get currently configured value
                 value = menuItems[selectedItem].currentValue(objects);
                 // clear display
@@ -397,6 +451,7 @@ void handleMenu(display_task_parameters_t * objects, SSD1306_t *display)
             //exit menu mode
             case RE_ET_BTN_LONG_PRESSED:
                 //change to previous mode (e.g. JOYSTICK)
+                objects->buzzer->beep(4, 15, 5);
                 objects->control->toggleMode(controlMode_t::MENU); //currently already in MENU -> changes to previous mode
                 ssd1306_clear_screen(display, false);
                 break;
@@ -413,15 +468,25 @@ void handleMenu(display_task_parameters_t * objects, SSD1306_t *display)
         //-------------------------
     case SET_VALUE:
         // wait for encoder event
-        showValueSelect(display, selectedItem);
+        if (!staticContentUpdated)
+        {
+            showValueSelectStatic(objects, display, selectedItem);
+            staticContentUpdated = true;
+        }
+        else
+        {
+            // update line with currently set value only (increses performance significantly)
+            updateValueSelect(display, selectedItem);
+        }
 
         if (xQueueReceive(objects->encoderQueue, &event, QUEUE_TIMEOUT / portTICK_PERIOD_MS))
         {
-            lastActivity = esp_log_timestamp();
+            objects->control->resetTimeout();
             switch (event.type)
             {
             case RE_ET_CHANGED:
                 //-- change value --
+                objects->buzzer->beep(1, 25, 10);
                 // increment value
                 if (event.diff < 0)
                     value += menuItems[selectedItem].valueIncrement;
@@ -436,6 +501,7 @@ void handleMenu(display_task_parameters_t * objects, SSD1306_t *display)
             case RE_ET_BTN_CLICKED:
                 //-- apply value --
                 ESP_LOGI(TAG, "Button pressed - running action function with value=%d for item '%s'", value, menuItems[selectedItem].title);
+                objects->buzzer->beep(2, 50, 50);
                 menuItems[selectedItem].action(objects, display, value);
                 menuState = MAIN_MENU;
                 break;
@@ -444,6 +510,7 @@ void handleMenu(display_task_parameters_t * objects, SSD1306_t *display)
             case RE_ET_BTN_LONG_PRESSED:
                 break;
             }
+            lastActivity = esp_log_timestamp();
         }
         break;
     }
@@ -455,6 +522,7 @@ void handleMenu(display_task_parameters_t * objects, SSD1306_t *display)
     //close menu and switch to IDLE mode when no encoder event within MENU_TIMEOUT
     if (esp_log_timestamp() - lastActivity > MENU_TIMEOUT)
     {
+        objects->buzzer->beep(1, 500, 10);
         ESP_LOGW(TAG, "TIMEOUT - no activity for more than %ds -> closing menu, switching to IDLE", MENU_TIMEOUT/1000);
         // reset menu
         selectedItem = 0;
diff --git a/board_single/main/menu.hpp b/board_single/main/menu.hpp
index 96534ea..b7682ec 100644
--- a/board_single/main/menu.hpp
+++ b/board_single/main/menu.hpp
@@ -17,6 +17,7 @@ typedef struct
 {
     void (*action)(display_task_parameters_t * objects, SSD1306_t * display, int value);   // pointer to function run when confirmed
     int (*currentValue)(display_task_parameters_t * objects); // pointer to function to get currently configured value
+    int (*defaultValue)(display_task_parameters_t * objects); // pointer to function to get currently configured value
     int valueMin;          // min allowed value
     int valueMax;          // max allowed value
     int valueIncrement;    // amount changed at one encoder tick (+/-)
diff --git a/common/motorctl.cpp b/common/motorctl.cpp
index 5083bc8..ef2dade 100644
--- a/common/motorctl.cpp
+++ b/common/motorctl.cpp
@@ -351,7 +351,7 @@ void controlledMotor::setFade(fadeType_t fadeType, uint32_t msFadeNew){
             break;
         case fadeType_t::DECEL:
             ESP_LOGW(TAG, "[%s] changed fade-down time from %d to %d",config.name, msFadeDecel, msFadeNew);
-            msFadeDecel = msFadeNew;
+            // write new value to nvs and update the variable
             writeDecelDuration(msFadeNew);
             break;
     }
@@ -474,6 +474,11 @@ void controlledMotor::loadDecelDuration(void)
 // write provided value to nvs to be persistent and update the local variable msFadeAccel
 void controlledMotor::writeAccelDuration(uint32_t newValue)
 {
+    // check if unchanged
+    if(msFadeAccel == newValue){
+        ESP_LOGW(TAG, "value unchanged at %d, not writing to nvs", newValue);
+        return;
+    }
     // generate nvs storage key
     char key[15];
     snprintf(key, 15, "m-%s-accel", config.name);
@@ -498,6 +503,11 @@ void controlledMotor::writeAccelDuration(uint32_t newValue)
 // TODO: reduce duplicate code
 void controlledMotor::writeDecelDuration(uint32_t newValue)
 {
+    // check if unchanged
+    if(msFadeDecel == newValue){
+        ESP_LOGW(TAG, "value unchanged at %d, not writing to nvs", newValue);
+        return;
+    }
     // generate nvs storage key
     char key[15];
     snprintf(key, 15, "m-%s-decel", config.name);