########################################### ## ## ## filename: base.yaml ## ## description: contains configuration ## ## that will be the same for all the ## ## possible configurations ## ## ## ########################################### esp32: flash_size: 16MB interval: - interval: 1min then: - logger.log: "ESPHome for Button+ v0.1.1" i2c: - id: temperature_sensor sda: GPIO1 scl: GPIO2 - id: buttons sda: GPIO47 scl: GPIO48 frequency: 400khz spi: clk_pin: GPIO36 mosi_pin: GPIO35 globals: - id: nr_of_pages type: int initial_value: '1' - id: actual_page type: int initial_value: '0' output: - platform: ledc pin: GPIO3 id: display_main_backlight_pwm inverted: True max_power: 1.0 frequency: 500Hz - platform: ledc pin: GPIO46 id: displays_mini_backlight_pwm inverted: True max_power: 0.6 frequency: 500Hz sensor: - platform: sts3x id: inside_temperature i2c_id: temperature_sensor name: "Temperature" address: 0x4A update_interval: 10s - platform: template name: "Active Page ID" id: active_page_id_ha update_interval: never text_sensor: - platform: template name: "Active Page Name" id: active_page_name_ha update_interval: never light: - platform: neopixelbus id: neopixels type: RGB variant: WS2812 pin: GPIO4 num_leds: 17 - platform: monochromatic output: display_main_backlight_pwm gamma_correct : 2.2 name: "Main Display Backlight" id: display_main_backlight restore_mode: ALWAYS_ON - platform: monochromatic output: displays_mini_backlight_pwm gamma_correct : 2.2 name: "Mini Display Backlight" id: displays_mini_backlight restore_mode: ALWAYS_ON time: - platform: homeassistant id: datetime number: ## Creates a number entity in Home Assistant # It sets the menu of the Button Plus - platform: template name: "Menu Selector" id: menuselector optimistic: true min_value: 0 # keep at 0 max_value: 10 # TODO replace page numbers with substitution step: 1 on_value: then: - lambda: |- if(id(menuselector).state >= id(nr_of_pages)) { id(actual_page) = id(nr_of_pages)-1; } else { id(actual_page) = id(menuselector).state; } # # Refresh the screen # - script.execute: update_page <<< this ends in an loop... script: ###################### ### paging scripts ### ###################### - id: next_page then: - lambda: |- id(actual_page) = id(actual_page) + 1; if(id(actual_page) >= id(nr_of_pages)) { id(actual_page) = 0; } - script.execute: update_page - id: prev_page then: - lambda: |- id(actual_page) = id(actual_page) - 1; if(id(actual_page) < 0) { id(actual_page) = id(nr_of_pages) - 1; } - script.execute: update_page - id: update_page then: - lambda: |- id(active_page_name) = id(page_names)[id(actual_page)]; - sensor.template.publish: id: active_page_id_ha state: !lambda 'return id(actual_page);' - text_sensor.template.publish: id: active_page_name_ha state: !lambda 'return id(active_page_name);' - number.set: id: menuselector value: !lambda 'return id(actual_page);' - script.execute: update_all ## display item constructor # requires all of the following parameters as input # it requires fonts and colors to be defined # can be called as lambda, for instance # id(displayitem).execute(160,2,"External","size_3_label","orange",id(outside_temperature).state,1,"","size_3_value","white","°C","size_3_unit","grey","TOP_LEFT"); # this will position a block with label "External" in size 3 and color orange # below that a value with 1 decimal, in white, size 3 # next to that unit "°C", size 3 in grey # anchored at x,y = 160,2 top left corner - id: displayitem parameters: x: int # x of anchor point y: int # y of anchor point label: string # label, to be shown above the value labelfont: string # any of size_1_value, size_2_unit, size_3_label and everything in between labelcolor: string # any of white, orange, red, green or grey valuenum: float # value to display (numerical) decimals: int # rounding of value to display valuestr: string # if left empty it will select display the value float valuefont: string # any of size_1_value, size_2_unit, size_3_label and everything in between valuecolor: string # any of white, orange, red, green or grey unit: string # displayed unit unitfont: string # any of size_1_value, size_2_unit, size_3_label and everything in between unitcolor: string # any of white, orange, red, green or grey align: string # TOP_LEFT, TOP_CENTER or TOP_RIGHT then: lambda: |- // Helper to get the font object from its ID string // This lambda function maps font ID strings to their corresponding font objects. // If the provided font_id matches a known font, it returns the appropriate font object. // Otherwise, it returns nullptr. auto get_font = [](const std::string &font_id) -> esphome::font::Font* { if (font_id == "size_1_value") return id(size_1_value); if (font_id == "size_1_label") return id(size_1_label); if (font_id == "size_1_unit") return id(size_1_unit); if (font_id == "size_2_value") return id(size_2_value); if (font_id == "size_2_label") return id(size_2_label); if (font_id == "size_2_unit") return id(size_2_unit); if (font_id == "size_3_value") return id(size_3_value); if (font_id == "size_3_label") return id(size_3_label); if (font_id == "size_3_unit") return id(size_3_unit); return nullptr; // Return nullptr if the font ID doesn't match any known font. }; auto get_color = [](const std::string &color_id) -> esphome::Color { if (color_id == "white") return id(white); if (color_id == "orange") return id(orange); if (color_id == "red") return id(red); if (color_id == "green") return id(green); if (color_id == "grey") return id(grey); return esphome::Color::WHITE; // Default color if no match }; esphome::Color labelcolor_script = get_color(labelcolor); esphome::Color valuecolor_script = get_color(valuecolor); esphome::Color unitcolor_script = get_color(unitcolor); // Resolve font objects based on the provided font IDs. // These objects will later be used to measure text dimensions and render text on the display. auto *labelfont_obj = get_font(labelfont); auto *valuefont_obj = get_font(valuefont); auto *unitfont_obj = get_font(unitfont); // Format the numeric value as a string with the specified number of decimals. // snprintf safely formats the float into a fixed-width character buffer. char value_str[40]; // Check if valuestr is not empty if (valuestr.empty()) { // Check if valuestr has any content snprintf(value_str, sizeof(value_str), "%.*f", decimals, valuenum); } else { // If valuestr is empty, copy a default or alternative string into value_str strncpy(value_str, valuestr.c_str(), sizeof(value_str)); // Example default value value_str[sizeof(value_str) - 1] = '\0'; // Ensure null-termination } // Helper function to calculate text dimensions. // This lambda takes a font object and a text string as input, // and it calculates the text's width and height (returned via reference parameters). auto calculate_text_dimensions = [](esphome::font::Font *font, const char *text, int &width, int &baseline) { int x_offset = 0, height = 0; font->measure(text, &width, &x_offset, &baseline, &height); }; // Calculate the dimensions of the label text. int label_width = 0, label_height = 0; calculate_text_dimensions(labelfont_obj, label.c_str(), label_width, label_height); // Calculate the dimensions of the value text. int value_width = 0, value_height = 0; calculate_text_dimensions(valuefont_obj, value_str, value_width, value_height); // Calculate the dimensions of the unit text. int unit_width = 0, unit_height = 0; calculate_text_dimensions(unitfont_obj, unit.c_str(), unit_width, unit_height); if (align == "TOP_RIGHT" ){ // Render the label on the display at the specified position (x, y). id(main_display).printf(x, y, labelfont_obj, labelcolor_script, TextAlign::TOP_RIGHT, "%s", label.c_str()); // Render the value below the label with a vertical offset equal to the label's height. id(main_display).printf(x - unit_width, y - 2 + label_height , valuefont_obj, valuecolor_script, TextAlign::TOP_RIGHT, "%s", value_str); // Render the unit text to the right of the value text with appropriate offsets. id(main_display).printf(x, y - 2 + label_height + value_height - unit_height, unitfont_obj, unitcolor_script, TextAlign::TOP_RIGHT, "%s", unit.c_str()); } else if (align == "TOP_CENTER" ){ // Calculate the maximum width of the display item, being the label or value+unit // subtract half of that from the anchor point and position based on top left corner of the text fields int offset = (std::max(label_width, value_width + unit_width) + 1) / 2; // Render the label on the display at the specified position (x, y). id(main_display).printf(x - offset, y, labelfont_obj, labelcolor_script, TextAlign::TOP_LEFT, "%s", label.c_str()); // Render the value below the label with a vertical offset equal to the label's height. id(main_display).printf(x - offset, y - 2 + label_height , valuefont_obj, valuecolor_script, TextAlign::TOP_LEFT, "%s", value_str); // Render the unit text to the right of the value text with appropriate offsets. id(main_display).printf(x - offset + value_width, y -2+ label_height + value_height - unit_height, unitfont_obj, unitcolor_script, TextAlign::TOP_LEFT, "%s", unit.c_str()); } else { // Render the label on the display at the specified position (x, y). id(main_display).printf(x, y, labelfont_obj, labelcolor_script, TextAlign::TOP_LEFT, "%s", label.c_str()); // Render the value below the label with a vertical offset equal to the label's height. id(main_display).printf(x, y - 2 + label_height , valuefont_obj, valuecolor_script, TextAlign::TOP_LEFT, "%s", value_str); // Render the unit text to the right of the value text with appropriate offsets. id(main_display).printf(x + value_width, y - 2 + label_height + value_height - unit_height , unitfont_obj, unitcolor_script, TextAlign::TOP_LEFT, "%s", unit.c_str()); }