Add menu (display + encoder) functional - wip

Added functional menu using display and encoder:
    - a menu item is defined in one struct
    - scroll in a list of defined items
    - select option
    - modify value
    - save value, return to list
Currently only menu is run (button and status display disabled)

- Add menu.cpp/hpp
- Add encoder.cpp/hpp mostly from previous test in board-control
- display.cpp: only run new handleMenu() function
- main.cpp: initialize encoder at startup, disable button task for testing
This commit is contained in:
jonny_jr9 2024-02-13 16:56:31 +01:00
parent f16b96c100
commit fc5aad0c14
7 changed files with 481 additions and 18 deletions

View File

@ -7,6 +7,8 @@ idf_component_register(
"fan.cpp"
"auto.cpp"
"display.cpp"
"menu.cpp"
"encoder.cpp"
INCLUDE_DIRS
"."
)

View File

@ -4,6 +4,8 @@ extern "C"{
#include "esp_ota_ops.h"
}
#include "menu.hpp"
//==== display config ====
@ -219,26 +221,32 @@ void display_task( void * pvParameters ){
// repeatedly update display with content
while (1)
{
//--- fast loop ---
showScreen1();
if (countFastloop >= SLOW_LOOP_INTERVAL / FAST_LOOP_INTERVAL)
{
//--- slow loop ---
//currently only showing menu:
handleMenu(&dev);
if (countSlowLoop >= VERY_SLOW_LOOP_INTERVAL / SLOW_LOOP_INTERVAL)
{
//--- very slow loop ---
// clear display - workaround for bugged line order after a few minutes
countSlowLoop = 0;
ssd1306_clear_screen(&dev, false);
}
countFastloop = 0;
countSlowLoop++;
}
countFastloop++;
vTaskDelay(FAST_LOOP_INTERVAL / portTICK_PERIOD_MS);
// TODO add pages and menus
//status screen currently disabled:
// //--- fast loop ---
// showScreen1();
// if (countFastloop >= SLOW_LOOP_INTERVAL / FAST_LOOP_INTERVAL)
// {
// //--- slow loop ---
// if (countSlowLoop >= VERY_SLOW_LOOP_INTERVAL / SLOW_LOOP_INTERVAL)
// {
// //--- very slow loop ---
// // clear display - workaround for bugged line order after a few minutes
// countSlowLoop = 0;
// ssd1306_clear_screen(&dev, false);
// }
// countFastloop = 0;
// countSlowLoop++;
// }
// countFastloop++;
// vTaskDelay(FAST_LOOP_INTERVAL / portTICK_PERIOD_MS);
// // TODO add pages and menus
}
}

View File

@ -0,0 +1,83 @@
extern "C"
{
#include <stdio.h>
#include <esp_system.h>
#include <esp_event.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "encoder.h"
}
#include "encoder.hpp"
//-------------------------
//------- variables -------
//-------------------------
static const char * TAG = "encoder";
uint16_t encoderCount;
rotary_encoder_btn_state_t encoderButtonState = {};
//global event queue:
QueueHandle_t encoderQueue = NULL;
//encoder config
rotary_encoder_t encoderConfig = {
.pin_a = PIN_A,
.pin_b = PIN_B,
.pin_btn = PIN_BUTTON,
.code = 1,
.store = encoderCount,
.index = 0,
.btn_pressed_time_us = 20000,
.btn_state = encoderButtonState
};
//==================================
//========== encoder_init ==========
//==================================
//initialize encoder
void encoder_init(){
encoderQueue = xQueueCreate(QUEUE_SIZE, sizeof(rotary_encoder_event_t));
rotary_encoder_init(encoderQueue);
rotary_encoder_add(&encoderConfig);
}
//==================================
//====== task_encoderExample =======
//==================================
//receive and handle all available encoder events
void task_encoderExample(void *arg) {
static rotary_encoder_event_t ev; //store event data
while (1) {
if (xQueueReceive(encoderQueue, &ev, portMAX_DELAY)) {
//log enocder events
switch (ev.type){
case RE_ET_CHANGED:
ESP_LOGI(TAG, "Event type: RE_ET_CHANGED, diff: %d", ev.diff);
break;
case RE_ET_BTN_PRESSED:
ESP_LOGI(TAG, "Button pressed");
break;
case RE_ET_BTN_RELEASED:
ESP_LOGI(TAG, "Button released");
break;
case RE_ET_BTN_CLICKED:
ESP_LOGI(TAG, "Button clicked");
break;
case RE_ET_BTN_LONG_PRESSED:
ESP_LOGI(TAG, "Button long-pressed");
break;
default:
ESP_LOGW(TAG, "Unknown event type");
break;
}
}
}
}

View File

@ -0,0 +1,20 @@
extern "C" {
#include "freertos/FreeRTOS.h" // FreeRTOS related headers
#include "freertos/task.h"
#include "encoder.h"
}
//config
#define QUEUE_SIZE 10
#define PIN_A GPIO_NUM_25
#define PIN_B GPIO_NUM_26
#define PIN_BUTTON GPIO_NUM_27
//global encoder queue
extern QueueHandle_t encoderQueue;
//init encoder with config in encoder.cpp
void encoder_init();
//task that handles encoder events
void task_encoderExample(void *arg);

View File

@ -29,6 +29,7 @@ extern "C"
#include "uart_common.hpp"
#include "display.hpp"
#include "encoder.hpp"
//tag for logging
static const char * TAG = "main";
@ -155,6 +156,7 @@ void setLoglevels(void){
//esp_log_level_set("current-sensors", ESP_LOG_INFO);
//esp_log_level_set("speedSensor", ESP_LOG_INFO);
esp_log_level_set("chair-adjustment", ESP_LOG_INFO);
esp_log_level_set("menu", ESP_LOG_INFO);
}
@ -173,6 +175,11 @@ extern "C" void app_main(void) {
//---- define log levels ----
setLoglevels();
// init encoder
//--- initialize encoder ---
encoder_init();
// now global encoderQueue providing all encoder events is available
//----------------------------------------------
//--- create task for controlling the motors ---
//----------------------------------------------
@ -194,7 +201,11 @@ extern "C" void app_main(void) {
//--- create task for button ---
//------------------------------
//task that evaluates and processes the button input and runs the configured commands
#define MENU_TEST
//currently disabled due to using button/encoder for testing the menu
#ifndef MENU_TEST
xTaskCreate(&task_button, "task_button", 4096, NULL, 4, NULL);
#endif
//-----------------------------------
//--- create task for fan control ---

307
board_single/main/menu.cpp Normal file
View File

@ -0,0 +1,307 @@
extern "C"{
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_log.h"
#include "ssd1306.h"
}
#include "menu.hpp"
#include "encoder.hpp"
//--- variables ---
static const char *TAG = "menu";
static menuState_t menuState = MAIN_MENU;
static int value = 0;
//================================
//===== CONFIGURE MENU ITEMS =====
//================================
// note: when line4 * and line5 * are empty the value is printed large
void maxDuty_action(int value)
{
//TODO actually store the value
ESP_LOGW(TAG, "set max duty to %d", value);
}
int maxDuty_currentValue()
{
//TODO get real current value
return 84;
}
menuItem_t item_first = {
maxDuty_action, // function action
maxDuty_currentValue,
-255, // valueMin
255, // valueMAx
2, // valueIncrement
"first item", // 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_maxDuty = {
maxDuty_action, // function action
maxDuty_currentValue,
1, // valueMin
99, // valueMAx
1, // valueIncrement
"set max duty", // title
"", // line1 (above value)
" set max-duty: ", // line2 (above value)
"", // line4 * (below value)
"", // line5 *
" 1-99 ", // line6
" percent ", // line7
};
menuItem_t item_next = {
maxDuty_action, // function action
maxDuty_currentValue,
-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
};
menuItem_t item_last = {
maxDuty_action, // function action
maxDuty_currentValue,
0, // valueMin
100, // valueMAx
5, // valueIncrement
"last item", // title
"", // line1 (above value)
"", // line2 (above value)
"", // line4 * (below value)
"", // line5 *
"", // line6
"", // line7
};
//store all configured menu items in one array
menuItem_t menuItems[] = {item_first, item_maxDuty, item_next, item_last};
int itemCount = 4;
//--------------------------
//------ showItemList ------
//--------------------------
#define SELECTED_ITEM_LINE 4
#define FIRST_ITEM_LINE 1
#define LAST_ITEM_LINE 7
void showItemList(SSD1306_t *display, int selectedItem)
{
//--- variables ---
char buf[20];
int len;
//-- show title line --
len = snprintf(buf, 19, " --- menu --- ");
ssd1306_display_text(display, 0, buf, len, true);
//-- show item list --
for (int line = FIRST_ITEM_LINE; line <= LAST_ITEM_LINE; line++)
{ // loop through all lines
int printItemIndex = selectedItem - SELECTED_ITEM_LINE + line;
// TODO: when reaching end of items, instead of showing "empty" change position of selected item to still use the entire screen
if (printItemIndex < 0 || printItemIndex >= itemCount) // out of range of available items
{
len = snprintf(buf, 20, " -- empty -- ");
}
else
{
if (printItemIndex == selectedItem)
{
// add '> <' when selected item
len = snprintf(buf, 20, "> %-13s<", menuItems[printItemIndex].title);
}
else
{
len = snprintf(buf, 20, "%-16s", menuItems[printItemIndex].title);
}
}
// update display line
ssd1306_display_text(display, line, buf, len, line == SELECTED_ITEM_LINE);
// logging
ESP_LOGD(TAG, "showItemList: loop - line=%d, item=%d, (selected=%d '%s')", line, printItemIndex, selectedItem, menuItems[selectedItem].title);
}
}
//---------------------------
//----- showValueSelect -----
//---------------------------
//TODO show previous value in one line?
void showValueSelect(SSD1306_t *display, int selectedItem)
{
//--- variables ---
char buf[20];
int len;
//-- show title line --
len = snprintf(buf, 19, " -- set value -- ");
ssd1306_display_text(display, 0, buf, len, true);
//-- show text above value --
len = snprintf(buf, 20, "%-16s", menuItems[selectedItem].line1);
ssd1306_display_text(display, 1, buf, len, false);
len = snprintf(buf, 20, "%-16s", menuItems[selectedItem].line2);
ssd1306_display_text(display, 2, buf, len, false);
//-- show value and other configured lines --
// 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
len = snprintf(buf, 20, " %d ", value);
ssd1306_display_text_x3(display, 3, buf, len, false);
len = snprintf(buf, 20, "%-16s", menuItems[selectedItem].line6);
ssd1306_display_text(display, 6, buf, len, false);
len = snprintf(buf, 20, "%-16s", menuItems[selectedItem].line7);
ssd1306_display_text(display, 7, buf, len, false);
}
else
{
// print value centered
int numDigits = snprintf(NULL, 0, "%d", value);
int numSpaces = (16 - numDigits) / 2;
snprintf(buf, sizeof(buf), "%*s%d%*s", numSpaces, "", value, 16 - numSpaces - numDigits, "");
ESP_LOGD(TAG, "showValueSelect: center number - value=%d, needed-spaces=%d, resulted-string='%s'", value, numSpaces, buf);
ssd1306_display_text(display, 3, buf, len, false);
// print description lines 4 to 7
len = snprintf(buf, 20, "%-16s", menuItems[selectedItem].line4);
ssd1306_display_text(display, 4, buf, len, false);
len = snprintf(buf, 20, "%-16s", menuItems[selectedItem].line5);
ssd1306_display_text(display, 5, buf, len, false);
len = snprintf(buf, 20, "%-16s", menuItems[selectedItem].line6);
ssd1306_display_text(display, 6, buf, len, false);
len = snprintf(buf, 20, "%-16s", menuItems[selectedItem].line7);
ssd1306_display_text(display, 7, buf, len, false);
}
}
//========================
//====== handleMenu ======
//========================
//controls menu with encoder input and displays the text on oled display
//function is repeatedly called when in menu state
void handleMenu(SSD1306_t *display)
{
static int selectedItem = 0;
rotary_encoder_event_t event; // store event data
switch (menuState)
{
//-------------------------
//---- State MAIN MENU ----
//-------------------------
case MAIN_MENU:
// update display
showItemList(display, selectedItem); // shows list of items with currently selected one on display
// wait for encoder event
if (xQueueReceive(encoderQueue, &event, portMAX_DELAY))
{
switch (event.type)
{
case RE_ET_CHANGED:
//--- scroll in list ---
if (event.diff < 0)
{
if (selectedItem != itemCount - 1)
selectedItem++;
ESP_LOGD(TAG, "showing next item: %d '%s'", selectedItem, menuItems[selectedItem].title);
//note: display will update at start of next run
}
else
{
if (selectedItem != 0)
selectedItem--;
ESP_LOGD(TAG, "showing previous item: %d '%s'", selectedItem, menuItems[selectedItem].title);
//note: display will update at start of next run
}
break;
case RE_ET_BTN_PRESSED:
//--- switch to edit value page ---
ESP_LOGI(TAG, "Button pressed - switching to state SET_VALUE");
// change state (menu to set value)
menuState = SET_VALUE;
// get currently configured value
value = menuItems[selectedItem].currentValue();
// clear display
ssd1306_clear_screen(display, false);
break;
case RE_ET_BTN_RELEASED:
case RE_ET_BTN_CLICKED:
case RE_ET_BTN_LONG_PRESSED:
break;
}
}
break;
//-------------------------
//---- State SET VALUE ----
//-------------------------
case SET_VALUE:
// wait for encoder event
if (xQueueReceive(encoderQueue, &event, portMAX_DELAY))
{
switch (event.type)
{
case RE_ET_CHANGED:
//-- change value --
// increment value
if (event.diff < 0)
value += menuItems[selectedItem].valueIncrement;
else
value -= menuItems[selectedItem].valueIncrement;
// limit to min/max range
if (value > menuItems[selectedItem].valueMax)
value = menuItems[selectedItem].valueMax;
if (value < menuItems[selectedItem].valueMin)
value = menuItems[selectedItem].valueMin;
break;
case RE_ET_BTN_PRESSED:
//-- apply value --
ESP_LOGI(TAG, "Button pressed - running action function with value=%d for item '%s'", value, menuItems[selectedItem].title);
menuItems[selectedItem].action(value);
menuState = MAIN_MENU;
break;
case RE_ET_BTN_RELEASED:
case RE_ET_BTN_CLICKED:
case RE_ET_BTN_LONG_PRESSED:
break;
}
}
showValueSelect(display, selectedItem);
break;
}
}

View File

@ -0,0 +1,32 @@
#pragma once
#include "display.hpp"
//--- menuState_t ---
// modes the menu can be in
typedef enum {
MAIN_MENU = 0,
SET_VALUE
} menuState_t;
//--- menuItem_t ---
// struct describes one menu element (all defined in menu.cpp)
typedef struct {
void (*action)(int); // pointer to function run when confirmed
int (*currentValue)(); // 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 (+/-)
const char title[17]; // shown in list
const char line1[17]; // above value
const char line2[17]; // above value
const char line4[17]; // below value *
const char line5[17]; // below value *
const char line6[17]; // below value
const char line7[17]; // below value
} menuItem_t;
void handleMenu(SSD1306_t * display);