Last active
April 14, 2025 10:36
-
-
Save kmplngj/c02d0f3e0d68ad97dc4c2fcd3a0edb51 to your computer and use it in GitHub Desktop.
GeekMagic Display Small TV ESPHome Config
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This ESPHome configuration file is for a GeekMagic Display device (esp01). | |
# It sets up the device to display pages of text and notifications, | |
# with customizable intervals and padding. The display is updated | |
# periodically and can show different pages based on Home Assistant sensors. | |
# The configuration includes: | |
# - WiFi setup with fallback hotspot | |
# - Web server for remote access | |
# Usage: | |
# - The device connects to your WiFi network and can be accessed via a web server. | |
# - It displays text from Home Assistant sensors on a rotating basis. | |
# - Notifications can be sent to the display via the Home Assistant API. | |
# - Display update and page change intervals can be customized. | |
# - Padding can be adjusted. | |
# - The display can be turned on and off remotely. Brightness can be adjusted. | |
# - The page rotation can be enabled or disabled. | |
# - The display can be set to a fixed page. | |
# - The display can show different colors and font sizes with special markers. | |
# - Progress bars can be displayed at the top and bottom of the screen. | |
# Possible markers for text customization: | |
# Color theme is Catppuccino Macchiato | |
# - [rosewater]: Changes the text color to rosewater. | |
# - [flamingo]: Changes the text color to flamingo. | |
# - [pink]: Changes the text color to pink. | |
# - [mauve]: Changes the text color to mauve. | |
# - [red]: Changes the text color to red. | |
# - [maroon]: Changes the text color to maroon. | |
# - [peach]: Changes the text color to peach. | |
# - [yellow]: Changes the text color to yellow. | |
# - [green]: Changes the text color to green. | |
# - [teal]: Changes the text color to teal. | |
# - [sky]: Changes the text color to sky. | |
# - [sapphire]: Changes the text color to sapphire. | |
# - [blue]: Changes the text color to blue. | |
# - [lavender]: Changes the text color to lavender. | |
# - [text]: Changes the text color to text. | |
# - [subtext1]: Changes the text color to subtext1. | |
# - [subtext0]: Changes the text color to subtext0. | |
# - [overlay2]: Changes the text color to overlay2. | |
# - [overlay1]: Changes the text color to overlay1. | |
# - [overlay0]: Changes the text color to overlay0. | |
# - [surface2]: Changes the text color to surface2. | |
# - [surface1]: Changes the text color to surface1. | |
# - [surface0]: Changes the text color to surface0. | |
# - [base]: Changes the text color to base. | |
# - [mantle]: Changes the text color to mantle. | |
# - [crust]: Changes the text color to crust. | |
# - [center]: Centers the text. | |
# - [left]: Aligns the text to the left. | |
# - [right]: Aligns the text to the right. | |
# - [font_small]: Changes the font size to small. | |
# - [bg_rosewater]: Changes the background color to rosewater. | |
# - [bg_flamingo]: Changes the background color to flamingo. | |
# - [bg_pink]: Changes the background color to pink. | |
# - [bg_mauve]: Changes the background color to mauve. | |
# - [bg_red]: Changes the background color to red. | |
# - [bg_maroon]: Changes the background color to maroon. | |
# - [bg_peach]: Changes the background color to peach. | |
# - [bg_yellow]: Changes the background color to yellow. | |
# - [bg_green]: Changes the background color to green. | |
# - [bg_teal]: Changes the background color to teal. | |
# - [bg_sky]: Changes the background color to sky. | |
# - [bg_sapphire]: Changes the background color to sapphire. | |
# - [bg_blue]: Changes the background color to blue. | |
# - [bg_lavender]: Changes the background color to lavender. | |
# - [bg_text]: Changes the background color to text. | |
# - [bg_subtext1]: Changes the background color to subtext1. | |
# - [bg_subtext0]: Changes the background color to subtext0. | |
# - [bg_overlay2]: Changes the background color to overlay2. | |
# - [bg_overlay1]: Changes the background color to overlay1. | |
# - [bg_overlay0]: Changes the background color to overlay0. | |
# - [bg_surface2]: Changes the background color to surface2. | |
# - [bg_surface1]: Changes the background color to surface1. | |
# - [bg_surface0]: Changes the background color to surface0. | |
# - [bg_base]: Changes the background color to base. | |
# - [bg_mantle]: Changes the background color to mantle. | |
# - [bg_crust]: Changes the background color to crust. | |
# - [column_1]: Starts the first column. | |
# - [column_2]: Starts the second column. | |
# - [initial] [/initial]: Sets the page initial (max 4 characters). | |
# - "\n": Adds a new line. | |
# Here is an example string that can be displayed: | |
# "[bg_crust][center]Welcome to Page 1[/center]\n[red]Important: Check the status below.[/red]\n[column_1][font_small]Column 1 Text[/font_small][/column_1][column_2][font_small]Column 2 Text[/font_small][/column_2]\nEnjoy your stay!\n[green][/green]\n[blue]Blue Text[/blue][/bg_crust]" | |
# Here is an example string that can be displayed with columns: | |
# "[column_1][font_small][red]This is text in column 1.\n[column_2]This is text in column 2.\n" | |
# The display can be controlled via the following Home Assistant sensors: | |
# This uses the substitutions friendly_sensor_name value to create the sensor names. Defaults would be | |
# - sensor.geekmagic_display_07c9f4_page_1 | |
# - sensor.geekmagic_display_07c9f4_page_2 | |
# - sensor.geekmagic_display_07c9f4_page_3 | |
# - sensor.geekmagic_display_07c9f4_page_4 | |
# - sensor.geekmagic_display_07c9f4_page_5 | |
# - number.geekmagic_display_07c9f4_progress_top | |
# - number.geekmagic_display_07c9f4_progress_bottom | |
# These sensors can be updated via Home Assistant automations, scripts, templates. | |
# The display can be controlled via the following Home Assistant services: | |
# This service can be called via Home Assistant automations, scripts, templates. | |
# - esphome.display_notification | |
# The service requires the following parameters: | |
# - title: string | |
# - message: string | |
# - timeout: int | |
# - esphome.trigger_display_update | |
# - esphome.show_specific_page | |
# The service requires the following parameter: | |
# - page_number: int | |
# - esphome.deactivate_notification | |
# - esphome.repeat_last_notification | |
# - esphome.next_page | |
# - esphome.previous_page | |
# - esphome.jump_to_page | |
# The service requires the following parameter: | |
# - page_number: int | |
# - esphome.set_number_of_pages | |
# The service requires the following parameter: | |
# - num_pages: int | |
substitutions: | |
device_name: esphome-web-07c9f4 | |
friendly_sensor_name: geekmagic_display_07c9f4 | |
esphome: | |
name: ${device_name} | |
friendly_name: GeekMagic Display | |
min_version: 2024.11.0 | |
name_add_mac_suffix: false | |
on_boot: | |
priority: 10 | |
then: | |
- script.execute: initialize_device | |
esp8266: | |
board: esp01_1m | |
external_components: | |
- source: | |
type: git | |
url: https://github.com/rletendu/esphome.git | |
ref: st7789_nobuffer_202312 | |
components: [st7789v] | |
# logger: | |
# level: NONE # Reduced logging level to save memory! | |
api: | |
encryption: | |
key: !secret api-key | |
actions: | |
- action: display_notification | |
variables: | |
message: string | |
timeout: int | |
then: | |
- lambda: |- | |
id(notification_message) = message; | |
id(notification_timeout) = timeout; | |
- script.execute: show_notification | |
- action: trigger_display_update | |
then: | |
- script.execute: update_display | |
- action: show_specific_page | |
variables: | |
page_number: int | |
then: | |
- lambda: |- | |
id(page) = page_number; | |
id(display_update_triggered) = true; | |
- component.update: disp | |
- action: deactivate_notification | |
then: | |
- lambda: |- | |
id(notification_active) = false; | |
id(display_update_triggered) = true; | |
- component.update: disp | |
- action: repeat_last_notification | |
then: | |
- lambda: |- | |
id(notification_active) = true; | |
id(display_update_triggered) = true; | |
- component.update: disp | |
- delay: !lambda "return id(notification_timeout) * 1000;" | |
- lambda: |- | |
id(notification_active) = false; | |
id(display_update_triggered) = true; | |
- component.update: disp | |
- action: next_page | |
then: | |
- lambda: |- | |
id(page) = (id(page) % id(number_of_pages)) + 1; | |
id(display_update_triggered) = true; | |
- component.update: disp | |
- action: previous_page | |
then: | |
- lambda: |- | |
id(page) = (id(page) - 2 + id(number_of_pages)) % id(number_of_pages) + 1; | |
id(display_update_triggered) = true; | |
- component.update: disp | |
- action: jump_to_page | |
variables: | |
page_number: int | |
then: | |
- lambda: |- | |
id(page) = page_number; | |
id(display_update_triggered) = true; | |
- component.update: disp | |
- action: set_number_of_pages | |
variables: | |
num_pages: int | |
then: | |
- lambda: |- | |
id(number_of_pages) = num_pages; | |
- action: set_page_initial | |
variables: | |
initial: string | |
then: | |
- lambda: |- | |
id(page_initial) = initial.substr(0, 4); // Limit to 4 characters | |
ota: | |
- platform: esphome | |
safe_mode: | |
num_attempts: 5 # Number of failed boot attempts before entering safe mode | |
reboot_timeout: 10s # Time to wait before rebooting in safe mode | |
# on_safe_mode: | |
# then: | |
# - logger.log: "Entering safe mode due to repeated boot failures" | |
wifi: | |
ssid: !secret wifi-ssid | |
password: !secret wifi-password | |
ap: | |
ssid: ${device_name}_hotspot | |
password: !secret wifi-recovery-password | |
web_server: | |
port: 80 | |
version: 3 | |
auth: | |
username: !secret server_username | |
password: !secret server_password | |
sorting_groups: | |
- id: sorting_group_display_settings | |
name: "Display Settings" | |
sorting_weight: 20 | |
- id: sorting_group_network_settings | |
name: "Network Settings" | |
sorting_weight: 30 | |
- id: sorting_group_control_buttons | |
name: "Control Buttons" | |
sorting_weight: 10 | |
- id: sorting_group_control_buttons_basic | |
name: "ESPHome Basic Control Buttons" | |
sorting_weight: 25 | |
captive_portal: | |
button: | |
# The restart button platform allows you to restart your node remotely through Home Assistant. | |
# https://esphome.io/components/button/restart.html | |
- platform: restart | |
name: ${device_name} Restart | |
web_server: | |
sorting_group_id: sorting_group_control_buttons_basic | |
# The safe_mode button allows you to remotely reboot your node into Safe Mode. | |
# https://esphome.io/components/button/safe_mode.html | |
- platform: safe_mode | |
name: ${device_name} Restart (Safe Mode) | |
web_server: | |
sorting_group_id: sorting_group_control_buttons | |
- platform: template | |
name: "Deactivate Notification" | |
id: deactivate_notification_button | |
on_press: | |
then: | |
- lambda: |- | |
id(notification_active) = false; | |
id(display_update_triggered) = true; | |
- component.update: disp | |
web_server: | |
sorting_group_id: sorting_group_control_buttons_basic | |
- platform: template | |
name: "Update Display" | |
id: update_display_button | |
on_press: | |
then: | |
- script.execute: update_display | |
web_server: | |
sorting_group_id: sorting_group_control_buttons | |
- platform: template | |
name: "Next Page" | |
id: next_page_button | |
on_press: | |
then: | |
- lambda: |- | |
id(page) = (id(page) % id(number_of_pages)) + 1; | |
id(display_update_triggered) = true; | |
- component.update: disp | |
web_server: | |
sorting_group_id: sorting_group_control_buttons | |
- platform: template | |
name: "Previous Page" | |
id: previous_page_button | |
on_press: | |
then: | |
- lambda: |- | |
id(page) = (id(page) - 2 + id(number_of_pages)) % id(number_of_pages) + 1; | |
id(display_update_triggered) = true; | |
- component.update: disp | |
web_server: | |
sorting_group_id: sorting_group_control_buttons | |
- platform: template | |
name: "Show Last Notification" | |
id: show_last_notification_button | |
on_press: | |
then: | |
- script.execute: show_notification | |
web_server: | |
sorting_group_id: sorting_group_control_buttons | |
- platform: template | |
name: "Jump to Page 1" | |
id: jump_to_page_1_button | |
on_press: | |
then: | |
- lambda: |- | |
id(page) = 1; | |
id(display_update_triggered) = true; | |
- component.update: disp | |
web_server: | |
sorting_group_id: sorting_group_control_buttons | |
- platform: template | |
name: "Jump to Page 2" | |
id: jump_to_page_2_button | |
on_press: | |
then: | |
- lambda: |- | |
id(page) = 2; | |
id(display_update_triggered) = true; | |
- component.update: disp | |
web_server: | |
sorting_group_id: sorting_group_control_buttons | |
- platform: template | |
name: "Jump to Page 3" | |
id: jump_to_page_3_button | |
on_press: | |
then: | |
- lambda: |- | |
id(page) = 3; | |
id(display_update_triggered) = true; | |
- component.update: disp | |
web_server: | |
sorting_group_id: sorting_group_control_buttons | |
- platform: template | |
name: "Jump to Page 4" | |
id: jump_to_page_4_button | |
on_press: | |
then: | |
- lambda: |- | |
id(page) = 4; | |
id(display_update_triggered) = true; | |
- component.update: disp | |
web_server: | |
sorting_group_id: sorting_group_control_buttons | |
- platform: template | |
name: "Jump to Page 5" | |
id: jump_to_page_5_button | |
on_press: | |
then: | |
- lambda: |- | |
id(page) = 5; | |
id(display_update_triggered) = true; | |
- component.update: disp | |
web_server: | |
sorting_group_id: sorting_group_control_buttons | |
- platform: template | |
name: "Display Buzzer" | |
id: display_buzzer_button | |
on_press: | |
then: | |
- script.execute: display_buzzer | |
web_server: | |
sorting_group_id: sorting_group_control_buttons | |
switch: | |
- platform: template | |
name: "Enable Page Rotation" | |
id: page_rotation_switch | |
optimistic: true | |
restore_mode: RESTORE_DEFAULT_OFF | |
on_turn_on: | |
then: | |
- script.execute: update_display | |
on_turn_off: | |
then: | |
- script.execute: update_display | |
web_server: | |
sorting_group_id: sorting_group_display_settings | |
- platform: template | |
name: "Enable Auto Display Update" | |
id: auto_display_update_switch | |
optimistic: true | |
restore_mode: RESTORE_DEFAULT_OFF | |
web_server: | |
sorting_group_id: sorting_group_display_settings | |
spi: | |
clk_pin: GPIO14 | |
mosi_pin: GPIO13 | |
interface: hardware | |
id: spihwd | |
time: | |
- platform: homeassistant | |
id: homeassistant_time | |
output: | |
- platform: esp8266_pwm | |
pin: GPIO05 | |
frequency: 40 Hz | |
id: pwm_output | |
inverted: true | |
light: | |
- platform: monochromatic | |
output: pwm_output | |
name: "Backlight" | |
id: back_light | |
restore_mode: RESTORE_DEFAULT_ON | |
web_server: | |
sorting_group_id: sorting_group_display_settings | |
# - platform: status_led | |
# name: "Status LED" | |
# pin: GPIO10 | |
font: | |
- file: | |
type: gfonts | |
family: Inter Tight | |
weight: 400 | |
id: font_normal | |
size: 21 | |
glyphs: '!"%()+,-_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz/' | |
bpp: 2 | |
- file: | |
type: gfonts | |
family: Inter Tight | |
weight: 400 | |
id: font_small | |
size: 17 | |
glyphs: '!"%()+,-_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz/' | |
bpp: 2 | |
color: | |
- id: color_rosewater | |
hex: f4dbd6 | |
- id: color_flamingo | |
hex: f0c6c6 | |
- id: color_pink | |
hex: f5bde6 | |
- id: color_mauve | |
hex: c6a0f6 | |
- id: color_red | |
hex: ed8796 | |
- id: color_maroon | |
hex: ee99a0 | |
- id: color_peach | |
hex: f5a97f | |
- id: color_yellow | |
hex: eed49f | |
- id: color_green | |
hex: a6da95 | |
- id: color_teal | |
hex: 8bd5ca | |
- id: color_sky | |
hex: 91d7e3 | |
- id: color_sapphire | |
hex: 7dc4e4 | |
- id: color_blue | |
hex: 8aadf4 | |
- id: color_lavender | |
hex: b7bdf8 | |
- id: color_text | |
hex: cad3f5 | |
- id: color_subtext1 | |
hex: b8c0e0 | |
- id: color_subtext0 | |
hex: a5adcb | |
- id: color_overlay2 | |
hex: 939ab7 | |
- id: color_overlay1 | |
hex: 8087a2 | |
- id: color_overlay0 | |
hex: 6e738d | |
- id: color_surface2 | |
hex: 5b6078 | |
- id: color_surface1 | |
hex: 494d64 | |
- id: color_surface0 | |
hex: 363a4f | |
- id: color_base | |
hex: 24273a | |
- id: color_mantle | |
hex: 1e2030 | |
- id: color_crust | |
hex: 181926 | |
# sensor: | |
# # Uptime sensor | |
# - platform: uptime | |
# name: ${device_name}_uptime | |
# # WiFi Signal sensor | |
# - platform: wifi_signal | |
# name: ${device_name}_wifi_signal | |
# update_interval: 30s | |
text_sensor: | |
- platform: homeassistant | |
id: geekmagic_page_1 | |
entity_id: sensor.${friendly_sensor_name}_page_1 | |
on_value: | |
- if: | |
condition: | |
lambda: "return id(auto_display_update_switch).state && id(page) == 1;" | |
then: | |
- script.execute: update_display | |
- platform: homeassistant | |
id: geekmagic_page_2 | |
entity_id: sensor.${friendly_sensor_name}_page_2 | |
on_value: | |
then: | |
- if: | |
condition: | |
lambda: "return id(auto_display_update_switch).state && id(page) == 2;" | |
then: | |
- script.execute: update_display | |
- platform: homeassistant | |
id: geekmagic_page_3 | |
entity_id: sensor.${friendly_sensor_name}_page_3 | |
on_value: | |
then: | |
- if: | |
condition: | |
lambda: "return id(auto_display_update_switch).state && id(page) == 3;" | |
then: | |
- script.execute: update_display | |
- platform: homeassistant | |
id: geekmagic_page_4 | |
entity_id: sensor.${friendly_sensor_name}_page_4 | |
on_value: | |
then: | |
- if: | |
condition: | |
lambda: "return id(auto_display_update_switch).state && id(page) == 4;" | |
then: | |
- script.execute: update_display | |
- platform: homeassistant | |
id: geekmagic_page_5 | |
entity_id: sensor.${friendly_sensor_name}_page_5 | |
on_value: | |
then: | |
- if: | |
condition: | |
lambda: "return id(auto_display_update_switch).state && id(page) == 5;" | |
then: | |
- script.execute: update_display | |
- platform: version | |
name: ${device_name}_version | |
- platform: wifi_info | |
ip_address: | |
name: ${device_name}_ip | |
ssid: | |
name: ${device_name}_ssid | |
globals: | |
- id: page | |
type: int | |
initial_value: "1" | |
restore_value: yes | |
- id: notification_active | |
type: bool | |
initial_value: "false" | |
restore_value: yes | |
- id: notification_message | |
type: std::string | |
initial_value: '"Reset Notification."' | |
restore_value: yes | |
- id: notification_timeout | |
type: int | |
initial_value: "2" | |
restore_value: yes | |
- id: display_update_triggered | |
type: bool | |
initial_value: "false" | |
restore_value: yes | |
- id: background_color | |
type: Color | |
initial_value: "color_base" | |
restore_value: yes | |
- id: last_background_color | |
type: Color | |
initial_value: "color_base" | |
restore_value: yes | |
- id: first_boot | |
type: bool | |
initial_value: "true" | |
restore_value: yes | |
- id: startup_message_shown | |
type: bool | |
initial_value: "false" | |
restore_value: yes | |
- id: progress_top | |
type: int | |
initial_value: "0" | |
restore_value: yes | |
- id: progress_bottom | |
type: int | |
initial_value: "0" | |
restore_value: yes | |
- id: current_brightness | |
type: float | |
initial_value: "0.0" | |
restore_value: yes | |
- id: number_of_pages | |
type: int | |
initial_value: "3" | |
restore_value: yes | |
- id: page_initial | |
type: std::string | |
initial_value: '""' | |
restore_value: yes | |
number: | |
- platform: template | |
name: "Display Update Interval" | |
id: display_update_interval | |
min_value: 1 | |
max_value: 120 | |
step: 1 | |
unit_of_measurement: "s" | |
initial_value: 30 | |
optimistic: true | |
restore_value: yes | |
on_value: | |
then: | |
- script.execute: update_display | |
web_server: | |
sorting_group_id: sorting_group_display_settings | |
- platform: template | |
name: "Page Change Interval" | |
id: page_change_interval | |
min_value: 1 | |
max_value: 120 | |
step: 1 | |
unit_of_measurement: "s" | |
initial_value: 10 | |
optimistic: true | |
restore_value: yes | |
web_server: | |
sorting_group_id: sorting_group_display_settings | |
- platform: template | |
name: "Fixed Page" | |
id: fixed_page | |
min_value: 1 | |
max_value: 5 | |
step: 1 | |
unit_of_measurement: "" | |
initial_value: 1 | |
optimistic: true | |
restore_value: yes | |
on_value: | |
then: | |
- lambda: |- | |
id(page) = id(fixed_page).state; | |
id(display_update_triggered) = true; | |
- component.update: disp | |
web_server: | |
sorting_group_id: sorting_group_display_settings | |
- platform: template | |
name: "Left/Right Padding" | |
id: lr_padding | |
min_value: 0 | |
max_value: 15 | |
step: 1 | |
unit_of_measurement: "px" | |
initial_value: 5 | |
optimistic: true | |
restore_value: yes | |
web_server: | |
sorting_group_id: sorting_group_display_settings | |
sorting_weight: 100 | |
- platform: template | |
name: "Top/Bottom Padding" | |
id: tb_padding | |
min_value: 0 | |
max_value: 15 | |
step: 1 | |
unit_of_measurement: "px" | |
initial_value: 10 | |
optimistic: true | |
restore_value: yes | |
web_server: | |
sorting_group_id: sorting_group_display_settings | |
sorting_weight: 110 | |
- platform: template | |
name: "Number of Pages to Rotate" | |
id: number_of_pages_template | |
min_value: 2 | |
max_value: 5 | |
step: 1 | |
unit_of_measurement: "" | |
initial_value: 3 | |
optimistic: true | |
restore_value: yes | |
on_value: | |
then: | |
- lambda: |- | |
id(number_of_pages) = int(id(number_of_pages_template).state); | |
web_server: | |
sorting_group_id: sorting_group_display_settings | |
- platform: homeassistant | |
id: progress_top_sensor | |
entity_id: number.${friendly_sensor_name}_progress_top | |
on_value: | |
then: | |
- lambda: |- | |
id(progress_top) = int(id(progress_top_sensor).state); | |
web_server: | |
sorting_group_id: sorting_group_display_settings | |
- platform: homeassistant | |
id: progress_bottom_sensor | |
entity_id: number.${friendly_sensor_name}_progress_bottom | |
on_value: | |
then: | |
- lambda: |- | |
id(progress_bottom) = int(id(progress_bottom_sensor).state); | |
web_server: | |
sorting_group_id: sorting_group_display_settings | |
script: | |
- id: show_startup_message | |
mode: restart | |
then: | |
- lambda: |- | |
id(notification_message) = "Welcome! Device is starting up..."; | |
id(notification_active) = true; | |
id(display_update_triggered) = true; | |
- component.update: disp | |
- delay: 5s | |
- lambda: |- | |
id(display_update_triggered) = true; | |
id(startup_message_shown) = true; | |
- component.update: disp | |
- id: update_page | |
mode: restart | |
then: | |
- lambda: |- | |
id(display_update_triggered) = true; | |
- if: | |
condition: | |
lambda: "return id(page_rotation_switch).state;" | |
then: | |
- lambda: |- | |
id(page) = (id(page) % id(number_of_pages)) + 1; | |
else: | |
- lambda: |- | |
id(page) = id(fixed_page).state; | |
- delay: !lambda "return id(page_change_interval).state * 1000;" | |
- script.execute: update_page | |
- id: update_display | |
mode: single | |
then: | |
- lambda: |- | |
id(display_update_triggered) = true; | |
// Update the display content based on the current page sensor values | |
if (id(page) == 1) { | |
id(geekmagic_page_1).publish_state(id(geekmagic_page_1).state); | |
} else if (id(page) == 2) { | |
id(geekmagic_page_2).publish_state(id(geekmagic_page_2).state); | |
} else if (id(page) == 3) { | |
id(geekmagic_page_3).publish_state(id(geekmagic_page_3).state); | |
} else if (id(page) == 4) { | |
id(geekmagic_page_4).publish_state(id(geekmagic_page_4).state); | |
} else if (id(page) == 5) { | |
id(geekmagic_page_5).publish_state(id(geekmagic_page_5).state); | |
} | |
- component.update: disp | |
- delay: !lambda "return id(display_update_interval).state * 1000;" | |
- script.execute: update_display | |
- id: show_notification | |
mode: queued | |
then: | |
- lambda: |- | |
id(notification_active) = true; | |
id(display_update_triggered) = true; | |
- component.update: disp | |
- delay: !lambda "return id(notification_timeout) * 1000;" | |
- lambda: |- | |
id(notification_active) = false; | |
id(display_update_triggered) = true; | |
- component.update: disp | |
- id: display_update_loop | |
mode: restart | |
then: | |
- while: | |
condition: | |
lambda: "return true;" | |
then: | |
- if: | |
condition: | |
lambda: "return !id(display_update_triggered);" | |
then: | |
- component.update: disp | |
- lambda: |- | |
id(display_update_triggered) = false; | |
- delay: !lambda "return id(page_change_interval).state * 1000;" | |
- id: display_buzzer | |
mode: single | |
then: | |
- lambda: |- | |
id(current_brightness) = id(back_light).current_values.get_brightness(); // Store the brightness in a global variable | |
- repeat: | |
count: 4 | |
then: | |
- light.dim_relative: | |
id: back_light | |
relative_brightness: -0.99 | |
- delay: 500ms | |
- light.dim_relative: | |
id: back_light | |
relative_brightness: 0.99 | |
- delay: 500ms | |
- light.dim_relative: | |
id: back_light | |
relative_brightness: !lambda "return id(current_brightness) - id(back_light).current_values.get_brightness();" | |
- id: initialize_device | |
mode: restart | |
then: | |
- if: | |
condition: | |
lambda: "return id(first_boot);" | |
then: | |
- script.execute: show_startup_message | |
- lambda: |- | |
id(first_boot) = false; | |
- delay: 5s # Ensure the startup message is shown for 5 seconds before proceeding | |
- switch.turn_on: page_rotation_switch | |
- script.execute: update_page | |
- script.execute: update_display | |
- script.execute: display_update_loop | |
display: | |
- platform: st7789v | |
model: "Custom" | |
spi_id: spihwd | |
height: 240 | |
width: 240 | |
offset_height: 0 | |
offset_width: 0 | |
dc_pin: GPIO00 | |
reset_pin: GPIO02 | |
eightbitcolor: True | |
update_interval: 120s | |
id: disp | |
spi_mode: mode3 | |
lambda: |- | |
// Define average character widths and line heights | |
const int avg_char_width_normal = 9; | |
const int avg_char_width_small = 7; | |
const int avg_line_height_normal = 32; | |
const int avg_line_height_small = 26; | |
// Fill the global background color if it changes | |
if (id(background_color) != id(last_background_color)) { | |
it.fill(id(background_color)); // Apply global background | |
id(last_background_color) = id(background_color); | |
} | |
// Padding and progress bar adjustments | |
const int progress_bar_thickness = 3; | |
const int progress_bar_bottom_offset = 6; | |
const int y_start = id(tb_padding).state + (id(progress_top) > 0 ? progress_bar_thickness + 3 : 0); | |
int y = y_start; // Initial y position | |
int x = id(lr_padding).state; // Initial x position | |
// Column-related variables | |
const int display_width = it.get_width() - 2 * id(lr_padding).state; // Full display width | |
const int column_width = display_width / 2; // Width of each column | |
const int column_padding = 10; // Padding between columns | |
int left_x = id(lr_padding).state; // Start X for column 1 | |
int right_x = left_x + column_width + column_padding; // Start X for column 2 | |
int left_y = y_start; // Start Y for column 1 | |
int right_y = y_start; // Start Y for column 2 | |
bool in_column_1 = false; // Indicates if rendering in Column 1 | |
bool in_column_2 = false; // Indicates if rendering in Column 2 | |
// Default background colors for columns | |
Color column_1_bg_color = id(background_color); | |
Color column_2_bg_color = id(background_color); | |
const char* text = ""; // Placeholder for the text content | |
auto font = id(font_normal); | |
// Function to reset column positions | |
auto reset_column_positions = [&]() { | |
left_x = id(lr_padding).state; | |
right_x = left_x + column_width + column_padding; | |
left_y = y_start; // Reset Y for Column 1 | |
right_y = y_start; // Reset Y for Column 2 | |
}; | |
// Function to remove formatting markers from a string | |
auto remove_markers = [&](std::string &line) { | |
size_t marker_pos; | |
while ((marker_pos = line.find("[")) != std::string::npos) { | |
size_t end_marker_pos = line.find("]", marker_pos); | |
if (end_marker_pos != std::string::npos) { | |
line.erase(marker_pos, end_marker_pos - marker_pos + 1); | |
} else { | |
break; // Malformed marker | |
} | |
} | |
}; | |
// Function to render a single line with line wrapping | |
auto print_line = [&](int x, int &y, std::string line, Color color, TextAlign align, auto font, int avg_char_width, int avg_line_height) { | |
std::string clean_line = line; // Copy the line to clean formatting markers | |
remove_markers(clean_line); // Clean formatting markers for accurate line wrapping | |
// Determine the maximum characters per line based on the context (column or full width) | |
int usable_width = (in_column_1 || in_column_2) ? column_width - 2 * column_padding : display_width; | |
int max_chars_per_line = usable_width / avg_char_width; | |
// Wrap lines based on clean text length | |
while (clean_line.length() > max_chars_per_line) { | |
// Break the line at the max character limit | |
std::string sub_line = clean_line.substr(0, max_chars_per_line); | |
size_t last_space = sub_line.find_last_of(" "); // Try to break at a space | |
if (last_space != std::string::npos) { | |
sub_line = clean_line.substr(0, last_space); | |
clean_line.erase(0, last_space + 1); | |
} else { | |
sub_line += "-"; // Add a hyphen if no space is found | |
clean_line.erase(0, max_chars_per_line - 1); | |
} | |
// Render the wrapped line | |
it.printf(x, y, font, color, align, "%s", sub_line.c_str()); | |
y += avg_line_height; // Move to the next line | |
} | |
// Render the remaining part of the line | |
it.printf(x, y, font, color, align, "%s", clean_line.c_str()); | |
y += avg_line_height; // Adjust line height | |
}; | |
// Function to parse and render text with markers | |
auto parse_line = [&](std::string line) { | |
Color color = id(color_text); // Default text color | |
TextAlign align = TextAlign::LEFT; // Default text alignment | |
auto font = id(font_normal); // Default font | |
int avg_char_width = avg_char_width_normal; | |
int avg_line_height = avg_line_height_normal; | |
// Function to apply style markers (e.g., [red], [font_small]) | |
auto apply_style_markers = [&](std::string &line) { | |
auto find_and_replace = [&](std::string &line, const std::string &marker, auto &property, auto new_value) { | |
size_t pos = line.find(marker); | |
if (pos != std::string::npos) { | |
property = new_value; | |
line.erase(pos, marker.length()); | |
} | |
}; | |
// Apply text styles and font sizes | |
find_and_replace(line, "[font_small]", font, id(font_small)); | |
find_and_replace(line, "[font_large]", font, id(font_normal)); // Example for large font | |
find_and_replace(line, "[red]", color, id(color_red)); // Example for red text | |
// Alignment markers | |
find_and_replace(line, "[center]", align, TextAlign::CENTER); | |
find_and_replace(line, "[left]", align, TextAlign::LEFT); | |
find_and_replace(line, "[right]", align, TextAlign::RIGHT); | |
// Page initial marker | |
size_t initial_start_pos = line.find("[initial]"); | |
size_t initial_end_pos = line.find("[/initial]"); | |
if (initial_start_pos != std::string::npos && initial_end_pos != std::string::npos) { | |
id(page_initial) = line.substr(initial_start_pos + 9, initial_end_pos - initial_start_pos - 9).substr(0, 4); // Extract up to 4 characters | |
line.erase(initial_start_pos, initial_end_pos - initial_start_pos + 10); // Remove the marker and the initial | |
} | |
}; | |
// First, process column markers to set the rendering context | |
if (line.find("[column_1]") != std::string::npos) { | |
in_column_1 = true; | |
in_column_2 = false; | |
x = left_x; | |
y = left_y; | |
line.erase(line.find("[column_1]"), std::string("[column_1]").length()); | |
} else if (line.find("[/column_1]") != std::string::npos) { | |
in_column_1 = false; | |
left_y = y; // Update left_y after finishing Column 1 | |
line.erase(line.find("[/column_1]"), std::string("[/column_1]").length()); | |
} else if (line.find("[column_2]") != std::string::npos) { | |
in_column_2 = true; | |
in_column_1 = false; | |
x = right_x; | |
y = right_y; | |
line.erase(line.find("[column_2]"), std::string("[column_2]").length()); | |
} else if (line.find("[/column_2]") != std::string::npos) { | |
in_column_2 = false; | |
right_y = y; // Update right_y after finishing Column 2 | |
line.erase(line.find("[/column_2]"), std::string("[/column_2]").length()); | |
} | |
// Apply any remaining style markers | |
apply_style_markers(line); | |
// Render the line in the appropriate column or full display | |
if (in_column_1) { | |
print_line(left_x, left_y, line, color, align, font, avg_char_width, avg_line_height); | |
} else if (in_column_2) { | |
print_line(right_x, right_y, line, color, align, font, avg_char_width, avg_line_height); | |
} else { | |
print_line(x, y, line, color, align, font, avg_char_width, avg_line_height); // Full width | |
} | |
}; | |
// Reset the page initial at the start of rendering | |
id(page_initial) = ""; | |
// Determine the text to display based on the current page or notification | |
if (id(notification_active)) { | |
id(notification_active) = false; // Reset the notification flag | |
text = id(notification_message).c_str(); | |
} else { | |
switch (id(page)) { | |
case 1: | |
text = id(geekmagic_page_1).state.c_str(); | |
break; | |
case 2: | |
text = id(geekmagic_page_2).state.c_str(); | |
break; | |
case 3: | |
text = id(geekmagic_page_3).state.c_str(); | |
break; | |
case 4: | |
text = id(geekmagic_page_4).state.c_str(); | |
break; | |
case 5: | |
text = id(geekmagic_page_5).state.c_str(); | |
break; | |
default: | |
text = "Invalid page!"; | |
} | |
} | |
// Remove all line breaks and returns from the text | |
std::string text_str(text); | |
text_str.erase(std::remove(text_str.begin(), text_str.end(), '\n'), text_str.end()); | |
text_str.erase(std::remove(text_str.begin(), text_str.end(), '\r'), text_str.end()); | |
// Reset column positions at the start of rendering | |
reset_column_positions(); | |
size_t pos = 0; | |
while ((pos = text_str.find("\\n")) != std::string::npos) { | |
std::string line = text_str.substr(0, pos); | |
parse_line(line); | |
text_str.erase(0, pos + 2); | |
} | |
parse_line(text_str); // Render remaining text | |
// Draw progress bars | |
auto get_progress_color = [&](int progress) { | |
if (progress <= 25) { | |
return id(color_green); | |
} else if (progress <= 50) { | |
return id(color_yellow); | |
} else if (progress <= 75) { | |
return id(color_peach); | |
} else { | |
return id(color_red); | |
} | |
}; | |
int progress_top_width = (it.get_width() * id(progress_top)) / 100; | |
int progress_bottom_width = (it.get_width() * id(progress_bottom)) / 100; | |
// Top progress bar | |
if (id(progress_top) > 0) { | |
it.filled_rectangle(0, 0, progress_top_width, progress_bar_thickness, get_progress_color(id(progress_top))); | |
} | |
// Bottom progress bar | |
if (id(progress_bottom) > 0) { | |
it.filled_rectangle( | |
0, | |
it.get_height() - progress_bar_thickness - progress_bar_bottom_offset, | |
progress_bottom_width, | |
progress_bar_thickness, | |
get_progress_color(id(progress_bottom)) | |
); | |
} | |
// Function to render the page initial | |
auto render_page_initial = [&]() { | |
if (!id(page_initial).empty()) { | |
int initial_length = id(page_initial).length(); | |
int initial_x = it.get_width() - id(lr_padding).state - initial_length * 8; // Adjust x position based on initial length | |
int initial_y = it.get_height() - id(tb_padding).state - 28; // Small font height | |
it.printf(initial_x, initial_y, id(font_small), id(color_overlay0), TextAlign::RIGHT, "%s", id(page_initial).c_str()); | |
} | |
}; | |
// Render the page initial at the end of the display update | |
render_page_initial(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I’ve made some updates to my ESPHome configuration for the low-end GeekMagic TV:
• Removed unnecessary sensors and the logger to save RAM.
• Added an option to create two columns on a page.
• Enabled displaying a page initially in the bottom right corner.
• Included the API key in the API configuration for potential REST API functionality (not thoroughly tested yet).
• Updated the web server GUI to a newer, more user-friendly version.
• Added sorting to the web server UI.
• Settings are now saved between reboots.
• Fixed issues with display and sensor updates.
• Notifications are now queued for better handling.