Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display Menu (Buttons & Rotary Input) #498

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
3 changes: 3 additions & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ lib_deps =
git+https://github.com/esphome/AsyncTCP @ 2.1.3
git+https://github.com/esphome/ESPAsyncWebServer#f2a65ff
git+https://github.com/tzapu/WiFiManager @ 2.0.17
git+https://github.com/craftmetrics/esp32-button.git#36c6c0a
git+https://github.com/chris-dot-exe/MonoMenu @ 0.1.2
madhephaestus/ESP32Encoder @ 0.11.5
extra_scripts =
pre:auto_firmware_version.py
pre:run_clangformat.py
Expand Down
2 changes: 2 additions & 0 deletions src/defaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,5 @@ int writeSysParamsToStorage(void);
#define PID_KP_STEAM_MAX 500
#define STANDBY_MODE_TIME_MIN 30
#define STANDBY_MODE_TIME_MAX 120

#define ENCODER_CLICKS_PER_NOTCH 4
4 changes: 4 additions & 0 deletions src/hardware/GPIOPin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,7 @@ void GPIOPin::setType(Type pinType) {
break;
}
}

int GPIOPin::getPinNumber() const {
return pin;
}
6 changes: 6 additions & 0 deletions src/hardware/GPIOPin.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ class GPIOPin {
*/
Type getType() const;

/**
* @brief Returns pin number of this GPIO pin
* @return Pin number of this pin
*/
int getPinNumber() const;

private:
/**
* @brief Set the type of the GPIO pin
Expand Down
7 changes: 4 additions & 3 deletions src/hardware/pinmapping.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
#define PIN_STEAMSWITCH 35
#define PIN_WATERSWITCH 36

#define PIN_ROTARY_DT 4 // Rotary encoder data pin
#define PIN_ROTARY_CLK 3 // Rotary encoder clock pin
#define PIN_ROTARY_SW 5 // Rotary encoder switch
// Menu Input
#define PIN_MENU_OUT_A 4 // Menu Input - Rotary encoder Output A / Data pin or down button
#define PIN_MENU_OUT_B 3 // Menu Input - Rotary encoder Output B / CLK pin or up button
#define PIN_MENU_ENTER 5 // Menu Input - Rotary encoder switch or enter button

// Sensors
#define PIN_TEMPSENSOR 16
Expand Down
27 changes: 27 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,8 @@ Timer logbrew([&]() { LOGF(DEBUG, "(tB,T,hra) --> %5.2f %6.2f %8.2f", (double)(m
// Embedded HTTP Server
#include "embeddedWebserver.h"

#include "menuHandler.h"

enum SectionNames {
sPIDSection,
sTempSection,
Expand Down Expand Up @@ -1644,6 +1646,10 @@ void setup() {
u8g2_prepare();
displayLogo(String("Version "), String(sysVersion));
delay(2000); // caused crash with wifi manager on esp8266, should be ok on esp32

#if FEATURE_MENU == 1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe you could be more specific with the naming:
-> FEATURE_DISPLAY_MENU
-> displayMenuHandler

initMenu(u8g2);
#endif
#endif

// Fallback offline
Expand Down Expand Up @@ -1859,6 +1865,27 @@ void looppid() {
// Check if PID should run or not. If not, set to manual and force output to zero
#if OLED_DISPLAY != 0
printDisplayTimer();

if (menu != nullptr) {
menu->EventHandler();
menu->Loop();
}

if (menu == nullptr || !menu->IsOpen()) {
unsigned long currentMillisDisplay = millis();

if (currentMillisDisplay - previousMillisDisplay >= 100) {
displayShottimer();
}

if (currentMillisDisplay - previousMillisDisplay >= intervalDisplay) {
previousMillisDisplay = currentMillisDisplay;
#if DISPLAYTEMPLATE < 20 // not using vertical template
displayMachineState();
#endif
printScreen(); // refresh display
}
}
#endif

if (machineState == kPidDisabled || machineState == kWaterEmpty || machineState == kSensorError || machineState == kEmergencyStop || machineState == kEepromError || machineState == kStandby || brewPIDDisabled) {
Expand Down
219 changes: 219 additions & 0 deletions src/menuHandler.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@

#pragma once

#include <ESP32Encoder.h>
#include <Menu.h>
#include <button.h>
#include <hardware/pinmapping.h>
#include <icons/menuIcons.h>

enum MENUINPUT {
BUTTONS,
ROTARY,
};

Menu* menu;
GPIOPin* menuEnterPin;
GPIOPin* menuUpPin;
GPIOPin* menuDownPin;
ESP32Encoder encoder;
QueueHandle_t button_events;
button_event_t ev;

int last = 0;

void saveBrewTemp() {
sysParaBrewSetpoint.setStorage(true);
}

void saveSteamTemp() {
sysParaSteamSetpoint.setStorage(true);
}

void savePIDOn() {
sysParaPidOn.setStorage(true);
}

void saveStandby() {
sysParaStandbyModeOn.setStorage(true);
}

void saveStandbyTime() {
sysParaStandbyModeTime.setStorage(true);
}

bool hasBrewControl() {
return FEATURE_BREWCONTROL > 0;
}

bool hasScale() {
return FEATURE_SCALE > 0;
}

bool hasSoftwareDetection() {
return BREWDETECTION_TYPE == 1;
}

void menuInputInit() {
switch (MENU_INPUT) {
case MENUINPUT::BUTTONS:
menuEnterPin = new GPIOPin(PIN_MENU_ENTER, GPIOPin::IN_PULLUP);
menuUpPin = new GPIOPin(PIN_MENU_OUT_A, GPIOPin::IN_PULLUP);
menuDownPin = new GPIOPin(PIN_MENU_OUT_B, GPIOPin::IN_PULLUP);

button_events = pulled_button_init(PIN_BIT(menuEnterPin->getPinNumber()) | PIN_BIT(menuUpPin->getPinNumber()) | PIN_BIT(menuDownPin->getPinNumber()), GPIO_PULLUP_ONLY);
break;
case MENUINPUT::ROTARY:
menuEnterPin = new GPIOPin(PIN_MENU_ENTER, GPIOPin::IN_PULLUP);
menuUpPin = new GPIOPin(PIN_MENU_OUT_A, GPIOPin::IN_PULLUP);
menuDownPin = new GPIOPin(PIN_MENU_OUT_B, GPIOPin::IN_PULLUP);

button_events = pulled_button_init(PIN_BIT(menuEnterPin->getPinNumber()), GPIO_PULLUP_ONLY);

encoder.useInternalWeakPullResistors = puType::up;
encoder.attachFullQuad(PIN_MENU_OUT_A, PIN_MENU_OUT_B);
encoder.setCount(0);

break;
default:
break;
}
}

void initMenu(U8G2& display) {
menu = new Menu(display);

menuInputInit();

/* Main Menu */
menu->AddInputItem("Brew Temp.", "Brew Temperature", "", "°C", BREW_SETPOINT_MIN, BREW_SETPOINT_MAX, saveBrewTemp, brewSetpoint, bitmap_icon_temp, 0.1, 0.5);
menu->AddInputItem("Steam Temp.", "Steam Temperature", "", "°C", STEAM_SETPOINT_MIN, STEAM_SETPOINT_MAX, saveSteamTemp, steamSetpoint, bitmap_icon_steam, 0.1, 0.5);

menu->AddToggleItem("PID", savePIDOn, reinterpret_cast<bool&>(pidON), bitmap_icon_pid);

menu->SetEventHandler([&]() {
if (xQueueReceive(button_events, &ev, 1 / portTICK_PERIOD_MS)) {
if (ev.pin == menuEnterPin->getPinNumber()) {
if (standbyModeRemainingTimeMillis == 0) {
resetStandbyTimer();
display.setPowerSave(0);
pidON = 1;
if (steamON) {
machineState = kSteam;
}
else if (isBrewDetected) {
machineState = kBrew;
}
else {
machineState = kPidDisabled;
}
return;
}
if (ev.event == EventState::STATE_DOWN) {
resetStandbyTimer();
}
menu->Event(EVENT_ENTER, EventState(ev.event));
}
else {
if (MENU_INPUT == MENUINPUT::BUTTONS) {
if (ev.pin == menuUpPin->getPinNumber()) {
resetStandbyTimer();
menu->Event(EVENT_UP, EventState(ev.event));
}
else if (ev.pin == menuDownPin->getPinNumber()) {
resetStandbyTimer();
menu->Event(EVENT_DOWN, EventState(ev.event));
}
}
}
}
if (MENU_INPUT == MENUINPUT::ROTARY) {
int32_t pos = encoder.getCount() / ENCODER_CLICKS_PER_NOTCH;
if (pos > last) {
menu->Event(EVENT_UP, EventState(EventState::STATE_DOWN));
LOG(DEBUG, "Menu: Up\n");
menu->Event(EVENT_UP, EventState(EventState::STATE_UP));
}
else if (pos < last) {
menu->Event(EVENT_DOWN, EventState(EventState::STATE_DOWN));
LOG(DEBUG, "Menu: Down\n");
menu->Event(EVENT_DOWN, EventState(EventState::STATE_UP));
}

last = pos;
}
});

/* Brew Weight & Time */
Menu* weightNTime = new Menu(display);
weightNTime->AddInputItem("Brew by Time", "Brew Time", "", " s", BREW_TIME_MIN, BREW_TIME_MAX, []() { sysParaBrewTime.setStorage(true); }, brewTime, bitmap_icon_clock);
weightNTime->AddInputItem("Brew by Weight", "Brew Weight", "", "g", WEIGHTSETPOINT_MIN, WEIGHTSETPOINT_MAX, []() { sysParaWeightSetpoint.setStorage(true); }, weightSetpoint, bitmap_icon_scale, hasScale());
weightNTime->AddBackItem("Back", bitmap_icon_back);
menu->AddSubMenu("Brew Time & Weight", *weightNTime, hasBrewControl());

/* Preinfusion */
Menu* preInfusion = new Menu(display);
preInfusion->AddInputItem("Preinfusion Pause", "Pause", "", "s", PRE_INFUSION_PAUSE_MIN, PRE_INFUSION_PAUSE_MAX, []() { sysParaPreInfPause.setStorage(true); }, preinfusionPause, 1.0, 2.0, true);
preInfusion->AddInputItem("Preinfusion", "Time", "", "s", PRE_INFUSION_TIME_MIN, PRE_INFUSION_TIME_MAX, []() { sysParaPreInfTime.setStorage(true); }, preinfusion, 1.0, 2.0, true);
preInfusion->AddBackItem("Back", bitmap_icon_back);
menu->AddSubMenu("Preinfusion", *preInfusion, hasBrewControl());
/*
* Maintenance Menu
* */
Menu* maintenanceMenu = new Menu(display);
maintenanceMenu->AddToggleItem("Backflush", reinterpret_cast<bool&>(backflushOn), bitmap_icon_refresh);
maintenanceMenu->AddBackItem("Back", bitmap_icon_back);

menu->AddSubMenu("Maintenance", *maintenanceMenu, bitmap_icon_tools, hasBrewControl());

/*
* Advanced Menu
*/

Menu* advancedMenu = new Menu(display);
advancedMenu->AddInputItem("Brew Temp. Offset", "Brew temp. offset", "", "°C", BREW_TEMP_OFFSET_MIN, BREW_TEMP_OFFSET_MAX, []() { sysParaTempOffset.setStorage(true); }, brewTempOffset, bitmap_icon_temp);
/*
* Standby Menu
*/
Menu* standbyMenu = new Menu(display);
standbyMenu->AddToggleItem("Standby", saveStandby, reinterpret_cast<bool&>(standbyModeOn), true);
standbyMenu->AddInputItem("Standby Time", "Standby Time", "", " m", STANDBY_MODE_TIME_MIN, STANDBY_MODE_TIME_MAX, saveStandbyTime, standbyModeTime, bitmap_icon_clock, 1.0, 2.0, true);

standbyMenu->AddBackItem("Back", bitmap_icon_back);
advancedMenu->AddSubMenu("Standby", *standbyMenu, bitmap_icon_sleep_mode);

/* PID Settings */
Menu* pidSettings = new Menu(display);
pidSettings->AddToggleItem("Enable PonM", []() { sysParaUsePonM.setStorage(true); }, reinterpret_cast<bool&>(usePonM));
pidSettings->AddInputItem("Start Kp", "Start Kp", "", "", PID_KP_START_MIN, PID_KP_START_MAX, []() { (sysParaPidKpStart.setStorage(true)); }, startKp);
pidSettings->AddInputItem("Start Tn", "Start Tn", "", "", PID_TN_START_MIN, PID_TN_START_MAX, []() { sysParaPidTnStart.setStorage(true); }, startTn);
pidSettings->AddInputItem("Kp", "Kp", "", "", PID_KP_REGULAR_MIN, PID_KP_REGULAR_MAX, []() { sysParaPidKpReg.setStorage(true); }, aggKp);
pidSettings->AddInputItem("Tn", "Tn (=Kp/Ki)", "", "", PID_TN_REGULAR_MIN, PID_TN_REGULAR_MAX, []() { sysParaPidTnReg.setStorage(true); }, aggTn);
pidSettings->AddInputItem("Tv", "Tv (=Kd/Kp)", "", "", PID_TV_REGULAR_MIN, PID_TV_REGULAR_MAX, []() { sysParaPidTvReg.setStorage(true); }, aggTv);
pidSettings->AddInputItem("Integrator Max", "Integrator Max", "", "", PID_I_MAX_REGULAR_MIN, PID_I_MAX_REGULAR_MAX, []() { sysParaPidIMaxReg.setStorage(true); }, aggIMax);
pidSettings->AddInputItem("Steam Kp", "Steam Kp", "", "", PID_KP_STEAM_MIN, PID_KP_STEAM_MAX, []() { sysParaPidKpSteam.setStorage(true); }, steamKp);

/* Brew PID Settings */
Menu* brewPidSettings = new Menu(display);
brewPidSettings->AddToggleItem("Enable Brew PID", []() { sysParaUsePonM.setStorage(true); }, reinterpret_cast<bool&>(useBDPID));
brewPidSettings->AddInputItem("BD Kp", "BD Kp", "", "", PID_KP_BD_MIN, PID_KP_BD_MAX, []() { sysParaPidKpBd.setStorage(true); }, aggbKp);
brewPidSettings->AddInputItem("BD Tn", "BD Tn (=Kp/Ki)", "", "", PID_TN_BD_MIN, PID_TN_BD_MAX, []() { sysParaPidTnBd.setStorage(true); }, aggbTn);
brewPidSettings->AddInputItem("BD Tv", "BD Tv (=Kd/Kp)", "", "", PID_TV_BD_MIN, PID_TV_BD_MAX, []() { sysParaPidTvBd.setStorage(true); }, aggbTv);
brewPidSettings->AddInputItem("PID BD Time", "PID BD Time", "", "s", BREW_SW_TIME_MIN, BREW_SW_TIME_MAX, []() { sysParaBrewSwTime.setStorage(true); }, brewtimesoftware, hasSoftwareDetection());
brewPidSettings->AddInputItem("PID BD Sensitivity", "Sensitivity", "", "", BD_THRESHOLD_MIN, BD_THRESHOLD_MAX, []() { sysParaBrewThresh.setStorage(true); }, brewSensitivity, hasSoftwareDetection());
brewPidSettings->AddBackItem("Back", bitmap_icon_back);

pidSettings->AddSubMenu("Brew PID", *brewPidSettings);
pidSettings->AddBackItem("Back", bitmap_icon_back);

advancedMenu->AddSubMenu("PID Settings", *pidSettings, bitmap_icon_pid);
advancedMenu->AddBackItem("Back", bitmap_icon_back);
menu->AddSubMenu("Advanced", *advancedMenu, bitmap_icon_settings);

menu->AddBackItem("Close Menu", bitmap_icon_back);
menu->Init();
}

void menuLoop() {
menu->Loop();
}
6 changes: 5 additions & 1 deletion src/userConfig_sample.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ enum MACHINE {
#define FEATURE_PIDOFF_LOGO 1 // 0 = deactivated, 1 = activated
#define SHOTTIMERDISPLAYDELAY 3000 // time in ms that shot timer will be shown after brew finished

#define LANGUAGE 0 // LANGUAGE = 0 (DE), LANGUAGE = 1 (EN), LANGUAGE = 2 (ES)
// Display Menu
#define FEATURE_MENU 0 // 0 = deactivated, 1 = enabled
#define MENU_INPUT MENUINPUT::BUTTONS // MENUINPUT::BUTTONS = input with three buttons, MENUINPUT::ROTARY = input with rotary encoder with shaft switch

#define LANGUAGE 0 // LANGUAGE = 0 (DE), LANGUAGE = 1 (EN), LANGUAGE = 2 (ES)

// Connectivity
#define CONNECTMODE 1 // 0 = offline 1 = WIFI-MODE
Expand Down
Loading