mirror of
https://github.com/esphome/esphome.git
synced 2025-10-04 11:02:19 +01:00
Implement a simple LCD menu (#3406)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
74
esphome/components/lcd_menu/__init__.py
Normal file
74
esphome/components/lcd_menu/__init__.py
Normal file
@@ -0,0 +1,74 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_DIMENSIONS,
|
||||
)
|
||||
from esphome.core.entity_helpers import inherit_property_from
|
||||
from esphome.components import lcd_base
|
||||
from esphome.components.display_menu_base import (
|
||||
DISPLAY_MENU_BASE_SCHEMA,
|
||||
DisplayMenuComponent,
|
||||
display_menu_to_code,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@numo68"]
|
||||
|
||||
AUTO_LOAD = ["display_menu_base"]
|
||||
|
||||
lcd_menu_ns = cg.esphome_ns.namespace("lcd_menu")
|
||||
|
||||
CONF_DISPLAY_ID = "display_id"
|
||||
|
||||
CONF_MARK_SELECTED = "mark_selected"
|
||||
CONF_MARK_EDITING = "mark_editing"
|
||||
CONF_MARK_SUBMENU = "mark_submenu"
|
||||
CONF_MARK_BACK = "mark_back"
|
||||
|
||||
MINIMUM_COLUMNS = 12
|
||||
|
||||
LCDCharacterMenuComponent = lcd_menu_ns.class_(
|
||||
"LCDCharacterMenuComponent", DisplayMenuComponent
|
||||
)
|
||||
|
||||
MULTI_CONF = True
|
||||
|
||||
|
||||
def validate_lcd_dimensions(config):
|
||||
if config[CONF_DIMENSIONS][0] < MINIMUM_COLUMNS:
|
||||
raise cv.Invalid(
|
||||
f"LCD display must have at least {MINIMUM_COLUMNS} columns to be usable with the menu"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = DISPLAY_MENU_BASE_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(LCDCharacterMenuComponent),
|
||||
cv.GenerateID(CONF_DISPLAY_ID): cv.use_id(lcd_base.LCDDisplay),
|
||||
cv.Optional(CONF_MARK_SELECTED, default=0x3E): cv.uint8_t,
|
||||
cv.Optional(CONF_MARK_EDITING, default=0x2A): cv.uint8_t,
|
||||
cv.Optional(CONF_MARK_SUBMENU, default=0x7E): cv.uint8_t,
|
||||
cv.Optional(CONF_MARK_BACK, default=0x5E): cv.uint8_t,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.All(
|
||||
inherit_property_from(CONF_DIMENSIONS, CONF_DISPLAY_ID),
|
||||
validate_lcd_dimensions,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
disp = await cg.get_variable(config[CONF_DISPLAY_ID])
|
||||
cg.add(var.set_display(disp))
|
||||
cg.add(var.set_dimensions(config[CONF_DIMENSIONS][0], config[CONF_DIMENSIONS][1]))
|
||||
await display_menu_to_code(var, config)
|
||||
cg.add(var.set_mark_selected(config[CONF_MARK_SELECTED]))
|
||||
cg.add(var.set_mark_editing(config[CONF_MARK_EDITING]))
|
||||
cg.add(var.set_mark_submenu(config[CONF_MARK_SUBMENU]))
|
||||
cg.add(var.set_mark_back(config[CONF_MARK_BACK]))
|
74
esphome/components/lcd_menu/lcd_menu.cpp
Normal file
74
esphome/components/lcd_menu/lcd_menu.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#include "lcd_menu.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace esphome {
|
||||
namespace lcd_menu {
|
||||
|
||||
static const char *const TAG = "lcd_menu";
|
||||
|
||||
void LCDCharacterMenuComponent::setup() {
|
||||
if (this->display_->is_failed()) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
display_menu_base::DisplayMenuComponent::setup();
|
||||
}
|
||||
|
||||
float LCDCharacterMenuComponent::get_setup_priority() const { return setup_priority::PROCESSOR - 1.0f; }
|
||||
|
||||
void LCDCharacterMenuComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "LCD Menu");
|
||||
ESP_LOGCONFIG(TAG, " Columns: %u, Rows: %u", this->columns_, this->rows_);
|
||||
ESP_LOGCONFIG(TAG, " Mark characters: %02x, %02x, %02x, %02x", this->mark_selected_, this->mark_editing_,
|
||||
this->mark_submenu_, this->mark_back_);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "The connected display failed, the menu is disabled!");
|
||||
}
|
||||
}
|
||||
|
||||
void LCDCharacterMenuComponent::draw_item(const display_menu_base::MenuItem *item, uint8_t row, bool selected) {
|
||||
char data[this->columns_ + 1]; // Bounded to 65 through the config
|
||||
|
||||
memset(data, ' ', this->columns_);
|
||||
|
||||
if (selected) {
|
||||
data[0] = (this->editing_ || (this->mode_ == display_menu_base::MENU_MODE_JOYSTICK && item->get_immediate_edit()))
|
||||
? this->mark_editing_
|
||||
: this->mark_selected_;
|
||||
}
|
||||
|
||||
switch (item->get_type()) {
|
||||
case display_menu_base::MENU_ITEM_MENU:
|
||||
data[this->columns_ - 1] = this->mark_submenu_;
|
||||
break;
|
||||
case display_menu_base::MENU_ITEM_BACK:
|
||||
data[this->columns_ - 1] = this->mark_back_;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
auto text = item->get_text();
|
||||
size_t n = std::min(text.size(), (size_t) this->columns_ - 2);
|
||||
memcpy(data + 1, item->get_text().c_str(), n);
|
||||
|
||||
if (item->has_value()) {
|
||||
std::string value = item->get_value_text();
|
||||
|
||||
// Maximum: start mark, at least two chars of label, space, '[', value, ']',
|
||||
// end mark. Config guarantees columns >= 12
|
||||
size_t val_width = std::min((size_t) this->columns_ - 7, value.length());
|
||||
memcpy(data + this->columns_ - val_width - 4, " [", 2);
|
||||
memcpy(data + this->columns_ - val_width - 2, value.c_str(), val_width);
|
||||
data[this->columns_ - 2] = ']';
|
||||
}
|
||||
|
||||
data[this->columns_] = '\0';
|
||||
|
||||
this->display_->print(0, row, data);
|
||||
}
|
||||
|
||||
} // namespace lcd_menu
|
||||
} // namespace esphome
|
45
esphome/components/lcd_menu/lcd_menu.h
Normal file
45
esphome/components/lcd_menu/lcd_menu.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/lcd_base/lcd_display.h"
|
||||
#include "esphome/components/display_menu_base/display_menu_base.h"
|
||||
|
||||
#include <forward_list>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace lcd_menu {
|
||||
|
||||
/** Class to display a hierarchical menu.
|
||||
*
|
||||
*/
|
||||
class LCDCharacterMenuComponent : public display_menu_base::DisplayMenuComponent {
|
||||
public:
|
||||
void set_display(lcd_base::LCDDisplay *display) { this->display_ = display; }
|
||||
void set_dimensions(uint8_t columns, uint8_t rows) {
|
||||
this->columns_ = columns;
|
||||
set_rows(rows);
|
||||
}
|
||||
void set_mark_selected(uint8_t c) { this->mark_selected_ = c; }
|
||||
void set_mark_editing(uint8_t c) { this->mark_editing_ = c; }
|
||||
void set_mark_submenu(uint8_t c) { this->mark_submenu_ = c; }
|
||||
void set_mark_back(uint8_t c) { this->mark_back_ = c; }
|
||||
|
||||
void setup() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
void draw_item(const display_menu_base::MenuItem *item, uint8_t row, bool selected) override;
|
||||
void update() override { this->display_->update(); }
|
||||
|
||||
lcd_base::LCDDisplay *display_;
|
||||
uint8_t columns_;
|
||||
char mark_selected_;
|
||||
char mark_editing_;
|
||||
char mark_submenu_;
|
||||
char mark_back_;
|
||||
};
|
||||
|
||||
} // namespace lcd_menu
|
||||
} // namespace esphome
|
Reference in New Issue
Block a user