From 6ba2e1419e11f634fe53aa9d4ebb749972458743 Mon Sep 17 00:00:00 2001 From: kennyboy55 Date: Sat, 13 Jun 2026 16:52:01 +0200 Subject: [PATCH] Implement config split (with AI help) --- README.md | 57 +- esphome/latest_generated_full.yaml | 1833 ++++-------------- esphome/latest_generated_full_base.yaml | 1645 ++++++++++++++++ src/buttonplus_generator/constants.py | 6 +- src/buttonplus_generator/generator.py | 109 +- src/buttonplus_generator/merger.py | 274 ++- src/buttonplus_generator/snippets/display.py | 28 +- 7 files changed, 2479 insertions(+), 1473 deletions(-) create mode 100644 esphome/latest_generated_full_base.yaml diff --git a/README.md b/README.md index 2368104..482da0d 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,42 @@ # buttonplus-esphome-generator -A python script that generates a full ESPHome config for the Button+ V2 +A python script that generates a full ESPHome config for the Button+ with V2 PCB ## Features - Notification Action: `esphome._notification` - Shows a notification, which has to be dismissed +- Clear notification Action: `esphome._notification_clear` - Toast Action: `esphome._toast` - Shows a quick toast message which dissapears after a timeout - Perfect for confirming actions -- Get info Action: `esphome._get_page_info` +- Get info Action: `esphome._get_info` - Shows data needed for the set_page_led action + - Including the available effects, led names and page names - Set Led per page Action: `esphome._set_page_led` - Allows you to set the LED for a specific page - So that you don't need to keep track of which page is active when setting a LED + - All fields are required, because optionality is not possible at this moment + - Effect can be set to None + - RGB and Brightness should be values between 0 and 255 +- Reset all LEDS: `esphome._reset_all_page_leds` + - Every LED config for every page will be set back to off and clear all data +- Show web image: `esphome._show_web_jpeg_image` + - Must be a url to a JPG image! + - Will be resized to 320x240 pixels. + - Will disappear after the timeout and be removed from memory + - Will look absolutely horrendous +- Show HA image: `esphome._show_ha_jpeg_image` + - Only available if a Home Assistant `--token TOKEN` is set during generation + - Allows loading of images which require login from Home Assistant + - Sets the `Authorization: Bearer ` header + - All other things from the web image action +- Clear images before their timeout: `esphome._clear_image` + - Will clear both web or HA image immediatly and release the memory - Generates pages and all buttons and events belonging to each page - Breathe and Blink effect for all LEDs -- more to come.. +- Confirmation LED pulse on button press + +And all of that is split into two files for convenience. Everything you can and should edit or create manually in the top level file, while everything else is in the secondary file. ## Setup ### Project uses pipenv (Optional) @@ -27,29 +48,23 @@ run `pipenv shell` after that to open the virtual environment. You can then use `python src/main.py --help` to run the generator -### Example run command -`python src/main.py -v 1 -b 3 -p Start Music Lights Security -c single double hold` +### Example run commands +`python src/main.py -v 1 -b 3 -p Start Music Lights Security -c single double hold --token EXAMPLE_TOKEN --no-click-confirm` +`python src/main.py --device-name Generated_ButtonPlus --version 2 --bars 1 --pages Home Living --click-types single hold --no-split --output buttonplus` ## Config example -The configuration (handmade) on which this generator is based, can be found in the `esphome` folder. +The configuration (handmade) on which this generator was initially based, can be found in the `esphome` folder. There is also an example of the output of this generator. + +## Credits +Initially based on the configuration of [https://github.com/dixi83/ESPhome_ButtonPlus](https://github.com/dixi83/ESPhome_ButtonPlus), all initial effort for this was done by [dixi83](https://github.com/dixi83) and [balk77](https://github.com/balk77) + + [More information (in Dutch) can be found here on Tweakers.net](https://gathering.tweakers.net/forum/list_messages/2270086) ## Todo ### Ideas -- Show image service -- Top level config only for drawing, everything else hidden - - back to using packages or includes probably - - with --no-split option - - two files +None ### Missing config -- Event lambda's (see config.yaml) -- Implement --no-click-confirm -- Make effects optional (--no-effects) -- service: get led state - - led name - - page name - - page num -- service: get_page_info - - rename the service - - add the effect options \ No newline at end of file +None + diff --git a/esphome/latest_generated_full.yaml b/esphome/latest_generated_full.yaml index 374faad..8736997 100644 --- a/esphome/latest_generated_full.yaml +++ b/esphome/latest_generated_full.yaml @@ -1,33 +1,16 @@ # Generated by ESPHome for Buttonplus +# File: buttonplus.yaml # Options: # - device: buttonplus # - version: 1 # - bars: 3 # - pages: Start, Music, Lights, Security +packages: +- !include 'buttonplus_base.yaml' esphome: name: buttonplus friendly_name: buttonplus - on_boot: - priority: -100 - then: - - script.execute: setup_bar_displays - - script.execute: test_all_leds - - script.execute: bar1_left_script_update - - script.execute: bar1_right_script_update - - script.execute: bar2_left_script_update - - script.execute: bar2_right_script_update - - script.execute: bar3_left_script_update - - script.execute: bar3_right_script_update -esp32: - variant: ESP32S3 - board: esp32-s3-devkitc1-n16r8 - flash_size: 16MB - framework: - type: esp-idf -psram: - mode: octal - speed: 80MHz wifi: ssid: !secret 'wifi_ssid' password: !secret 'wifi_password' @@ -39,356 +22,14 @@ ota: api: encryption: key: !secret 'encryption_key' - actions: - - action: notification - variables: - flash_leds: bool - message: string - then: - - globals.set: - id: internal_notify - value: !lambda "return message;" - - script.execute: display_script_update - - while: - condition: - lambda: return (flash_leds && id(internal_notify).size() > 0); - then: - - light.turn_on: - id: internal_notification_rgb - brightness: 100% - red: 100% - green: 100% - blue: 100% - flash_length: 1s - - delay: 2s - - wait_until: - condition: - lambda: return id(internal_notify).size() == 0; - - light.control: - id: bar1_left_back_rgb_led - - light.control: - id: bar1_right_back_rgb_led - - action: notification_clear - then: - - globals.set: - id: internal_notify - value: !lambda "return std::string();" - - script.execute: display_script_update - - action: toast - variables: - timeout: int - message: string - then: - - globals.set: - id: internal_toast - value: !lambda "return message;" - - script.execute: display_script_update - - delay: !lambda "return timeout * 1000;" - - globals.set: - id: internal_toast - value: !lambda "return std::string();" - - script.execute: display_script_update - - action: set_page_led - variables: - led: string - page_name: string - r: float - g: float - b: float - brightness: float - effect: string - then: - - if: - condition: - lambda: |- - // Validate page_name if provided - auto it = std::find(id(page_names).begin(), id(page_names).end(), page_name); - if (it == id(page_names).end()) return true; - return false; - then: - - api.respond: - success: false - error_message: Invalid page_name (not found). - else: - - if: - condition: - lambda: return id(internal_page_leds_r).find(led) == id(internal_page_leds_r).end(); - then: - - api.respond: - success: false - error_message: The requested LED does not exist. - else: - - lambda: |- - // Resolve final page index - auto it = std::find(id(page_names).begin(), id(page_names).end(), page_name); - int page_num = std::distance(id(page_names).begin(), it); - - id(internal_page_leds_r)[led][page_num] = r; - id(internal_page_leds_g)[led][page_num] = g; - id(internal_page_leds_b)[led][page_num] = b; - id(internal_page_leds_brightness)[led][page_num] = brightness; - id(internal_page_leds_effect)[led][page_num] = effect; - - api.respond: - success: true - - if: - condition: - lambda: |- - auto it = std::find(id(page_names).begin(), id(page_names).end(), page_name); - int page_num = std::distance(id(page_names).begin(), it); - return page_num == id(active_page_nr); - then: - - script.execute: update_all_page_leds - - action: get_page_info - supports_response: only - then: - - api.respond: - data: !lambda | - root["num_pages"] = id(nr_of_pages); - JsonArray pages = root["page_names"].to(); - for(const std::string& page_name : id(page_names)){ - pages.add(page_name); - } - JsonArray leds = root["leds"].to(); - leds.add("bar1_left_front_rgb_led"); - leds.add("bar1_right_front_rgb_led"); - leds.add("bar1_left_back_rgb_led"); - leds.add("bar1_right_back_rgb_led"); - leds.add("bar2_left_front_rgb_led"); - leds.add("bar2_right_front_rgb_led"); - leds.add("bar2_left_back_rgb_led"); - leds.add("bar2_right_back_rgb_led"); - leds.add("bar3_left_front_rgb_led"); - leds.add("bar3_right_front_rgb_led"); - leds.add("bar3_left_back_rgb_led"); - leds.add("bar3_right_back_rgb_led"); color: - id: accent hex: FF0000 -- id: white - hex: FFFFFF -- id: black - hex: '000000' -- id: grey - hex: A6A6A6 -font: -- file: - url: https://github.com/web-fonts/ttf/raw/refs/heads/master/bpg-ingiri-arial-webfont.ttf - type: web - id: font_arial20 - size: 20 - bpp: 4 -globals: -- id: nr_of_pages - type: int - initial_value: '4' -- id: page_names - type: std::vector - initial_value: '{"Start", "Music", "Lights", "Security"}' -- id: active_page_nr - type: int - initial_value: '0' -- id: active_page_name - type: std::string - initial_value: '' -- id: internal_notify - type: std::string - initial_value: '' -- id: internal_toast - type: std::string - initial_value: '' -- id: internal_page_leds_r - type: std::unordered_map> - initial_value: '{{"bar1_left_front_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar1_right_front_rgb_led", - {0.0, 0.0, 0.0, 0.0}}, {"bar1_left_back_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar1_right_back_rgb_led", - {0.0, 0.0, 0.0, 0.0}}, {"bar2_left_front_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar2_right_front_rgb_led", - {0.0, 0.0, 0.0, 0.0}}, {"bar2_left_back_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar2_right_back_rgb_led", - {0.0, 0.0, 0.0, 0.0}}, {"bar3_left_front_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar3_right_front_rgb_led", - {0.0, 0.0, 0.0, 0.0}}, {"bar3_left_back_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar3_right_back_rgb_led", - {0.0, 0.0, 0.0, 0.0}}}' -- id: internal_page_leds_g - type: std::unordered_map> - initial_value: '{{"bar1_left_front_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar1_right_front_rgb_led", - {0.0, 0.0, 0.0, 0.0}}, {"bar1_left_back_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar1_right_back_rgb_led", - {0.0, 0.0, 0.0, 0.0}}, {"bar2_left_front_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar2_right_front_rgb_led", - {0.0, 0.0, 0.0, 0.0}}, {"bar2_left_back_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar2_right_back_rgb_led", - {0.0, 0.0, 0.0, 0.0}}, {"bar3_left_front_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar3_right_front_rgb_led", - {0.0, 0.0, 0.0, 0.0}}, {"bar3_left_back_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar3_right_back_rgb_led", - {0.0, 0.0, 0.0, 0.0}}}' -- id: internal_page_leds_b - type: std::unordered_map> - initial_value: '{{"bar1_left_front_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar1_right_front_rgb_led", - {0.0, 0.0, 0.0, 0.0}}, {"bar1_left_back_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar1_right_back_rgb_led", - {0.0, 0.0, 0.0, 0.0}}, {"bar2_left_front_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar2_right_front_rgb_led", - {0.0, 0.0, 0.0, 0.0}}, {"bar2_left_back_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar2_right_back_rgb_led", - {0.0, 0.0, 0.0, 0.0}}, {"bar3_left_front_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar3_right_front_rgb_led", - {0.0, 0.0, 0.0, 0.0}}, {"bar3_left_back_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar3_right_back_rgb_led", - {0.0, 0.0, 0.0, 0.0}}}' -- id: internal_page_leds_brightness - type: std::unordered_map> - initial_value: '{{"bar1_left_front_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar1_right_front_rgb_led", - {0.0, 0.0, 0.0, 0.0}}, {"bar1_left_back_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar1_right_back_rgb_led", - {0.0, 0.0, 0.0, 0.0}}, {"bar2_left_front_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar2_right_front_rgb_led", - {0.0, 0.0, 0.0, 0.0}}, {"bar2_left_back_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar2_right_back_rgb_led", - {0.0, 0.0, 0.0, 0.0}}, {"bar3_left_front_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar3_right_front_rgb_led", - {0.0, 0.0, 0.0, 0.0}}, {"bar3_left_back_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar3_right_back_rgb_led", - {0.0, 0.0, 0.0, 0.0}}}' -- id: internal_page_leds_effect - type: std::unordered_map> - initial_value: '{{"bar1_left_front_rgb_led", {"", "", "", ""}}, {"bar1_right_front_rgb_led", - {"", "", "", ""}}, {"bar1_left_back_rgb_led", {"", "", "", ""}}, {"bar1_right_back_rgb_led", - {"", "", "", ""}}, {"bar2_left_front_rgb_led", {"", "", "", ""}}, {"bar2_right_front_rgb_led", - {"", "", "", ""}}, {"bar2_left_back_rgb_led", {"", "", "", ""}}, {"bar2_right_back_rgb_led", - {"", "", "", ""}}, {"bar3_left_front_rgb_led", {"", "", "", ""}}, {"bar3_right_front_rgb_led", - {"", "", "", ""}}, {"bar3_left_back_rgb_led", {"", "", "", ""}}, {"bar3_right_back_rgb_led", - {"", "", "", ""}}}' -sensor: -- platform: template - name: Active Page Number - icon: mdi:order-numeric-ascending - id: active_page_nr_ha - update_interval: never - accuracy_decimals: 0 -- platform: sts3x - address: 74 - id: buttonplus_temperature - i2c_id: sensors - name: Temperature - update_interval: 10s - unit_of_measurement: "\xB0C" - icon: mdi:thermometer - device_class: temperature - state_class: measurement - accuracy_decimals: 1 -- platform: ltr_als_ps - address: 41 - i2c_id: sensors - update_interval: 10s - type: ALS - auto_mode: false - gain: 96x - ambient_light: - name: Ambient Light - id: buttonplus_ambientlight - unit_of_measurement: lx - icon: mdi:brightness-6 - device_class: illuminance - state_class: measurement - accuracy_decimals: 1 -text_sensor: -- platform: template - name: Active Page Name - icon: mdi:order-alphabetical-ascending - id: active_page_name_ha - update_interval: never -number: -- platform: template - name: Page Selector - id: pageselector - optimistic: true - min_value: 0 - max_value: 3 - step: 1 - on_value: - then: - - lambda: |- - if(id(pageselector).state >= id(nr_of_pages)) { - id(active_page_nr) = id(nr_of_pages)-1; - } else { - id(active_page_nr) = id(pageselector).state; - } - - script.execute: set_page script: -- id: next_page +- id: display_script_draw_user then: - - if: - condition: - - lambda: !lambda "return id(internal_notify).size() > 0;" - then: - - globals.set: - id: internal_notify - value: !lambda "return std::string();" - - script.execute: display_script_update - - script.stop: next_page - lambda: |- - id(active_page_nr) = id(active_page_nr) + 1; - if(id(active_page_nr) >= id(nr_of_pages)) { - id(active_page_nr) = 0; - } - - script.execute: update_page -- id: prev_page - then: - - if: - condition: - - lambda: !lambda "return id(internal_notify).size() > 0;" - then: - - globals.set: - id: internal_notify - value: !lambda "return std::string();" - - script.execute: display_script_update - - script.stop: next_page - - lambda: |- - id(active_page_nr) = id(active_page_nr) - 1; - if(id(active_page_nr) < 0) { - id(active_page_nr) = id(nr_of_pages) - 1; - } - - script.execute: update_page -- id: set_page - then: - - lambda: id(active_page_name) = id(page_names)[id(active_page_nr)]; - - sensor.template.publish: - id: active_page_nr_ha - state: !lambda "return id(active_page_nr);" - - text_sensor.template.publish: - id: active_page_name_ha - state: !lambda "return id(active_page_name);" - - script.execute: update_all - - script.execute: update_all_page_leds -- id: update_page - then: - - number.set: - id: pageselector - value: !lambda "return id(active_page_nr);" - - script.execute: set_page -- id: test_all_leds - then: - - delay: 1000ms - - light.turn_on: - id: internal_all_rgb - red: 100% - green: 0% - blue: 0% - flash_length: 100ms - - delay: 200ms - - light.turn_on: - id: internal_all_rgb - red: 0% - green: 100% - blue: 0% - flash_length: 100ms - - delay: 200ms - - light.turn_on: - id: internal_all_rgb - red: 0% - green: 0% - blue: 100% - flash_length: 100ms -- id: update_all - mode: restart - then: - - script.execute: bars_script_update - - delay: 0.1s - - script.execute: display_script_update -- id: display_script_update - then: - - component.update: display_disp -- id: display_script_draw - then: - - lambda: | // All pages - id(display_disp).image(0, 235, id(icon_prev_page), ImageAlign::BOTTOM_LEFT, id(grey)); - id(display_disp).image(320, 235, id(icon_next_page), ImageAlign::BOTTOM_RIGHT, id(grey)); - id(display_disp).print(160, 235, id(font_arial20), id(white), TextAlign::BOTTOM_CENTER, id(active_page_name).c_str()); - id(display_disp).strftime(160, 5, id(font_arial20), TextAlign::TOP_CENTER, "%H:%M", id(datetime).now()); id(display_disp).strftime(160, 80, id(font_arial20), TextAlign::TOP_CENTER, "%d-%m-%Y", id(datetime).now()); @@ -406,141 +47,9 @@ script: case 3: break; } - - lambda: | - if(id(internal_notify).size() > 0){ - // Draw white box over existing text - id(display_disp).filled_rectangle(0, 200, 320, 40, white); - id(display_disp).rectangle(0, 200, 320, 40, accent); - // Show notification message - id(display_disp).print(160, 235, id(font_arial20), id(black), TextAlign::BOTTOM_CENTER, id(internal_notify).c_str()); - } - - lambda: | - if(id(internal_toast).size() > 0){ - // Draw white box over existing text - id(display_disp).filled_rectangle(0, 200, 320, 40, white); - // Show toast message - id(display_disp).print(160, 235, id(font_arial20), id(black), TextAlign::BOTTOM_CENTER, id(internal_toast).c_str()); - } -- id: bar1_left_script_update - then: - - lambda: |- - id(bar1_left_cs_pin).turn_on(); - id(bar1_right_cs_pin).turn_off(); - id(bar2_left_cs_pin).turn_off(); - id(bar2_right_cs_pin).turn_off(); - id(bar3_left_cs_pin).turn_off(); - id(bar3_right_cs_pin).turn_off(); - id(bar_display).clear(); - id(bar_display).set_rotation(display::DisplayRotation::DISPLAY_ROTATION_270_DEGREES); - id(bar1_left_script_draw).execute(); - id(bar_display).update(); - delay(1); - id(bar1_left_cs_pin).turn_off(); -- id: bar1_right_script_update - then: - - lambda: |- - id(bar1_left_cs_pin).turn_off(); - id(bar1_right_cs_pin).turn_on(); - id(bar2_left_cs_pin).turn_off(); - id(bar2_right_cs_pin).turn_off(); - id(bar3_left_cs_pin).turn_off(); - id(bar3_right_cs_pin).turn_off(); - id(bar_display).clear(); - id(bar_display).set_rotation(display::DisplayRotation::DISPLAY_ROTATION_90_DEGREES); - id(bar1_right_script_draw).execute(); - id(bar_display).update(); - delay(1); - id(bar1_right_cs_pin).turn_off(); -- id: bar2_left_script_update - then: - - lambda: |- - id(bar1_left_cs_pin).turn_off(); - id(bar1_right_cs_pin).turn_off(); - id(bar2_left_cs_pin).turn_on(); - id(bar2_right_cs_pin).turn_off(); - id(bar3_left_cs_pin).turn_off(); - id(bar3_right_cs_pin).turn_off(); - id(bar_display).clear(); - id(bar_display).set_rotation(display::DisplayRotation::DISPLAY_ROTATION_270_DEGREES); - id(bar2_left_script_draw).execute(); - id(bar_display).update(); - delay(1); - id(bar2_left_cs_pin).turn_off(); -- id: bar2_right_script_update - then: - - lambda: |- - id(bar1_left_cs_pin).turn_off(); - id(bar1_right_cs_pin).turn_off(); - id(bar2_left_cs_pin).turn_off(); - id(bar2_right_cs_pin).turn_on(); - id(bar3_left_cs_pin).turn_off(); - id(bar3_right_cs_pin).turn_off(); - id(bar_display).clear(); - id(bar_display).set_rotation(display::DisplayRotation::DISPLAY_ROTATION_90_DEGREES); - id(bar2_right_script_draw).execute(); - id(bar_display).update(); - delay(1); - id(bar2_right_cs_pin).turn_off(); -- id: bar3_left_script_update - then: - - lambda: |- - id(bar1_left_cs_pin).turn_off(); - id(bar1_right_cs_pin).turn_off(); - id(bar2_left_cs_pin).turn_off(); - id(bar2_right_cs_pin).turn_off(); - id(bar3_left_cs_pin).turn_on(); - id(bar3_right_cs_pin).turn_off(); - id(bar_display).clear(); - id(bar_display).set_rotation(display::DisplayRotation::DISPLAY_ROTATION_270_DEGREES); - id(bar3_left_script_draw).execute(); - id(bar_display).update(); - delay(1); - id(bar3_left_cs_pin).turn_off(); -- id: bar3_right_script_update - then: - - lambda: |- - id(bar1_left_cs_pin).turn_off(); - id(bar1_right_cs_pin).turn_off(); - id(bar2_left_cs_pin).turn_off(); - id(bar2_right_cs_pin).turn_off(); - id(bar3_left_cs_pin).turn_off(); - id(bar3_right_cs_pin).turn_on(); - id(bar_display).clear(); - id(bar_display).set_rotation(display::DisplayRotation::DISPLAY_ROTATION_90_DEGREES); - id(bar3_right_script_draw).execute(); - id(bar_display).update(); - delay(1); - id(bar3_right_cs_pin).turn_off(); -- id: setup_bar_displays - then: - - lambda: | - id(bar1_left_cs_pin).turn_on(); - id(bar_display).setup(); - delay(5); - id(bar1_left_cs_pin).turn_off(); - id(bar1_right_cs_pin).turn_on(); - id(bar_display).setup(); - delay(5); - id(bar1_right_cs_pin).turn_off(); - id(bar2_left_cs_pin).turn_on(); - id(bar_display).setup(); - delay(5); - id(bar2_left_cs_pin).turn_off(); - id(bar2_right_cs_pin).turn_on(); - id(bar_display).setup(); - delay(5); - id(bar2_right_cs_pin).turn_off(); - id(bar3_left_cs_pin).turn_on(); - id(bar_display).setup(); - delay(5); - id(bar3_left_cs_pin).turn_off(); - id(bar3_right_cs_pin).turn_on(); - id(bar_display).setup(); - delay(5); - id(bar3_right_cs_pin).turn_off(); - id: bar1_left_script_draw then: - - lambda: | + - lambda: |- switch (id(active_page_nr)){ // Page 0 - Start case 0: @@ -565,7 +74,7 @@ script: } - id: bar1_right_script_draw then: - - lambda: | + - lambda: |- switch (id(active_page_nr)){ // Page 0 - Start case 0: @@ -590,7 +99,7 @@ script: } - id: bar2_left_script_draw then: - - lambda: | + - lambda: |- switch (id(active_page_nr)){ // Page 0 - Start case 0: @@ -615,7 +124,7 @@ script: } - id: bar2_right_script_draw then: - - lambda: | + - lambda: |- switch (id(active_page_nr)){ // Page 0 - Start case 0: @@ -640,7 +149,7 @@ script: } - id: bar3_left_script_draw then: - - lambda: | + - lambda: |- switch (id(active_page_nr)){ // Page 0 - Start case 0: @@ -665,7 +174,7 @@ script: } - id: bar3_right_script_draw then: - - lambda: | + - lambda: |- switch (id(active_page_nr)){ // Page 0 - Start case 0: @@ -688,910 +197,420 @@ script: id(bar_display).printf(80, 80, id(font_arial20), id(white), TextAlign::BOTTOM_CENTER , "Security"); break; } -- id: bars_script_update - then: - - script.execute: bar1_left_script_update - - delay: 0.1s - - script.execute: bar1_right_script_update - - delay: 0.1s - - script.execute: bar2_left_script_update - - delay: 0.1s - - script.execute: bar2_right_script_update - - delay: 0.1s - - script.execute: bar3_left_script_update - - delay: 0.1s - - script.execute: bar3_right_script_update -- id: event_trigger_feedback - parameters: - event_id: esphome::template_::TemplateEvent* - event_msg: std::string - led_id: light::LightState* - r: float - g: float - b: float - then: - - lambda: |- - event_id->trigger(event_msg); - - if(led_id != nullptr){ - auto call = led_id->make_call(); - call.set_state(true); - call.set_rgb(r, g, b); - call.set_brightness(1.0); - call.set_flash_length(200); // Returns to previous state automatically - call.perform(); - } -- id: update_all_page_leds - then: - - lambda: | - id(update_single_led).execute("bar1_left_front_rgb_led", id(bar1_left_front_rgb_led), id(active_page_nr)); - id(update_single_led).execute("bar1_right_front_rgb_led", id(bar1_right_front_rgb_led), id(active_page_nr)); - id(update_single_led).execute("bar1_left_back_rgb_led", id(bar1_left_back_rgb_led), id(active_page_nr)); - id(update_single_led).execute("bar1_right_back_rgb_led", id(bar1_right_back_rgb_led), id(active_page_nr)); - id(update_single_led).execute("bar2_left_front_rgb_led", id(bar2_left_front_rgb_led), id(active_page_nr)); - id(update_single_led).execute("bar2_right_front_rgb_led", id(bar2_right_front_rgb_led), id(active_page_nr)); - id(update_single_led).execute("bar2_left_back_rgb_led", id(bar2_left_back_rgb_led), id(active_page_nr)); - id(update_single_led).execute("bar2_right_back_rgb_led", id(bar2_right_back_rgb_led), id(active_page_nr)); - id(update_single_led).execute("bar3_left_front_rgb_led", id(bar3_left_front_rgb_led), id(active_page_nr)); - id(update_single_led).execute("bar3_right_front_rgb_led", id(bar3_right_front_rgb_led), id(active_page_nr)); - id(update_single_led).execute("bar3_left_back_rgb_led", id(bar3_left_back_rgb_led), id(active_page_nr)); - id(update_single_led).execute("bar3_right_back_rgb_led", id(bar3_right_back_rgb_led), id(active_page_nr)); -- id: update_single_led - parameters: - led_name: string - led_id: light::LightState* - page: int - then: - - lambda: |- - if(led_id != nullptr){ - float r = id(internal_page_leds_r)[led_name][page]; - float g = id(internal_page_leds_g)[led_name][page]; - float b = id(internal_page_leds_b)[led_name][page]; - float br = id(internal_page_leds_brightness)[led_name][page]; - std::string effect = id(internal_page_leds_effect)[led_name][page]; - if(r > 1.5) r = r / 255.0; - if(g > 1.5) g = g / 255.0; - if(b > 1.5) b = b / 255.0; - if(br > 1.5) br = br / 255.0; - auto call = led_id->make_call(); - call.set_transition_length(10); - if(br <= 0.0001) { - call.set_state(false); - } else { - call.set_state(true); - call.set_rgb(r,g,b); - call.set_brightness(br); - call.set_effect(effect); - } - call.perform(); - } -image: -- file: mdi:chevron-right - id: icon_next_page - resize: 30x30 - type: BINARY -- file: mdi:chevron-left - id: icon_prev_page - resize: 30x30 - type: BINARY -time: -- platform: homeassistant - id: datetime -i2c: -- id: buttons - sda: GPIO1 - scl: GPIO2 - frequency: 50khz -- id: sensors - sda: GPIO47 - scl: GPIO48 - frequency: 50khz -spi: - clk_pin: GPIO6 - mosi_pin: GPIO5 -light: -- platform: esp32_rmt_led_strip - id: neopixels - rgb_order: RGB - chipset: ws2812 - pin: GPIO4 - num_leds: 13 - internal: true -- platform: partition - id: internal_all_rgb - internal: true - segments: - - id: neopixels - from: 0 - to: 12 -- platform: monochromatic - output: display_backlight_pwm - gamma_correct: 2.2 - name: Main Display Backlight - id: display_backlight - restore_mode: ALWAYS_ON -- platform: partition - id: internal_notification_rgb - internal: true - segments: - - id: neopixels - from: 1 - to: 1 - - id: neopixels - from: 3 - to: 3 -- platform: monochromatic - output: bars_backlight_pwm - gamma_correct: 2.2 - name: Bars Display Backlight - id: bars_backlight - restore_mode: ALWAYS_ON -- platform: partition - name: BAR 1 Left RGB Front - id: bar1_left_front_rgb_led - segments: - - id: neopixels - from: 0 - to: 0 - effects: - - pulse: - name: Breathe - transition_length: 1s - update_interval: 1.5s - min_brightness: 20% - max_brightness: 100% - - pulse: - name: Blink - transition_length: 50ms - update_interval: 500ms -- platform: partition - name: BAR 1 Left RGB Back - id: bar1_left_back_rgb_led - segments: - - id: neopixels - from: 1 - to: 1 - effects: - - pulse: - name: Breathe - transition_length: 1s - update_interval: 1.5s - min_brightness: 20% - max_brightness: 100% - - pulse: - name: Blink - transition_length: 50ms - update_interval: 500ms -- platform: partition - name: BAR 1 Right RGB Front - id: bar1_right_front_rgb_led - segments: - - id: neopixels - from: 2 - to: 2 - effects: - - pulse: - name: Breathe - transition_length: 1s - update_interval: 1.5s - min_brightness: 20% - max_brightness: 100% - - pulse: - name: Blink - transition_length: 50ms - update_interval: 500ms -- platform: partition - name: BAR 1 Right RGB Back - id: bar1_right_back_rgb_led - segments: - - id: neopixels - from: 3 - to: 3 - effects: - - pulse: - name: Breathe - transition_length: 1s - update_interval: 1.5s - min_brightness: 20% - max_brightness: 100% - - pulse: - name: Blink - transition_length: 50ms - update_interval: 500ms -- platform: partition - name: BAR 2 Left RGB Front - id: bar2_left_front_rgb_led - segments: - - id: neopixels - from: 4 - to: 4 - effects: - - pulse: - name: Breathe - transition_length: 1s - update_interval: 1.5s - min_brightness: 20% - max_brightness: 100% - - pulse: - name: Blink - transition_length: 50ms - update_interval: 500ms -- platform: partition - name: BAR 2 Left RGB Back - id: bar2_left_back_rgb_led - segments: - - id: neopixels - from: 5 - to: 5 - effects: - - pulse: - name: Breathe - transition_length: 1s - update_interval: 1.5s - min_brightness: 20% - max_brightness: 100% - - pulse: - name: Blink - transition_length: 50ms - update_interval: 500ms -- platform: partition - name: BAR 2 Right RGB Front - id: bar2_right_front_rgb_led - segments: - - id: neopixels - from: 6 - to: 6 - effects: - - pulse: - name: Breathe - transition_length: 1s - update_interval: 1.5s - min_brightness: 20% - max_brightness: 100% - - pulse: - name: Blink - transition_length: 50ms - update_interval: 500ms -- platform: partition - name: BAR 2 Right RGB Back - id: bar2_right_back_rgb_led - segments: - - id: neopixels - from: 7 - to: 7 - effects: - - pulse: - name: Breathe - transition_length: 1s - update_interval: 1.5s - min_brightness: 20% - max_brightness: 100% - - pulse: - name: Blink - transition_length: 50ms - update_interval: 500ms -- platform: partition - name: BAR 3 Left RGB Front - id: bar3_left_front_rgb_led - segments: - - id: neopixels - from: 8 - to: 8 - effects: - - pulse: - name: Breathe - transition_length: 1s - update_interval: 1.5s - min_brightness: 20% - max_brightness: 100% - - pulse: - name: Blink - transition_length: 50ms - update_interval: 500ms -- platform: partition - name: BAR 3 Left RGB Back - id: bar3_left_back_rgb_led - segments: - - id: neopixels - from: 9 - to: 9 - effects: - - pulse: - name: Breathe - transition_length: 1s - update_interval: 1.5s - min_brightness: 20% - max_brightness: 100% - - pulse: - name: Blink - transition_length: 50ms - update_interval: 500ms -- platform: partition - name: BAR 3 Right RGB Front - id: bar3_right_front_rgb_led - segments: - - id: neopixels - from: 10 - to: 10 - effects: - - pulse: - name: Breathe - transition_length: 1s - update_interval: 1.5s - min_brightness: 20% - max_brightness: 100% - - pulse: - name: Blink - transition_length: 50ms - update_interval: 500ms -- platform: partition - name: BAR 3 Right RGB Back - id: bar3_right_back_rgb_led - segments: - - id: neopixels - from: 11 - to: 11 - effects: - - pulse: - name: Breathe - transition_length: 1s - update_interval: 1.5s - min_brightness: 20% - max_brightness: 100% - - pulse: - name: Blink - transition_length: 50ms - update_interval: 500ms -output: -- platform: ledc - pin: GPIO3 - id: display_backlight_pwm - inverted: true - max_power: 1 - frequency: 500Hz -- platform: ledc - pin: GPIO46 - id: bars_backlight_pwm - inverted: true - max_power: 0.6 - frequency: 500Hz -- platform: gpio - id: bar1_left_cs_pin - pin: - mcp23xxx: base_J1 - number: 5 - mode: - output: true - inverted: true -- platform: gpio - id: bar1_right_cs_pin - pin: - mcp23xxx: base_J1 - number: 1 - mode: - output: true - inverted: true -- platform: gpio - id: bar2_left_cs_pin - pin: - mcp23xxx: base_J2 - number: 5 - mode: - output: true - inverted: true -- platform: gpio - id: bar2_right_cs_pin - pin: - mcp23xxx: base_J2 - number: 1 - mode: - output: true - inverted: true -- platform: gpio - id: bar3_left_cs_pin - pin: - mcp23xxx: base_J3 - number: 5 - mode: - output: true - inverted: true -- platform: gpio - id: bar3_right_cs_pin - pin: - mcp23xxx: base_J3 - number: 1 - mode: - output: true - inverted: true -display: -- id: display_disp - platform: ili9xxx - model: ili9341 - invert_colors: false - color_order: rgb - update_interval: 30s - dc_pin: - number: GPIO7 - allow_other_uses: true - cs_pin: - mcp23xxx: base_J0 - number: 5 - mode: - output: true - inverted: false - show_test_card: false - dimensions: - height: 240 - width: 320 - rotation: 180 - lambda: id(display_script_draw).execute(); -- id: bar_display - platform: ili9xxx - model: ST7735 - color_order: bgr - update_interval: never - dc_pin: - number: GPIO7 - allow_other_uses: true - invert_colors: false - show_test_card: false - auto_clear_enabled: false - dimensions: - height: 160 - width: 80 - offset_width: 24 -mcp23008: -- id: base_J0 - i2c_id: buttons - address: '32' -- id: base_J3 - i2c_id: buttons - address: 35 -- id: base_J2 - i2c_id: buttons - address: 34 -- id: base_J1 - i2c_id: buttons - address: 33 -binary_sensor: -- platform: gpio - id: display_left_btn - name: Main Display button Left - internal: true - pin: - mcp23xxx: base_J0 - number: 6 - mode: INPUT_PULLUP - inverted: true - filters: - - delayed_off: 10ms - on_multi_click: - - timing: - - ON for at most 300ms - - OFF for at least 0.2s - then: - - script.execute: prev_page - - timing: - - ON for at most 300ms - - OFF for at most 0.2s - - ON for at most 300ms - - OFF for at least 0.2s - then: - - script.execute: - id: event_trigger_feedback - event_id: display_left_btn_event - led_id: !lambda "return nullptr;" - r: 0 - g: 0 - b: 255 - event_msg: !lambda "return \"double_click \" + id(active_page_name);" - - timing: - - ON for at least 1s - - OFF for at least 0.2s - then: - - script.execute: - id: event_trigger_feedback - event_id: display_left_btn_event - led_id: !lambda "return nullptr;" - r: 255 - g: 0 - b: 255 - event_msg: !lambda "return \"hold \" + id(active_page_name);" -- platform: gpio - id: display_right_btn - name: Main Display button Right - internal: true - pin: - mcp23xxx: base_J0 - number: 2 - mode: INPUT_PULLUP - inverted: true - filters: - - delayed_off: 10ms - on_multi_click: - - timing: - - ON for at most 300ms - - OFF for at least 0.2s - then: - - script.execute: next_page - - timing: - - ON for at most 300ms - - OFF for at most 0.2s - - ON for at most 300ms - - OFF for at least 0.2s - then: - - script.execute: - id: event_trigger_feedback - event_id: display_right_btn_event - led_id: !lambda "return nullptr;" - r: 0 - g: 0 - b: 255 - event_msg: !lambda "return \"double_click \" + id(active_page_name);" - - timing: - - ON for at least 1s - - OFF for at least 0.2s - then: - - script.execute: - id: event_trigger_feedback - event_id: display_right_btn_event - led_id: !lambda "return nullptr;" - r: 255 - g: 0 - b: 255 - event_msg: !lambda "return \"hold \" + id(active_page_name);" -- platform: gpio - id: bar1_left_btn - name: BAR 1 button Left - internal: true - pin: - mcp23xxx: base_J1 - number: 6 - mode: INPUT_PULLUP - inverted: true - filters: - - delayed_off: 10ms - on_multi_click: - - timing: - - ON for at most 300ms - - OFF for at least 0.2s - then: - - script.execute: - id: event_trigger_feedback - event_id: bar1_left_btn_event - led_id: bar1_left_front_rgb_led - r: 0 - g: 255 - b: 0 - event_msg: !lambda "return \"click \" + id(active_page_name);" - - timing: - - ON for at most 300ms - - OFF for at most 0.2s - - ON for at most 300ms - - OFF for at least 0.2s - then: - - script.execute: - id: event_trigger_feedback - event_id: bar1_left_btn_event - led_id: bar1_left_front_rgb_led - r: 0 - g: 0 - b: 255 - event_msg: !lambda "return \"double_click \" + id(active_page_name);" - - timing: - - ON for at least 1s - - OFF for at least 0.2s - then: - - script.execute: - id: event_trigger_feedback - event_id: bar1_left_btn_event - led_id: bar1_left_front_rgb_led - r: 255 - g: 0 - b: 255 - event_msg: !lambda "return \"hold \" + id(active_page_name);" -- platform: gpio - id: bar1_right_btn - name: BAR 1 button Right - internal: true - pin: - mcp23xxx: base_J1 - number: 2 - mode: INPUT_PULLUP - inverted: true - filters: - - delayed_off: 10ms - on_multi_click: - - timing: - - ON for at most 300ms - - OFF for at least 0.2s - then: - - script.execute: - id: event_trigger_feedback - event_id: bar1_right_btn_event - led_id: bar1_right_front_rgb_led - r: 0 - g: 255 - b: 0 - event_msg: !lambda "return \"click \" + id(active_page_name);" - - timing: - - ON for at most 300ms - - OFF for at most 0.2s - - ON for at most 300ms - - OFF for at least 0.2s - then: - - script.execute: - id: event_trigger_feedback - event_id: bar1_right_btn_event - led_id: bar1_right_front_rgb_led - r: 0 - g: 0 - b: 255 - event_msg: !lambda "return \"double_click \" + id(active_page_name);" - - timing: - - ON for at least 1s - - OFF for at least 0.2s - then: - - script.execute: - id: event_trigger_feedback - event_id: bar1_right_btn_event - led_id: bar1_right_front_rgb_led - r: 255 - g: 0 - b: 255 - event_msg: !lambda "return \"hold \" + id(active_page_name);" -- platform: gpio - id: bar2_left_btn - name: BAR 2 button Left - internal: true - pin: - mcp23xxx: base_J2 - number: 6 - mode: INPUT_PULLUP - inverted: true - filters: - - delayed_off: 10ms - on_multi_click: - - timing: - - ON for at most 300ms - - OFF for at least 0.2s - then: - - script.execute: - id: event_trigger_feedback - event_id: bar2_left_btn_event - led_id: bar2_left_front_rgb_led - r: 0 - g: 255 - b: 0 - event_msg: !lambda "return \"click \" + id(active_page_name);" - - timing: - - ON for at most 300ms - - OFF for at most 0.2s - - ON for at most 300ms - - OFF for at least 0.2s - then: - - script.execute: - id: event_trigger_feedback - event_id: bar2_left_btn_event - led_id: bar2_left_front_rgb_led - r: 0 - g: 0 - b: 255 - event_msg: !lambda "return \"double_click \" + id(active_page_name);" - - timing: - - ON for at least 1s - - OFF for at least 0.2s - then: - - script.execute: - id: event_trigger_feedback - event_id: bar2_left_btn_event - led_id: bar2_left_front_rgb_led - r: 255 - g: 0 - b: 255 - event_msg: !lambda "return \"hold \" + id(active_page_name);" -- platform: gpio - id: bar2_right_btn - name: BAR 2 button Right - internal: true - pin: - mcp23xxx: base_J2 - number: 2 - mode: INPUT_PULLUP - inverted: true - filters: - - delayed_off: 10ms - on_multi_click: - - timing: - - ON for at most 300ms - - OFF for at least 0.2s - then: - - script.execute: - id: event_trigger_feedback - event_id: bar2_right_btn_event - led_id: bar2_right_front_rgb_led - r: 0 - g: 255 - b: 0 - event_msg: !lambda "return \"click \" + id(active_page_name);" - - timing: - - ON for at most 300ms - - OFF for at most 0.2s - - ON for at most 300ms - - OFF for at least 0.2s - then: - - script.execute: - id: event_trigger_feedback - event_id: bar2_right_btn_event - led_id: bar2_right_front_rgb_led - r: 0 - g: 0 - b: 255 - event_msg: !lambda "return \"double_click \" + id(active_page_name);" - - timing: - - ON for at least 1s - - OFF for at least 0.2s - then: - - script.execute: - id: event_trigger_feedback - event_id: bar2_right_btn_event - led_id: bar2_right_front_rgb_led - r: 255 - g: 0 - b: 255 - event_msg: !lambda "return \"hold \" + id(active_page_name);" -- platform: gpio - id: bar3_left_btn - name: BAR 3 button Left - internal: true - pin: - mcp23xxx: base_J3 - number: 6 - mode: INPUT_PULLUP - inverted: true - filters: - - delayed_off: 10ms - on_multi_click: - - timing: - - ON for at most 300ms - - OFF for at least 0.2s - then: - - script.execute: - id: event_trigger_feedback - event_id: bar3_left_btn_event - led_id: bar3_left_front_rgb_led - r: 0 - g: 255 - b: 0 - event_msg: !lambda "return \"click \" + id(active_page_name);" - - timing: - - ON for at most 300ms - - OFF for at most 0.2s - - ON for at most 300ms - - OFF for at least 0.2s - then: - - script.execute: - id: event_trigger_feedback - event_id: bar3_left_btn_event - led_id: bar3_left_front_rgb_led - r: 0 - g: 0 - b: 255 - event_msg: !lambda "return \"double_click \" + id(active_page_name);" - - timing: - - ON for at least 1s - - OFF for at least 0.2s - then: - - script.execute: - id: event_trigger_feedback - event_id: bar3_left_btn_event - led_id: bar3_left_front_rgb_led - r: 255 - g: 0 - b: 255 - event_msg: !lambda "return \"hold \" + id(active_page_name);" -- platform: gpio - id: bar3_right_btn - name: BAR 3 button Right - internal: true - pin: - mcp23xxx: base_J3 - number: 2 - mode: INPUT_PULLUP - inverted: true - filters: - - delayed_off: 10ms - on_multi_click: - - timing: - - ON for at most 300ms - - OFF for at least 0.2s - then: - - script.execute: - id: event_trigger_feedback - event_id: bar3_right_btn_event - led_id: bar3_right_front_rgb_led - r: 0 - g: 255 - b: 0 - event_msg: !lambda "return \"click \" + id(active_page_name);" - - timing: - - ON for at most 300ms - - OFF for at most 0.2s - - ON for at most 300ms - - OFF for at least 0.2s - then: - - script.execute: - id: event_trigger_feedback - event_id: bar3_right_btn_event - led_id: bar3_right_front_rgb_led - r: 0 - g: 0 - b: 255 - event_msg: !lambda "return \"double_click \" + id(active_page_name);" - - timing: - - ON for at least 1s - - OFF for at least 0.2s - then: - - script.execute: - id: event_trigger_feedback - event_id: bar3_right_btn_event - led_id: bar3_right_front_rgb_led - r: 255 - g: 0 - b: 255 - event_msg: !lambda "return \"hold \" + id(active_page_name);" -substitutions: - button_events: - - click Start - - double_click Start - - hold Start - - click Music - - double_click Music - - hold Music - - click Lights - - double_click Lights - - hold Lights - - click Security - - double_click Security - - hold Security event: -- platform: template - id: display_left_btn_event - name: DISPLAY Button Left - device_class: button - event_types: ${button_events} -- platform: template - id: display_right_btn_event - name: DISPLAY Button Right - device_class: button - event_types: ${button_events} -- platform: template - id: bar1_left_btn_event - name: BAR 1 Button Left - device_class: button - event_types: ${button_events} -- platform: template - id: bar1_right_btn_event - name: BAR 1 Button Right - device_class: button - event_types: ${button_events} -- platform: template - id: bar2_left_btn_event - name: BAR 2 Button Left - device_class: button - event_types: ${button_events} -- platform: template - id: bar2_right_btn_event - name: BAR 2 Button Right - device_class: button - event_types: ${button_events} -- platform: template - id: bar3_left_btn_event - name: BAR 3 Button Left - device_class: button - event_types: ${button_events} -- platform: template - id: bar3_right_btn_event - name: BAR 3 Button Right - device_class: button - event_types: ${button_events} +- id: !extend 'display_left_btn_event' + on_event: + then: + - lambda: |- + if(event_type == "click Start"){ + // Handle this event locally + } + if(event_type == "double_click Start"){ + // Handle this event locally + } + if(event_type == "triple_click Start"){ + // Handle this event locally + } + if(event_type == "hold Start"){ + // Handle this event locally + } + if(event_type == "click Music"){ + // Handle this event locally + } + if(event_type == "double_click Music"){ + // Handle this event locally + } + if(event_type == "triple_click Music"){ + // Handle this event locally + } + if(event_type == "hold Music"){ + // Handle this event locally + } + if(event_type == "click Lights"){ + // Handle this event locally + } + if(event_type == "double_click Lights"){ + // Handle this event locally + } + if(event_type == "triple_click Lights"){ + // Handle this event locally + } + if(event_type == "hold Lights"){ + // Handle this event locally + } + if(event_type == "click Security"){ + // Handle this event locally + } + if(event_type == "double_click Security"){ + // Handle this event locally + } + if(event_type == "triple_click Security"){ + // Handle this event locally + } + if(event_type == "hold Security"){ + // Handle this event locally + } +- id: !extend 'display_right_btn_event' + on_event: + then: + - lambda: |- + if(event_type == "click Start"){ + // Handle this event locally + } + if(event_type == "double_click Start"){ + // Handle this event locally + } + if(event_type == "triple_click Start"){ + // Handle this event locally + } + if(event_type == "hold Start"){ + // Handle this event locally + } + if(event_type == "click Music"){ + // Handle this event locally + } + if(event_type == "double_click Music"){ + // Handle this event locally + } + if(event_type == "triple_click Music"){ + // Handle this event locally + } + if(event_type == "hold Music"){ + // Handle this event locally + } + if(event_type == "click Lights"){ + // Handle this event locally + } + if(event_type == "double_click Lights"){ + // Handle this event locally + } + if(event_type == "triple_click Lights"){ + // Handle this event locally + } + if(event_type == "hold Lights"){ + // Handle this event locally + } + if(event_type == "click Security"){ + // Handle this event locally + } + if(event_type == "double_click Security"){ + // Handle this event locally + } + if(event_type == "triple_click Security"){ + // Handle this event locally + } + if(event_type == "hold Security"){ + // Handle this event locally + } +- id: !extend 'bar1_left_btn_event' + on_event: + then: + - lambda: |- + if(event_type == "click Start"){ + // Handle this event locally + } + if(event_type == "double_click Start"){ + // Handle this event locally + } + if(event_type == "triple_click Start"){ + // Handle this event locally + } + if(event_type == "hold Start"){ + // Handle this event locally + } + if(event_type == "click Music"){ + // Handle this event locally + } + if(event_type == "double_click Music"){ + // Handle this event locally + } + if(event_type == "triple_click Music"){ + // Handle this event locally + } + if(event_type == "hold Music"){ + // Handle this event locally + } + if(event_type == "click Lights"){ + // Handle this event locally + } + if(event_type == "double_click Lights"){ + // Handle this event locally + } + if(event_type == "triple_click Lights"){ + // Handle this event locally + } + if(event_type == "hold Lights"){ + // Handle this event locally + } + if(event_type == "click Security"){ + // Handle this event locally + } + if(event_type == "double_click Security"){ + // Handle this event locally + } + if(event_type == "triple_click Security"){ + // Handle this event locally + } + if(event_type == "hold Security"){ + // Handle this event locally + } +- id: !extend 'bar1_right_btn_event' + on_event: + then: + - lambda: |- + if(event_type == "click Start"){ + // Handle this event locally + } + if(event_type == "double_click Start"){ + // Handle this event locally + } + if(event_type == "triple_click Start"){ + // Handle this event locally + } + if(event_type == "hold Start"){ + // Handle this event locally + } + if(event_type == "click Music"){ + // Handle this event locally + } + if(event_type == "double_click Music"){ + // Handle this event locally + } + if(event_type == "triple_click Music"){ + // Handle this event locally + } + if(event_type == "hold Music"){ + // Handle this event locally + } + if(event_type == "click Lights"){ + // Handle this event locally + } + if(event_type == "double_click Lights"){ + // Handle this event locally + } + if(event_type == "triple_click Lights"){ + // Handle this event locally + } + if(event_type == "hold Lights"){ + // Handle this event locally + } + if(event_type == "click Security"){ + // Handle this event locally + } + if(event_type == "double_click Security"){ + // Handle this event locally + } + if(event_type == "triple_click Security"){ + // Handle this event locally + } + if(event_type == "hold Security"){ + // Handle this event locally + } +- id: !extend 'bar2_left_btn_event' + on_event: + then: + - lambda: |- + if(event_type == "click Start"){ + // Handle this event locally + } + if(event_type == "double_click Start"){ + // Handle this event locally + } + if(event_type == "triple_click Start"){ + // Handle this event locally + } + if(event_type == "hold Start"){ + // Handle this event locally + } + if(event_type == "click Music"){ + // Handle this event locally + } + if(event_type == "double_click Music"){ + // Handle this event locally + } + if(event_type == "triple_click Music"){ + // Handle this event locally + } + if(event_type == "hold Music"){ + // Handle this event locally + } + if(event_type == "click Lights"){ + // Handle this event locally + } + if(event_type == "double_click Lights"){ + // Handle this event locally + } + if(event_type == "triple_click Lights"){ + // Handle this event locally + } + if(event_type == "hold Lights"){ + // Handle this event locally + } + if(event_type == "click Security"){ + // Handle this event locally + } + if(event_type == "double_click Security"){ + // Handle this event locally + } + if(event_type == "triple_click Security"){ + // Handle this event locally + } + if(event_type == "hold Security"){ + // Handle this event locally + } +- id: !extend 'bar2_right_btn_event' + on_event: + then: + - lambda: |- + if(event_type == "click Start"){ + // Handle this event locally + } + if(event_type == "double_click Start"){ + // Handle this event locally + } + if(event_type == "triple_click Start"){ + // Handle this event locally + } + if(event_type == "hold Start"){ + // Handle this event locally + } + if(event_type == "click Music"){ + // Handle this event locally + } + if(event_type == "double_click Music"){ + // Handle this event locally + } + if(event_type == "triple_click Music"){ + // Handle this event locally + } + if(event_type == "hold Music"){ + // Handle this event locally + } + if(event_type == "click Lights"){ + // Handle this event locally + } + if(event_type == "double_click Lights"){ + // Handle this event locally + } + if(event_type == "triple_click Lights"){ + // Handle this event locally + } + if(event_type == "hold Lights"){ + // Handle this event locally + } + if(event_type == "click Security"){ + // Handle this event locally + } + if(event_type == "double_click Security"){ + // Handle this event locally + } + if(event_type == "triple_click Security"){ + // Handle this event locally + } + if(event_type == "hold Security"){ + // Handle this event locally + } +- id: !extend 'bar3_left_btn_event' + on_event: + then: + - lambda: |- + if(event_type == "click Start"){ + // Handle this event locally + } + if(event_type == "double_click Start"){ + // Handle this event locally + } + if(event_type == "triple_click Start"){ + // Handle this event locally + } + if(event_type == "hold Start"){ + // Handle this event locally + } + if(event_type == "click Music"){ + // Handle this event locally + } + if(event_type == "double_click Music"){ + // Handle this event locally + } + if(event_type == "triple_click Music"){ + // Handle this event locally + } + if(event_type == "hold Music"){ + // Handle this event locally + } + if(event_type == "click Lights"){ + // Handle this event locally + } + if(event_type == "double_click Lights"){ + // Handle this event locally + } + if(event_type == "triple_click Lights"){ + // Handle this event locally + } + if(event_type == "hold Lights"){ + // Handle this event locally + } + if(event_type == "click Security"){ + // Handle this event locally + } + if(event_type == "double_click Security"){ + // Handle this event locally + } + if(event_type == "triple_click Security"){ + // Handle this event locally + } + if(event_type == "hold Security"){ + // Handle this event locally + } +- id: !extend 'bar3_right_btn_event' + on_event: + then: + - lambda: |- + if(event_type == "click Start"){ + // Handle this event locally + } + if(event_type == "double_click Start"){ + // Handle this event locally + } + if(event_type == "triple_click Start"){ + // Handle this event locally + } + if(event_type == "hold Start"){ + // Handle this event locally + } + if(event_type == "click Music"){ + // Handle this event locally + } + if(event_type == "double_click Music"){ + // Handle this event locally + } + if(event_type == "triple_click Music"){ + // Handle this event locally + } + if(event_type == "hold Music"){ + // Handle this event locally + } + if(event_type == "click Lights"){ + // Handle this event locally + } + if(event_type == "double_click Lights"){ + // Handle this event locally + } + if(event_type == "triple_click Lights"){ + // Handle this event locally + } + if(event_type == "hold Lights"){ + // Handle this event locally + } + if(event_type == "click Security"){ + // Handle this event locally + } + if(event_type == "double_click Security"){ + // Handle this event locally + } + if(event_type == "triple_click Security"){ + // Handle this event locally + } + if(event_type == "hold Security"){ + // Handle this event locally + } diff --git a/esphome/latest_generated_full_base.yaml b/esphome/latest_generated_full_base.yaml new file mode 100644 index 0000000..a271d41 --- /dev/null +++ b/esphome/latest_generated_full_base.yaml @@ -0,0 +1,1645 @@ +# Generated by ESPHome for Buttonplus +# File: buttonplus_base.yaml +# Options: +# - device: buttonplus +# - version: 1 +# - bars: 3 +# - pages: Start, Music, Lights, Security + +esphome: + on_boot: + priority: -100 + then: + - script.execute: setup_bar_displays + - script.execute: test_all_leds + - script.execute: bar1_left_script_update + - script.execute: bar1_right_script_update + - script.execute: bar2_left_script_update + - script.execute: bar2_right_script_update + - script.execute: bar3_left_script_update + - script.execute: bar3_right_script_update +esp32: + variant: ESP32S3 + board: esp32-s3-devkitc1-n16r8 + flash_size: 16MB + framework: + type: esp-idf +psram: + mode: octal + speed: 80MHz +api: + actions: + - action: notification + variables: + flash_leds: bool + message: string + then: + - globals.set: + id: internal_notify + value: !lambda "return message;" + - script.execute: display_script_update + - while: + condition: + lambda: return (flash_leds && id(internal_notify).size() > 0); + then: + - light.turn_on: + id: internal_notification_rgb + brightness: 100% + red: 100% + green: 100% + blue: 100% + flash_length: 1s + - delay: 2s + - wait_until: + condition: + lambda: return id(internal_notify).size() == 0; + - light.control: + id: bar1_left_back_rgb_led + - light.control: + id: bar1_right_back_rgb_led + - action: notification_clear + then: + - globals.set: + id: internal_notify + value: !lambda "return std::string();" + - script.execute: display_script_update + - action: toast + variables: + timeout: int + message: string + then: + - globals.set: + id: internal_toast + value: !lambda "return message;" + - script.execute: display_script_update + - delay: !lambda "return timeout * 1000;" + - globals.set: + id: internal_toast + value: !lambda "return std::string();" + - script.execute: display_script_update + - action: clear_image + then: + - globals.set: + id: show_web_image + value: !lambda "return false;" + - online_image.release: web_image + - globals.set: + id: show_ha_image + value: !lambda "return false;" + - online_image.release: ha_image + - script.execute: display_script_update + - action: show_web_jpeg_image + variables: + timeout: int + image_url: string + then: + - online_image.set_url: + id: web_image + url: !lambda "return image_url;" + - globals.set: + id: show_web_image + value: !lambda "return true;" + - delay: !lambda "return timeout * 1000;" + - globals.set: + id: show_web_image + value: !lambda "return false;" + - online_image.release: web_image + - script.execute: display_script_update + - action: show_ha_jpeg_image + variables: + timeout: int + image_url: string + then: + - online_image.set_url: + id: ha_image + url: !lambda "return image_url;" + - globals.set: + id: show_ha_image + value: !lambda "return true;" + - delay: !lambda "return timeout * 1000;" + - globals.set: + id: show_ha_image + value: !lambda "return false;" + - online_image.release: ha_image + - script.execute: display_script_update + - action: set_page_led + variables: + led: string + page_name: string + r: float + g: float + b: float + brightness: float + effect: string + then: + - if: + condition: + lambda: |- + // Validate page_name if provided + auto it = std::find(id(page_names).begin(), id(page_names).end(), page_name); + if (it == id(page_names).end()) return true; + return false; + then: + - api.respond: + success: false + error_message: Invalid page_name (not found). + else: + - if: + condition: + lambda: return id(internal_page_leds_r).find(led) == id(internal_page_leds_r).end(); + then: + - api.respond: + success: false + error_message: The requested LED does not exist. + else: + - lambda: |- + // Resolve final page index + auto it = std::find(id(page_names).begin(), id(page_names).end(), page_name); + int page_num = std::distance(id(page_names).begin(), it); + + id(internal_page_leds_r)[led][page_num] = r; + id(internal_page_leds_g)[led][page_num] = g; + id(internal_page_leds_b)[led][page_num] = b; + id(internal_page_leds_brightness)[led][page_num] = brightness; + id(internal_page_leds_effect)[led][page_num] = effect; + - api.respond: + success: true + - if: + condition: + lambda: |- + auto it = std::find(id(page_names).begin(), id(page_names).end(), page_name); + int page_num = std::distance(id(page_names).begin(), it); + return page_num == id(active_page_nr); + then: + - script.execute: update_all_page_leds + - action: get_info + supports_response: only + then: + - api.respond: + data: !lambda |- + root["num_pages"] = id(nr_of_pages); + JsonArray pages = root["page_names"].to(); + for(const std::string& page_name : id(page_names)){ + pages.add(page_name); + } + JsonArray effects = root["effects"].to(); + JsonArray leds = root["leds"].to(); + leds.add("bar1_left_front_rgb_led"); + leds.add("bar1_right_front_rgb_led"); + leds.add("bar1_left_back_rgb_led"); + leds.add("bar1_right_back_rgb_led"); + leds.add("bar2_left_front_rgb_led"); + leds.add("bar2_right_front_rgb_led"); + leds.add("bar2_left_back_rgb_led"); + leds.add("bar2_right_back_rgb_led"); + leds.add("bar3_left_front_rgb_led"); + leds.add("bar3_right_front_rgb_led"); + leds.add("bar3_left_back_rgb_led"); + leds.add("bar3_right_back_rgb_led"); + effects.add("None"); + effects.add("Breathe"); + effects.add("Blink"); + - action: reset_all_page_leds + then: + - lambda: |- + for(auto &kv : id(internal_page_leds_r)) std::fill(kv.second.begin(), kv.second.end(), 0.0); + for(auto &kv : id(internal_page_leds_g)) std::fill(kv.second.begin(), kv.second.end(), 0.0); + for(auto &kv : id(internal_page_leds_b)) std::fill(kv.second.begin(), kv.second.end(), 0.0); + for(auto &kv : id(internal_page_leds_brightness)) std::fill(kv.second.begin(), kv.second.end(), 0.0); + for(auto &kv : id(internal_page_leds_effect)) std::fill(kv.second.begin(), kv.second.end(), std::string()); + - script.execute: update_all_page_leds +color: +- id: white + hex: FFFFFF +- id: black + hex: '000000' +- id: grey + hex: A6A6A6 +font: +- file: + url: https://github.com/web-fonts/ttf/raw/refs/heads/master/bpg-ingiri-arial-webfont.ttf + type: web + id: font_arial20 + size: 20 + bpp: 4 +globals: +- id: nr_of_pages + type: int + initial_value: '4' +- id: page_names + type: std::vector + initial_value: '{"Start", "Music", "Lights", "Security"}' +- id: active_page_nr + type: int + initial_value: '0' +- id: active_page_name + type: std::string + initial_value: '' +- id: internal_notify + type: std::string + initial_value: '' +- id: internal_toast + type: std::string + initial_value: '' +- id: show_web_image + type: bool + initial_value: '''false''' +- id: show_ha_image + type: bool + initial_value: '''false''' +- id: internal_page_leds_r + type: std::unordered_map> + initial_value: '{{"bar1_left_front_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar1_right_front_rgb_led", + {0.0, 0.0, 0.0, 0.0}}, {"bar1_left_back_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar1_right_back_rgb_led", + {0.0, 0.0, 0.0, 0.0}}, {"bar2_left_front_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar2_right_front_rgb_led", + {0.0, 0.0, 0.0, 0.0}}, {"bar2_left_back_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar2_right_back_rgb_led", + {0.0, 0.0, 0.0, 0.0}}, {"bar3_left_front_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar3_right_front_rgb_led", + {0.0, 0.0, 0.0, 0.0}}, {"bar3_left_back_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar3_right_back_rgb_led", + {0.0, 0.0, 0.0, 0.0}}}' +- id: internal_page_leds_g + type: std::unordered_map> + initial_value: '{{"bar1_left_front_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar1_right_front_rgb_led", + {0.0, 0.0, 0.0, 0.0}}, {"bar1_left_back_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar1_right_back_rgb_led", + {0.0, 0.0, 0.0, 0.0}}, {"bar2_left_front_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar2_right_front_rgb_led", + {0.0, 0.0, 0.0, 0.0}}, {"bar2_left_back_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar2_right_back_rgb_led", + {0.0, 0.0, 0.0, 0.0}}, {"bar3_left_front_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar3_right_front_rgb_led", + {0.0, 0.0, 0.0, 0.0}}, {"bar3_left_back_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar3_right_back_rgb_led", + {0.0, 0.0, 0.0, 0.0}}}' +- id: internal_page_leds_b + type: std::unordered_map> + initial_value: '{{"bar1_left_front_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar1_right_front_rgb_led", + {0.0, 0.0, 0.0, 0.0}}, {"bar1_left_back_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar1_right_back_rgb_led", + {0.0, 0.0, 0.0, 0.0}}, {"bar2_left_front_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar2_right_front_rgb_led", + {0.0, 0.0, 0.0, 0.0}}, {"bar2_left_back_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar2_right_back_rgb_led", + {0.0, 0.0, 0.0, 0.0}}, {"bar3_left_front_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar3_right_front_rgb_led", + {0.0, 0.0, 0.0, 0.0}}, {"bar3_left_back_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar3_right_back_rgb_led", + {0.0, 0.0, 0.0, 0.0}}}' +- id: internal_page_leds_brightness + type: std::unordered_map> + initial_value: '{{"bar1_left_front_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar1_right_front_rgb_led", + {0.0, 0.0, 0.0, 0.0}}, {"bar1_left_back_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar1_right_back_rgb_led", + {0.0, 0.0, 0.0, 0.0}}, {"bar2_left_front_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar2_right_front_rgb_led", + {0.0, 0.0, 0.0, 0.0}}, {"bar2_left_back_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar2_right_back_rgb_led", + {0.0, 0.0, 0.0, 0.0}}, {"bar3_left_front_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar3_right_front_rgb_led", + {0.0, 0.0, 0.0, 0.0}}, {"bar3_left_back_rgb_led", {0.0, 0.0, 0.0, 0.0}}, {"bar3_right_back_rgb_led", + {0.0, 0.0, 0.0, 0.0}}}' +- id: internal_page_leds_effect + type: std::unordered_map> + initial_value: '{{"bar1_left_front_rgb_led", {"", "", "", ""}}, {"bar1_right_front_rgb_led", + {"", "", "", ""}}, {"bar1_left_back_rgb_led", {"", "", "", ""}}, {"bar1_right_back_rgb_led", + {"", "", "", ""}}, {"bar2_left_front_rgb_led", {"", "", "", ""}}, {"bar2_right_front_rgb_led", + {"", "", "", ""}}, {"bar2_left_back_rgb_led", {"", "", "", ""}}, {"bar2_right_back_rgb_led", + {"", "", "", ""}}, {"bar3_left_front_rgb_led", {"", "", "", ""}}, {"bar3_right_front_rgb_led", + {"", "", "", ""}}, {"bar3_left_back_rgb_led", {"", "", "", ""}}, {"bar3_right_back_rgb_led", + {"", "", "", ""}}}' +sensor: +- platform: template + name: Active Page Number + icon: mdi:order-numeric-ascending + id: active_page_nr_ha + update_interval: never + accuracy_decimals: 0 +- platform: sts3x + address: 74 + id: buttonplus_temperature + i2c_id: sensors + name: Temperature + update_interval: 10s + unit_of_measurement: "\xB0C" + icon: mdi:thermometer + device_class: temperature + state_class: measurement + accuracy_decimals: 1 +- platform: ltr_als_ps + address: 41 + i2c_id: sensors + update_interval: 10s + type: ALS + auto_mode: false + gain: 96x + ambient_light: + name: Ambient Light + id: buttonplus_ambientlight + unit_of_measurement: lx + icon: mdi:brightness-6 + device_class: illuminance + state_class: measurement + accuracy_decimals: 1 +text_sensor: +- platform: template + name: Active Page Name + icon: mdi:order-alphabetical-ascending + id: active_page_name_ha + update_interval: never +number: +- platform: template + name: Page Selector + id: pageselector + optimistic: true + min_value: 0 + max_value: 3 + step: 1 + on_value: + then: + - lambda: |- + if(id(pageselector).state >= id(nr_of_pages)) { + id(active_page_nr) = id(nr_of_pages)-1; + } else { + id(active_page_nr) = id(pageselector).state; + } + - script.execute: set_page +script: +- id: next_page + then: + - if: + condition: + - lambda: !lambda "return id(internal_notify).size() > 0;" + then: + - globals.set: + id: internal_notify + value: !lambda "return std::string();" + - script.execute: display_script_update + - script.stop: next_page + - lambda: |- + id(active_page_nr) = id(active_page_nr) + 1; + if(id(active_page_nr) >= id(nr_of_pages)) { + id(active_page_nr) = 0; + } + - script.execute: update_page +- id: prev_page + then: + - if: + condition: + - lambda: !lambda "return id(internal_notify).size() > 0;" + then: + - globals.set: + id: internal_notify + value: !lambda "return std::string();" + - script.execute: display_script_update + - script.stop: next_page + - lambda: |- + id(active_page_nr) = id(active_page_nr) - 1; + if(id(active_page_nr) < 0) { + id(active_page_nr) = id(nr_of_pages) - 1; + } + - script.execute: update_page +- id: set_page + then: + - lambda: id(active_page_name) = id(page_names)[id(active_page_nr)]; + - sensor.template.publish: + id: active_page_nr_ha + state: !lambda "return id(active_page_nr);" + - text_sensor.template.publish: + id: active_page_name_ha + state: !lambda "return id(active_page_name);" + - script.execute: update_all + - script.execute: update_all_page_leds +- id: update_page + then: + - number.set: + id: pageselector + value: !lambda "return id(active_page_nr);" + - script.execute: set_page +- id: test_all_leds + then: + - delay: 1000ms + - light.turn_on: + id: internal_all_rgb + red: 100% + green: 0% + blue: 0% + flash_length: 100ms + - delay: 200ms + - light.turn_on: + id: internal_all_rgb + red: 0% + green: 100% + blue: 0% + flash_length: 100ms + - delay: 200ms + - light.turn_on: + id: internal_all_rgb + red: 0% + green: 0% + blue: 100% + flash_length: 100ms +- id: update_all + mode: restart + then: + - script.execute: bars_script_update + - delay: 0.1s + - script.execute: display_script_update +- id: display_script_update + then: + - component.update: display_disp +- id: display_script_draw + then: + - lambda: |- + id(display_disp).image(0, 235, id(icon_prev_page), ImageAlign::BOTTOM_LEFT, id(grey)); + id(display_disp).image(320, 235, id(icon_next_page), ImageAlign::BOTTOM_RIGHT, id(grey)); + id(display_disp).print(160, 235, id(font_arial20), id(white), TextAlign::BOTTOM_CENTER, id(active_page_name).c_str()); + - script.execute: display_script_draw_user + - lambda: |- + if(id(internal_notify).size() > 0){ + // Draw white box over existing text + id(display_disp).filled_rectangle(0, 200, 320, 40, white); + id(display_disp).rectangle(0, 200, 320, 40, accent); + // Show notification message + id(display_disp).print(160, 235, id(font_arial20), id(black), TextAlign::BOTTOM_CENTER, id(internal_notify).c_str()); + } + - lambda: |- + if(id(internal_toast).size() > 0){ + // Draw white box over existing text + id(display_disp).filled_rectangle(0, 200, 320, 40, white); + // Show toast message + id(display_disp).print(160, 235, id(font_arial20), id(black), TextAlign::BOTTOM_CENTER, id(internal_toast).c_str()); + } + - lambda: |- + if(id(show_web_image)){ + // Draw web Image + id(display_disp).image(0, 0, id(web_image)); + } + - lambda: |- + if(id(show_ha_image)){ + // Draw ha Image + id(display_disp).image(0, 0, id(ha_image)); + } +- id: bar1_left_script_update + then: + - lambda: |- + id(bar1_left_cs_pin).turn_on(); + id(bar1_right_cs_pin).turn_off(); + id(bar2_left_cs_pin).turn_off(); + id(bar2_right_cs_pin).turn_off(); + id(bar3_left_cs_pin).turn_off(); + id(bar3_right_cs_pin).turn_off(); + id(bar_display).clear(); + id(bar_display).set_rotation(display::DisplayRotation::DISPLAY_ROTATION_270_DEGREES); + id(bar1_left_script_draw).execute(); + id(bar_display).update(); + delay(1); + id(bar1_left_cs_pin).turn_off(); +- id: bar1_right_script_update + then: + - lambda: |- + id(bar1_left_cs_pin).turn_off(); + id(bar1_right_cs_pin).turn_on(); + id(bar2_left_cs_pin).turn_off(); + id(bar2_right_cs_pin).turn_off(); + id(bar3_left_cs_pin).turn_off(); + id(bar3_right_cs_pin).turn_off(); + id(bar_display).clear(); + id(bar_display).set_rotation(display::DisplayRotation::DISPLAY_ROTATION_90_DEGREES); + id(bar1_right_script_draw).execute(); + id(bar_display).update(); + delay(1); + id(bar1_right_cs_pin).turn_off(); +- id: bar2_left_script_update + then: + - lambda: |- + id(bar1_left_cs_pin).turn_off(); + id(bar1_right_cs_pin).turn_off(); + id(bar2_left_cs_pin).turn_on(); + id(bar2_right_cs_pin).turn_off(); + id(bar3_left_cs_pin).turn_off(); + id(bar3_right_cs_pin).turn_off(); + id(bar_display).clear(); + id(bar_display).set_rotation(display::DisplayRotation::DISPLAY_ROTATION_270_DEGREES); + id(bar2_left_script_draw).execute(); + id(bar_display).update(); + delay(1); + id(bar2_left_cs_pin).turn_off(); +- id: bar2_right_script_update + then: + - lambda: |- + id(bar1_left_cs_pin).turn_off(); + id(bar1_right_cs_pin).turn_off(); + id(bar2_left_cs_pin).turn_off(); + id(bar2_right_cs_pin).turn_on(); + id(bar3_left_cs_pin).turn_off(); + id(bar3_right_cs_pin).turn_off(); + id(bar_display).clear(); + id(bar_display).set_rotation(display::DisplayRotation::DISPLAY_ROTATION_90_DEGREES); + id(bar2_right_script_draw).execute(); + id(bar_display).update(); + delay(1); + id(bar2_right_cs_pin).turn_off(); +- id: bar3_left_script_update + then: + - lambda: |- + id(bar1_left_cs_pin).turn_off(); + id(bar1_right_cs_pin).turn_off(); + id(bar2_left_cs_pin).turn_off(); + id(bar2_right_cs_pin).turn_off(); + id(bar3_left_cs_pin).turn_on(); + id(bar3_right_cs_pin).turn_off(); + id(bar_display).clear(); + id(bar_display).set_rotation(display::DisplayRotation::DISPLAY_ROTATION_270_DEGREES); + id(bar3_left_script_draw).execute(); + id(bar_display).update(); + delay(1); + id(bar3_left_cs_pin).turn_off(); +- id: bar3_right_script_update + then: + - lambda: |- + id(bar1_left_cs_pin).turn_off(); + id(bar1_right_cs_pin).turn_off(); + id(bar2_left_cs_pin).turn_off(); + id(bar2_right_cs_pin).turn_off(); + id(bar3_left_cs_pin).turn_off(); + id(bar3_right_cs_pin).turn_on(); + id(bar_display).clear(); + id(bar_display).set_rotation(display::DisplayRotation::DISPLAY_ROTATION_90_DEGREES); + id(bar3_right_script_draw).execute(); + id(bar_display).update(); + delay(1); + id(bar3_right_cs_pin).turn_off(); +- id: setup_bar_displays + then: + - lambda: |- + id(bar1_left_cs_pin).turn_on(); + id(bar_display).setup(); + delay(5); + id(bar1_left_cs_pin).turn_off(); + id(bar1_right_cs_pin).turn_on(); + id(bar_display).setup(); + delay(5); + id(bar1_right_cs_pin).turn_off(); + id(bar2_left_cs_pin).turn_on(); + id(bar_display).setup(); + delay(5); + id(bar2_left_cs_pin).turn_off(); + id(bar2_right_cs_pin).turn_on(); + id(bar_display).setup(); + delay(5); + id(bar2_right_cs_pin).turn_off(); + id(bar3_left_cs_pin).turn_on(); + id(bar_display).setup(); + delay(5); + id(bar3_left_cs_pin).turn_off(); + id(bar3_right_cs_pin).turn_on(); + id(bar_display).setup(); + delay(5); + id(bar3_right_cs_pin).turn_off(); +- id: bars_script_update + then: + - script.execute: bar1_left_script_update + - delay: 0.1s + - script.execute: bar1_right_script_update + - delay: 0.1s + - script.execute: bar2_left_script_update + - delay: 0.1s + - script.execute: bar2_right_script_update + - delay: 0.1s + - script.execute: bar3_left_script_update + - delay: 0.1s + - script.execute: bar3_right_script_update +- id: event_trigger_feedback + parameters: + event_id: esphome::template_::TemplateEvent* + event_msg: std::string + led_id: light::LightState* + r: float + g: float + b: float + then: + - lambda: |- + event_id->trigger(event_msg); + + if(led_id != nullptr){ + auto call = led_id->make_call(); + call.set_state(true); + call.set_rgb(r, g, b); + call.set_brightness(1.0); + call.set_flash_length(200); // Returns to previous state automatically + call.perform(); + } +- id: update_all_page_leds + then: + - lambda: |- + id(update_single_led).execute("bar1_left_front_rgb_led", id(bar1_left_front_rgb_led), id(active_page_nr)); + id(update_single_led).execute("bar1_right_front_rgb_led", id(bar1_right_front_rgb_led), id(active_page_nr)); + id(update_single_led).execute("bar1_left_back_rgb_led", id(bar1_left_back_rgb_led), id(active_page_nr)); + id(update_single_led).execute("bar1_right_back_rgb_led", id(bar1_right_back_rgb_led), id(active_page_nr)); + id(update_single_led).execute("bar2_left_front_rgb_led", id(bar2_left_front_rgb_led), id(active_page_nr)); + id(update_single_led).execute("bar2_right_front_rgb_led", id(bar2_right_front_rgb_led), id(active_page_nr)); + id(update_single_led).execute("bar2_left_back_rgb_led", id(bar2_left_back_rgb_led), id(active_page_nr)); + id(update_single_led).execute("bar2_right_back_rgb_led", id(bar2_right_back_rgb_led), id(active_page_nr)); + id(update_single_led).execute("bar3_left_front_rgb_led", id(bar3_left_front_rgb_led), id(active_page_nr)); + id(update_single_led).execute("bar3_right_front_rgb_led", id(bar3_right_front_rgb_led), id(active_page_nr)); + id(update_single_led).execute("bar3_left_back_rgb_led", id(bar3_left_back_rgb_led), id(active_page_nr)); + id(update_single_led).execute("bar3_right_back_rgb_led", id(bar3_right_back_rgb_led), id(active_page_nr)); +- id: update_single_led + parameters: + led_name: string + led_id: light::LightState* + page: int + then: + - lambda: |- + if(led_id != nullptr){ + float r = id(internal_page_leds_r)[led_name][page]; + float g = id(internal_page_leds_g)[led_name][page]; + float b = id(internal_page_leds_b)[led_name][page]; + float br = id(internal_page_leds_brightness)[led_name][page]; + std::string effect = id(internal_page_leds_effect)[led_name][page]; + if(r > 1.5) r = r / 255.0; + if(g > 1.5) g = g / 255.0; + if(b > 1.5) b = b / 255.0; + if(br > 1.5) br = br / 255.0; + auto call = led_id->make_call(); + call.set_transition_length(10); + if(br <= 0.0001) { + call.set_state(false); + } else { + call.set_state(true); + call.set_rgb(r,g,b); + call.set_brightness(br); + call.set_effect(effect); + } + call.perform(); + } +image: +- file: mdi:chevron-right + id: icon_next_page + resize: 30x30 + type: BINARY +- file: mdi:chevron-left + id: icon_prev_page + resize: 30x30 + type: BINARY +time: +- platform: homeassistant + id: datetime +i2c: +- id: buttons + sda: GPIO1 + scl: GPIO2 + frequency: 50khz +- id: sensors + sda: GPIO47 + scl: GPIO48 + frequency: 50khz +spi: + clk_pin: GPIO6 + mosi_pin: GPIO5 +light: +- platform: esp32_rmt_led_strip + id: neopixels + rgb_order: RGB + chipset: ws2812 + pin: GPIO4 + num_leds: 13 + internal: true +- platform: partition + id: internal_all_rgb + internal: true + segments: + - id: neopixels + from: 0 + to: 12 +- platform: monochromatic + output: display_backlight_pwm + gamma_correct: 2.2 + name: Main Display Backlight + id: display_backlight + restore_mode: ALWAYS_ON +- platform: partition + id: internal_notification_rgb + internal: true + segments: + - id: neopixels + from: 1 + to: 1 + - id: neopixels + from: 3 + to: 3 +- platform: monochromatic + output: bars_backlight_pwm + gamma_correct: 2.2 + name: Bars Display Backlight + id: bars_backlight + restore_mode: ALWAYS_ON +- platform: partition + name: BAR 1 Left RGB Front + id: bar1_left_front_rgb_led + segments: + - id: neopixels + from: 0 + to: 0 + effects: + - pulse: + name: Breathe + transition_length: 1s + update_interval: 1.5s + min_brightness: 20% + max_brightness: 100% + - pulse: + name: Blink + transition_length: 50ms + update_interval: 500ms +- platform: partition + name: BAR 1 Left RGB Back + id: bar1_left_back_rgb_led + segments: + - id: neopixels + from: 1 + to: 1 + effects: + - pulse: + name: Breathe + transition_length: 1s + update_interval: 1.5s + min_brightness: 20% + max_brightness: 100% + - pulse: + name: Blink + transition_length: 50ms + update_interval: 500ms +- platform: partition + name: BAR 1 Right RGB Front + id: bar1_right_front_rgb_led + segments: + - id: neopixels + from: 2 + to: 2 + effects: + - pulse: + name: Breathe + transition_length: 1s + update_interval: 1.5s + min_brightness: 20% + max_brightness: 100% + - pulse: + name: Blink + transition_length: 50ms + update_interval: 500ms +- platform: partition + name: BAR 1 Right RGB Back + id: bar1_right_back_rgb_led + segments: + - id: neopixels + from: 3 + to: 3 + effects: + - pulse: + name: Breathe + transition_length: 1s + update_interval: 1.5s + min_brightness: 20% + max_brightness: 100% + - pulse: + name: Blink + transition_length: 50ms + update_interval: 500ms +- platform: partition + name: BAR 2 Left RGB Front + id: bar2_left_front_rgb_led + segments: + - id: neopixels + from: 4 + to: 4 + effects: + - pulse: + name: Breathe + transition_length: 1s + update_interval: 1.5s + min_brightness: 20% + max_brightness: 100% + - pulse: + name: Blink + transition_length: 50ms + update_interval: 500ms +- platform: partition + name: BAR 2 Left RGB Back + id: bar2_left_back_rgb_led + segments: + - id: neopixels + from: 5 + to: 5 + effects: + - pulse: + name: Breathe + transition_length: 1s + update_interval: 1.5s + min_brightness: 20% + max_brightness: 100% + - pulse: + name: Blink + transition_length: 50ms + update_interval: 500ms +- platform: partition + name: BAR 2 Right RGB Front + id: bar2_right_front_rgb_led + segments: + - id: neopixels + from: 6 + to: 6 + effects: + - pulse: + name: Breathe + transition_length: 1s + update_interval: 1.5s + min_brightness: 20% + max_brightness: 100% + - pulse: + name: Blink + transition_length: 50ms + update_interval: 500ms +- platform: partition + name: BAR 2 Right RGB Back + id: bar2_right_back_rgb_led + segments: + - id: neopixels + from: 7 + to: 7 + effects: + - pulse: + name: Breathe + transition_length: 1s + update_interval: 1.5s + min_brightness: 20% + max_brightness: 100% + - pulse: + name: Blink + transition_length: 50ms + update_interval: 500ms +- platform: partition + name: BAR 3 Left RGB Front + id: bar3_left_front_rgb_led + segments: + - id: neopixels + from: 8 + to: 8 + effects: + - pulse: + name: Breathe + transition_length: 1s + update_interval: 1.5s + min_brightness: 20% + max_brightness: 100% + - pulse: + name: Blink + transition_length: 50ms + update_interval: 500ms +- platform: partition + name: BAR 3 Left RGB Back + id: bar3_left_back_rgb_led + segments: + - id: neopixels + from: 9 + to: 9 + effects: + - pulse: + name: Breathe + transition_length: 1s + update_interval: 1.5s + min_brightness: 20% + max_brightness: 100% + - pulse: + name: Blink + transition_length: 50ms + update_interval: 500ms +- platform: partition + name: BAR 3 Right RGB Front + id: bar3_right_front_rgb_led + segments: + - id: neopixels + from: 10 + to: 10 + effects: + - pulse: + name: Breathe + transition_length: 1s + update_interval: 1.5s + min_brightness: 20% + max_brightness: 100% + - pulse: + name: Blink + transition_length: 50ms + update_interval: 500ms +- platform: partition + name: BAR 3 Right RGB Back + id: bar3_right_back_rgb_led + segments: + - id: neopixels + from: 11 + to: 11 + effects: + - pulse: + name: Breathe + transition_length: 1s + update_interval: 1.5s + min_brightness: 20% + max_brightness: 100% + - pulse: + name: Blink + transition_length: 50ms + update_interval: 500ms +output: +- platform: ledc + pin: GPIO3 + id: display_backlight_pwm + inverted: true + max_power: 1 + frequency: 500Hz +- platform: ledc + pin: GPIO46 + id: bars_backlight_pwm + inverted: true + max_power: 0.6 + frequency: 500Hz +- platform: gpio + id: bar1_left_cs_pin + pin: + mcp23xxx: base_J1 + number: 5 + mode: + output: true + inverted: true +- platform: gpio + id: bar1_right_cs_pin + pin: + mcp23xxx: base_J1 + number: 1 + mode: + output: true + inverted: true +- platform: gpio + id: bar2_left_cs_pin + pin: + mcp23xxx: base_J2 + number: 5 + mode: + output: true + inverted: true +- platform: gpio + id: bar2_right_cs_pin + pin: + mcp23xxx: base_J2 + number: 1 + mode: + output: true + inverted: true +- platform: gpio + id: bar3_left_cs_pin + pin: + mcp23xxx: base_J3 + number: 5 + mode: + output: true + inverted: true +- platform: gpio + id: bar3_right_cs_pin + pin: + mcp23xxx: base_J3 + number: 1 + mode: + output: true + inverted: true +display: +- id: display_disp + platform: ili9xxx + model: ili9341 + invert_colors: false + color_order: rgb + update_interval: 30s + dc_pin: + number: GPIO7 + allow_other_uses: true + cs_pin: + mcp23xxx: base_J0 + number: 5 + mode: + output: true + inverted: false + show_test_card: false + dimensions: + height: 240 + width: 320 + rotation: 180 + lambda: id(display_script_draw).execute(); +- id: bar_display + platform: ili9xxx + model: ST7735 + color_order: bgr + update_interval: never + dc_pin: + number: GPIO7 + allow_other_uses: true + invert_colors: false + show_test_card: false + auto_clear_enabled: false + dimensions: + height: 160 + width: 80 + offset_width: 24 +mcp23008: +- id: base_J0 + i2c_id: buttons + address: '32' +- id: base_J3 + i2c_id: buttons + address: 35 +- id: base_J2 + i2c_id: buttons + address: 34 +- id: base_J1 + i2c_id: buttons + address: 33 +binary_sensor: +- platform: gpio + id: display_left_btn + name: Main Display button Left + internal: true + pin: + mcp23xxx: base_J0 + number: 6 + mode: INPUT_PULLUP + inverted: true + filters: + - delayed_off: 10ms + on_multi_click: + - timing: + - ON for at most 300ms + - OFF for at least 0.2s + then: + - script.execute: prev_page + - timing: + - ON for at most 300ms + - OFF for at most 0.2s + - ON for at most 300ms + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: display_left_btn_event + led_id: !lambda "return nullptr;" + r: 0 + g: 0 + b: 255 + event_msg: !lambda "return \"double_click \" + id(active_page_name);" + - timing: + - ON for at most 300ms + - OFF for at most 0.2s + - ON for at most 300ms + - OFF for at most 0.2s + - ON for at most 300ms + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: display_left_btn_event + led_id: !lambda "return nullptr;" + r: 255 + g: 0 + b: 0 + event_msg: !lambda "return \"triple_click \" + id(active_page_name);" + - timing: + - ON for at least 1s + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: display_left_btn_event + led_id: !lambda "return nullptr;" + r: 255 + g: 0 + b: 255 + event_msg: !lambda "return \"hold \" + id(active_page_name);" +- platform: gpio + id: display_right_btn + name: Main Display button Right + internal: true + pin: + mcp23xxx: base_J0 + number: 2 + mode: INPUT_PULLUP + inverted: true + filters: + - delayed_off: 10ms + on_multi_click: + - timing: + - ON for at most 300ms + - OFF for at least 0.2s + then: + - script.execute: next_page + - timing: + - ON for at most 300ms + - OFF for at most 0.2s + - ON for at most 300ms + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: display_right_btn_event + led_id: !lambda "return nullptr;" + r: 0 + g: 0 + b: 255 + event_msg: !lambda "return \"double_click \" + id(active_page_name);" + - timing: + - ON for at most 300ms + - OFF for at most 0.2s + - ON for at most 300ms + - OFF for at most 0.2s + - ON for at most 300ms + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: display_right_btn_event + led_id: !lambda "return nullptr;" + r: 255 + g: 0 + b: 0 + event_msg: !lambda "return \"triple_click \" + id(active_page_name);" + - timing: + - ON for at least 1s + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: display_right_btn_event + led_id: !lambda "return nullptr;" + r: 255 + g: 0 + b: 255 + event_msg: !lambda "return \"hold \" + id(active_page_name);" +- platform: gpio + id: bar1_left_btn + name: BAR 1 button Left + internal: true + pin: + mcp23xxx: base_J1 + number: 6 + mode: INPUT_PULLUP + inverted: true + filters: + - delayed_off: 10ms + on_multi_click: + - timing: + - ON for at most 300ms + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: bar1_left_btn_event + led_id: bar1_left_front_rgb_led + r: 0 + g: 255 + b: 0 + event_msg: !lambda "return \"click \" + id(active_page_name);" + - timing: + - ON for at most 300ms + - OFF for at most 0.2s + - ON for at most 300ms + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: bar1_left_btn_event + led_id: bar1_left_front_rgb_led + r: 0 + g: 0 + b: 255 + event_msg: !lambda "return \"double_click \" + id(active_page_name);" + - timing: + - ON for at most 300ms + - OFF for at most 0.2s + - ON for at most 300ms + - OFF for at most 0.2s + - ON for at most 300ms + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: bar1_left_btn_event + led_id: bar1_left_front_rgb_led + r: 255 + g: 0 + b: 0 + event_msg: !lambda "return \"triple_click \" + id(active_page_name);" + - timing: + - ON for at least 1s + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: bar1_left_btn_event + led_id: bar1_left_front_rgb_led + r: 255 + g: 0 + b: 255 + event_msg: !lambda "return \"hold \" + id(active_page_name);" +- platform: gpio + id: bar1_right_btn + name: BAR 1 button Right + internal: true + pin: + mcp23xxx: base_J1 + number: 2 + mode: INPUT_PULLUP + inverted: true + filters: + - delayed_off: 10ms + on_multi_click: + - timing: + - ON for at most 300ms + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: bar1_right_btn_event + led_id: bar1_right_front_rgb_led + r: 0 + g: 255 + b: 0 + event_msg: !lambda "return \"click \" + id(active_page_name);" + - timing: + - ON for at most 300ms + - OFF for at most 0.2s + - ON for at most 300ms + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: bar1_right_btn_event + led_id: bar1_right_front_rgb_led + r: 0 + g: 0 + b: 255 + event_msg: !lambda "return \"double_click \" + id(active_page_name);" + - timing: + - ON for at most 300ms + - OFF for at most 0.2s + - ON for at most 300ms + - OFF for at most 0.2s + - ON for at most 300ms + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: bar1_right_btn_event + led_id: bar1_right_front_rgb_led + r: 255 + g: 0 + b: 0 + event_msg: !lambda "return \"triple_click \" + id(active_page_name);" + - timing: + - ON for at least 1s + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: bar1_right_btn_event + led_id: bar1_right_front_rgb_led + r: 255 + g: 0 + b: 255 + event_msg: !lambda "return \"hold \" + id(active_page_name);" +- platform: gpio + id: bar2_left_btn + name: BAR 2 button Left + internal: true + pin: + mcp23xxx: base_J2 + number: 6 + mode: INPUT_PULLUP + inverted: true + filters: + - delayed_off: 10ms + on_multi_click: + - timing: + - ON for at most 300ms + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: bar2_left_btn_event + led_id: bar2_left_front_rgb_led + r: 0 + g: 255 + b: 0 + event_msg: !lambda "return \"click \" + id(active_page_name);" + - timing: + - ON for at most 300ms + - OFF for at most 0.2s + - ON for at most 300ms + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: bar2_left_btn_event + led_id: bar2_left_front_rgb_led + r: 0 + g: 0 + b: 255 + event_msg: !lambda "return \"double_click \" + id(active_page_name);" + - timing: + - ON for at most 300ms + - OFF for at most 0.2s + - ON for at most 300ms + - OFF for at most 0.2s + - ON for at most 300ms + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: bar2_left_btn_event + led_id: bar2_left_front_rgb_led + r: 255 + g: 0 + b: 0 + event_msg: !lambda "return \"triple_click \" + id(active_page_name);" + - timing: + - ON for at least 1s + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: bar2_left_btn_event + led_id: bar2_left_front_rgb_led + r: 255 + g: 0 + b: 255 + event_msg: !lambda "return \"hold \" + id(active_page_name);" +- platform: gpio + id: bar2_right_btn + name: BAR 2 button Right + internal: true + pin: + mcp23xxx: base_J2 + number: 2 + mode: INPUT_PULLUP + inverted: true + filters: + - delayed_off: 10ms + on_multi_click: + - timing: + - ON for at most 300ms + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: bar2_right_btn_event + led_id: bar2_right_front_rgb_led + r: 0 + g: 255 + b: 0 + event_msg: !lambda "return \"click \" + id(active_page_name);" + - timing: + - ON for at most 300ms + - OFF for at most 0.2s + - ON for at most 300ms + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: bar2_right_btn_event + led_id: bar2_right_front_rgb_led + r: 0 + g: 0 + b: 255 + event_msg: !lambda "return \"double_click \" + id(active_page_name);" + - timing: + - ON for at most 300ms + - OFF for at most 0.2s + - ON for at most 300ms + - OFF for at most 0.2s + - ON for at most 300ms + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: bar2_right_btn_event + led_id: bar2_right_front_rgb_led + r: 255 + g: 0 + b: 0 + event_msg: !lambda "return \"triple_click \" + id(active_page_name);" + - timing: + - ON for at least 1s + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: bar2_right_btn_event + led_id: bar2_right_front_rgb_led + r: 255 + g: 0 + b: 255 + event_msg: !lambda "return \"hold \" + id(active_page_name);" +- platform: gpio + id: bar3_left_btn + name: BAR 3 button Left + internal: true + pin: + mcp23xxx: base_J3 + number: 6 + mode: INPUT_PULLUP + inverted: true + filters: + - delayed_off: 10ms + on_multi_click: + - timing: + - ON for at most 300ms + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: bar3_left_btn_event + led_id: bar3_left_front_rgb_led + r: 0 + g: 255 + b: 0 + event_msg: !lambda "return \"click \" + id(active_page_name);" + - timing: + - ON for at most 300ms + - OFF for at most 0.2s + - ON for at most 300ms + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: bar3_left_btn_event + led_id: bar3_left_front_rgb_led + r: 0 + g: 0 + b: 255 + event_msg: !lambda "return \"double_click \" + id(active_page_name);" + - timing: + - ON for at most 300ms + - OFF for at most 0.2s + - ON for at most 300ms + - OFF for at most 0.2s + - ON for at most 300ms + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: bar3_left_btn_event + led_id: bar3_left_front_rgb_led + r: 255 + g: 0 + b: 0 + event_msg: !lambda "return \"triple_click \" + id(active_page_name);" + - timing: + - ON for at least 1s + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: bar3_left_btn_event + led_id: bar3_left_front_rgb_led + r: 255 + g: 0 + b: 255 + event_msg: !lambda "return \"hold \" + id(active_page_name);" +- platform: gpio + id: bar3_right_btn + name: BAR 3 button Right + internal: true + pin: + mcp23xxx: base_J3 + number: 2 + mode: INPUT_PULLUP + inverted: true + filters: + - delayed_off: 10ms + on_multi_click: + - timing: + - ON for at most 300ms + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: bar3_right_btn_event + led_id: bar3_right_front_rgb_led + r: 0 + g: 255 + b: 0 + event_msg: !lambda "return \"click \" + id(active_page_name);" + - timing: + - ON for at most 300ms + - OFF for at most 0.2s + - ON for at most 300ms + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: bar3_right_btn_event + led_id: bar3_right_front_rgb_led + r: 0 + g: 0 + b: 255 + event_msg: !lambda "return \"double_click \" + id(active_page_name);" + - timing: + - ON for at most 300ms + - OFF for at most 0.2s + - ON for at most 300ms + - OFF for at most 0.2s + - ON for at most 300ms + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: bar3_right_btn_event + led_id: bar3_right_front_rgb_led + r: 255 + g: 0 + b: 0 + event_msg: !lambda "return \"triple_click \" + id(active_page_name);" + - timing: + - ON for at least 1s + - OFF for at least 0.2s + then: + - script.execute: + id: event_trigger_feedback + event_id: bar3_right_btn_event + led_id: bar3_right_front_rgb_led + r: 255 + g: 0 + b: 255 + event_msg: !lambda "return \"hold \" + id(active_page_name);" +http_request: {} +online_image: +- id: web_image + url: https://invalid.jpg + type: RGB565 + format: JPG + resize: 320x240 + update_interval: never + on_download_finished: + then: + - script.execute: display_script_update + request_headers: {} +- id: ha_image + url: https://invalid.jpg + type: RGB565 + format: JPG + resize: 320x240 + update_interval: never + on_download_finished: + then: + - script.execute: display_script_update + request_headers: + Authorization: Bearer EXAMPLE_TOKEN +substitutions: + button_events: + - click Start + - double_click Start + - triple_click Start + - hold Start + - click Music + - double_click Music + - triple_click Music + - hold Music + - click Lights + - double_click Lights + - triple_click Lights + - hold Lights + - click Security + - double_click Security + - triple_click Security + - hold Security +event: +- platform: template + id: display_left_btn_event + name: DISPLAY Button Left + device_class: button + event_types: ${button_events} +- platform: template + id: display_right_btn_event + name: DISPLAY Button Right + device_class: button + event_types: ${button_events} +- platform: template + id: bar1_left_btn_event + name: BAR 1 Button Left + device_class: button + event_types: ${button_events} +- platform: template + id: bar1_right_btn_event + name: BAR 1 Button Right + device_class: button + event_types: ${button_events} +- platform: template + id: bar2_left_btn_event + name: BAR 2 Button Left + device_class: button + event_types: ${button_events} +- platform: template + id: bar2_right_btn_event + name: BAR 2 Button Right + device_class: button + event_types: ${button_events} +- platform: template + id: bar3_left_btn_event + name: BAR 3 Button Left + device_class: button + event_types: ${button_events} +- platform: template + id: bar3_right_btn_event + name: BAR 3 Button Right + device_class: button + event_types: ${button_events} diff --git a/src/buttonplus_generator/constants.py b/src/buttonplus_generator/constants.py index 272790e..a22bde8 100644 --- a/src/buttonplus_generator/constants.py +++ b/src/buttonplus_generator/constants.py @@ -34,6 +34,7 @@ class IdSuffix(StrEnum): CS_PIN = "cs_pin" SCRIPT_UPDATE = "script_update" + SCRIPT_DRAW_USER = "script_draw_user" SCRIPT_DRAW = "script_draw" class ClickType(StrEnum): @@ -86,6 +87,9 @@ TIMING_HOLD_CLICK = [ "ON for at least 1s", "OFF for at least 0.2s" ] + +class YAMLInclude: + def __init__(self, value): self.value = value class YAMLExtend: def __init__(self, value): self.value = value @@ -94,4 +98,4 @@ class YAMLLambda: def __init__(self, value): self.value = value class YAMLSecret: - def __init__(self, value): self.value = value \ No newline at end of file + def __init__(self, value): self.value = value diff --git a/src/buttonplus_generator/generator.py b/src/buttonplus_generator/generator.py index 5794c0b..7469cd7 100644 --- a/src/buttonplus_generator/generator.py +++ b/src/buttonplus_generator/generator.py @@ -8,16 +8,17 @@ from __future__ import annotations import argparse from typing import Any, Dict, List +import os import yaml -from buttonplus_generator.constants import YAMLExtend, YAMLLambda, YAMLSecret, ClickType +from buttonplus_generator.constants import YAMLExtend, YAMLInclude, YAMLLambda, YAMLSecret, ClickType from buttonplus_generator.snippets.common import config_common from buttonplus_generator.snippets.bars import config_bars from buttonplus_generator.snippets.display import config_display from buttonplus_generator.snippets.events import config_events from buttonplus_generator.snippets.services import config_services from buttonplus_generator.snippets.leds import config_leds -from buttonplus_generator.merger import deep_merge +from buttonplus_generator.merger import deep_merge, deep_split from buttonplus_generator.utils import list_to_click_type def compose_config( @@ -28,6 +29,7 @@ def compose_config( click_types: List[ClickType], click_confirm: bool, services: bool, + token: str, effects: bool ) -> Dict[str, Any]: @@ -44,7 +46,7 @@ def compose_config( # Services, only available with display if services: - config = deep_merge(config, config_services(version=version)) + config = deep_merge(config, config_services(version=version, token=token)) # Bars config = deep_merge( @@ -70,27 +72,31 @@ def compose_config( def yaml_dumper(config: Dict[str, Any], fh): ## Handle multiline strings - def represent_multistr(dumper, data): + def represent_multistr(dumper, data: str): """configures yaml for dumping multiline strings Ref: https://stackoverflow.com/questions/8640959/how-can-i-control-what-scalar-form-pyyaml-uses-for-my-data """ if len(data.splitlines()) > 1: # check for multiline string - return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|") + return dumper.represent_scalar("tag:yaml.org,2002:str", data.strip(), style="|") return dumper.represent_scalar("tag:yaml.org,2002:str", data) - def represent_extend(dumper, data): - return dumper.represent_scalar("!extend", data.value) - def represent_lambda(dumper, data): if len(data.value.splitlines()) > 1: # check for multiline string - return dumper.represent_scalar("!lambda", data.value, style="|") + return dumper.represent_scalar("!lambda", data.value.strip(), style="|") return dumper.represent_scalar("!lambda", data.value, style='"') + def represent_include(dumper, data): + return dumper.represent_scalar("!include", data.value) + + def represent_extend(dumper, data): + return dumper.represent_scalar("!extend", data.value) + def represent_secret(dumper, data): - return dumper.represent_scalar("!secret", data.value, style="") + return dumper.represent_scalar("!secret", data.value) yaml.add_representer(str, represent_multistr) yaml.add_representer(YAMLExtend, represent_extend) + yaml.add_representer(YAMLInclude, represent_include) yaml.add_representer(YAMLLambda, represent_lambda) yaml.add_representer(YAMLSecret, represent_secret) @@ -152,6 +158,10 @@ def main(argv=None) -> int: help="List click types buttons, choices are [single|double|triple|hold].", metavar="TYPE", ) + parser.add_argument( + "--token", + help="Home Assistant token used for loading home assistant images, creates a second image service", + ) parser.add_argument( "--no-click-confirm", default=False, @@ -171,7 +181,13 @@ def main(argv=None) -> int: help="Don't add any effects to the LEDs.", ) parser.add_argument( - "--output", "-o", default="buttonplus.yaml", help="Output file path" + "--no-split", + default=False, + action="store_true", + help="Don't split the configuration in two files.", + ) + parser.add_argument( + "--output", "-o", default="buttonplus", help="Output file path" ) args = parser.parse_args(argv) @@ -183,24 +199,61 @@ def main(argv=None) -> int: click_confirm: bool = not args.no_click_confirm effects: bool = not args.no_effects services: bool = not args.no_services - output: str = args.output + split: bool = not args.no_split + token: str = args.token or "" + output: str = args.output.removesuffix(".yaml") + + generate( + output, split, device_name, version, num_bars, pages, click_types, click_confirm, services, token, effects + ) + + return 0 + +def generate( + output: str, + split: bool, + device_name: str, + version: int, + num_bars: int, + pages: List[str], + click_types: List[ClickType], + click_confirm: bool, + services: bool, + token: str, + effects: bool): config = compose_config( - device_name, version, num_bars, pages, click_types, click_confirm, services, effects + device_name, version, num_bars, pages, click_types, click_confirm, services, token, effects ) - with open(output, "w", encoding="utf-8") as fh: - fh.writelines( - [ - "# Generated by ESPHome for Buttonplus\n", - "# Options: \n", - f"# - device: {device_name}\n", - f"# - version: {version}\n", - f"# - bars: {num_bars}\n", - f"# - pages: {", ".join(pages)}\n", - "\n", - ] - ) - yaml_dumper(config, fh) + + user_file = output + ".yaml" + base_file = output + "_base.yaml" + + files = {user_file: config} + + if split: + base, user = deep_split(config, os.path.basename(base_file)) + + files = { + base_file: base, + user_file: user + } + + for yfile, yconf in files.items(): + + with open(yfile, "w", encoding="utf-8") as fh: + fh.writelines( + [ + "# Generated by ESPHome for Buttonplus\n", + f"# File: {yfile}\n" + "# Options: \n", + f"# - device: {device_name}\n", + f"# - version: {version}\n", + f"# - bars: {num_bars}\n", + f"# - pages: {", ".join(pages)}\n", + "\n", + ] + ) + yaml_dumper(yconf, fh) - print(f"Wrote {output}") - return 0 + print(f"Wrote {yfile}") diff --git a/src/buttonplus_generator/merger.py b/src/buttonplus_generator/merger.py index 2c4df7c..503c92a 100644 --- a/src/buttonplus_generator/merger.py +++ b/src/buttonplus_generator/merger.py @@ -1,10 +1,13 @@ -from typing import Any, Dict, List - +import copy +import re +from typing import Any, Dict, List, Optional, Tuple +from buttonplus_generator.utils import display_id, bar_id +from buttonplus_generator.constants import IdSuffix, Side, YAMLExtend, YAMLInclude def deep_merge(a: Dict[str, Any], b: Dict[str, Any]) -> Dict[str, Any]: """Recursively merge b into a and return a new dict. - Lists are concatenated; scalar values in b override a. + Scalar values in b overwrite a. Lists are merged using the list_merge function. """ result = dict(a) for k, v in b.items(): @@ -15,20 +18,20 @@ def deep_merge(a: Dict[str, Any], b: Dict[str, Any]) -> Dict[str, Any]: result[k] = deep_merge(result[k], v) # a[k] and b[k] are both a list: list_merge elif isinstance(result[k], list) and isinstance(v, list): - result[k] = list_merge(result[k], v) + result[k] = _list_merge(result[k], v) else: result[k] = v else: result[k] = v return result -def list_merge(a: List[Any], b: List[Any]) -> List[Any]: +def _list_merge(a: List[Any], b: List[Any]) -> List[Any]: """ Merge two list together based on id's. This is similar to !extend in YAML. Steps: 1. Check if both lists contain Dicts with an `id` key in them - 1a. Otherwise do a pre-append list merge (extend) + 1a. Otherwise do a pre-append list merge (extend, see other function) 2. For every entry in a: 3. Store id in a list for later 4. Find the same id in b, if not found add to result without merge @@ -47,7 +50,7 @@ def list_merge(a: List[Any], b: List[Any]) -> List[Any]: # If there are no id-based dicts in either list, just extend if not (a_has_id and b_has_id): - return pre_append_list_merge(a, b) + return _pre_append_list_merge(a, b) result: List[Any] = [] seen_ids = set() @@ -103,7 +106,16 @@ def list_merge(a: List[Any], b: List[Any]) -> List[Any]: # Return the merged list return result -def pre_append_list_merge(a: List[Any], b: List[Any]) -> List[Any]: +def _pre_append_list_merge(a: List[Any], b: List[Any]) -> List[Any]: + """ + Merge two list together with optional pre-appending. This is step two + of the list merge. Checks if any list is requesting a pre-append (prepend key present), + in which case this function will process them. Otherwise just extend them together. + + For every list element, if prepend=true, add it to the front. Otherwise add it to the back. + List order is preserved. Meaning that multiple prepend entries will be added to the front + in the same order that they were in the original list. + """ # Detect whether both lists contain dicts with an 'prepend' key a_has_prepend = any(isinstance(x, dict) and "prepend" in x for x in a) @@ -135,4 +147,248 @@ def pre_append_list_merge(a: List[Any], b: List[Any]) -> List[Any]: for item in b: do_pre_or_append(item) - return list(prepend_list) + list(append_list) \ No newline at end of file + return list(prepend_list) + list(append_list) + + +### SPLITTER ### + +SPLITOUT_KEYS = [ + "esphome.name", + "esphome.friendly_name", + "wifi", + "logger", + "ota", + "api.encryption", + "color.[id=accent]", + f"script.[id={display_id(IdSuffix.SCRIPT_DRAW_USER)}]", + f"script.[id={bar_id(IdSuffix.SCRIPT_DRAW, 1, Side.LEFT)}]", + f"script.[id={bar_id(IdSuffix.SCRIPT_DRAW, 1, Side.RIGHT)}]", + f"script.[id={bar_id(IdSuffix.SCRIPT_DRAW, 2, Side.LEFT)}]", + f"script.[id={bar_id(IdSuffix.SCRIPT_DRAW, 2, Side.RIGHT)}]", + f"script.[id={bar_id(IdSuffix.SCRIPT_DRAW, 3, Side.LEFT)}]", + f"script.[id={bar_id(IdSuffix.SCRIPT_DRAW, 3, Side.RIGHT)}]", + f"event.[id={display_id(IdSuffix.EVENT_BUTTON, Side.LEFT)}].on_event", + f"event.[id={display_id(IdSuffix.EVENT_BUTTON, Side.RIGHT)}].on_event", + f"event.[id={bar_id(IdSuffix.EVENT_BUTTON, 1, Side.LEFT)}].on_event", + f"event.[id={bar_id(IdSuffix.EVENT_BUTTON, 1, Side.RIGHT)}].on_event", + f"event.[id={bar_id(IdSuffix.EVENT_BUTTON, 2, Side.LEFT)}].on_event", + f"event.[id={bar_id(IdSuffix.EVENT_BUTTON, 2, Side.RIGHT)}].on_event", + f"event.[id={bar_id(IdSuffix.EVENT_BUTTON, 3, Side.LEFT)}].on_event", + f"event.[id={bar_id(IdSuffix.EVENT_BUTTON, 3, Side.RIGHT)}].on_event", +] + +def _parse_split_path(path: str) -> List[Any]: + """Parse a split path string into tokens.""" + tokens = [] + for raw in re.findall(r"[^.\[\]]+|\[[^\]]+\]", path): + if raw.startswith('[') and raw.endswith(']'): + selector = raw[1:-1] + if '=' not in selector: + raise ValueError(f"Unsupported selector format: {path}") + key, value = selector.split('=', 1) + tokens.append(("filter", key, value.strip("\"'"))) + else: + tokens.append(raw) + return tokens + + +def _find_target(container: Any, tokens: List[Any]) -> Optional[Tuple[Any, Any, Any, Any]]: + """Find the target value described by tokens and return its parent context.""" + current = container + + for index, token in enumerate(tokens): + if isinstance(token, str): + if not isinstance(current, dict) or token not in current: + return None + if index == len(tokens) - 1: + return current, token, current[token], None + current = current[token] + else: + kind, key, value = token + if kind != "filter" or not isinstance(current, list): + return None + + for item_index, item in enumerate(current): + if isinstance(item, dict) and item.get(key) == value: + if index == len(tokens) - 1: + return current, item_index, item, item + next_result = _find_target(item, tokens[index + 1:]) + if next_result is not None: + return next_result[0], next_result[1], next_result[2], item + return None + + return None + + +def _make_split_fragment(item: Dict[str, Any], tokens: List[Any]) -> Dict[str, Any]: + """Create the fragment for a selector-based split path.""" + selector_index = next((i for i, token in enumerate(tokens) if isinstance(token, tuple)), None) + if selector_index is None: + return copy.deepcopy(item) + + if selector_index == len(tokens) - 1: + return copy.deepcopy(item) + + fragment: Dict[str, Any] = {} + if isinstance(item, dict) and "id" in item: + fragment["id"] = YAMLExtend(item['id']) + + current = fragment + for token in tokens[selector_index + 1:-1]: + if isinstance(token, str): + current[token] = {} + current = current[token] + + last_token = tokens[-1] + if isinstance(last_token, str) and isinstance(item, dict) and last_token in item: + current[last_token] = copy.deepcopy(item[last_token]) + + return fragment + + +def deep_split(origin: Dict[str, Any], origin_filename: str, splitout: List[str] = SPLITOUT_KEYS) -> Tuple[Dict[str, Any], Dict[str, Any]]: + """ + Split a complete YAML dict into two files based on a list of keys that are requested to be split out. + + Input: + origin: a yaml structure, consisting of nested dictionaries, lists, lists of dictionaries and scalar values + splitout: list of json-path like strings that declare which keys should be split out into a separate structure + + Splitout-path specification: + . : key-a is a dictionary key, which contains a dictionary of which key-b is a key. + key-b and its value (which can be another nested structure), should me moved to . inside the output structure + assume . also exists, this should remain in the origin structure + .[=]: key-a is a dictionary key, which contains a list of dictionaries, + of which we want the entry which has key-b with the value of value-b + there might also be other dictionaries in the list of key-a, but these must stay in the origin structure + the extracted dictionary should be placed in a list at the same path in the output structure, .[] + .[=].: key-a is a dictionary key, which contains a list of dictionaries, + of which we want the entry which has key-b with the value of value-b + we only want to move the key-c entry of this dictionary in the list + the extracted element should be placed in a new list at the same path in the output structure, .[=]. + + Example: + paths = + list.[id=entry1] + list.[id=entry2].c + dict.e.g + move + + origin = { + list: [ + { + id: entry1 + key1: moves_with_list_entry + key2: moves_with_list_entry + }, + { + id: entry2 + c: value_to_move + d: stays_here_value + } + ], + dict: { + e: { + g: value_to_move + }, + f: stays_here_value + }, + move: value_to_move + } + + expected_split = { + list: [ + { + id: entry1 + key1: moves_with_list_entry + key2: moves_with_list_entry + }, + { + id: !extend entry2 + c: value_to_move + } + ], + dict: { + e: { + g: value_to_move + } + }, + move: value_to_move + } + + expected_origin = { + list: [ + { + id: entry2 + d: stays_here_value + } + ], + dict: { + f: stays_here_value + } + } + + + Steps: + 1. Parse the splitout keys into tokens + 2. For every splitout key, find the entry in the origin dictionary. + 2a. It is important to retain the entire parent path, as we want to reconstruct it in the new output + 3. Create a new output dictionary where the entries are moved to + 3a. Only move the leaf node value and copy it's parent structure + 3b. Do not move any adjecant nodes, if there are no adjecant nodes it should be cleaned from the origin + 3c. The moved leaf should not remain present in the origin, it has been moved out to the output + 4. Ensure that the origin dictionary is still valid, after moving the node to the output structure + 4a. If the leaf is a list element (.[=]), remove it from the list. If this list is now empty, remove the list key + 4b. If the leaf has a list as parent node at some point (.[=].), move this leaf only. Keep the rest of the node and structure intact. + 4bb. Origin keeps the `id: `, output gets `id: !extend `. + 4c. If the leaf is a dictionary or scalar (.), move it over entirely and remove it from origin + 5. Return a tuple of the modified origin and the new output structure. + + NOTE: This function and its subfunctions were generated by AI + """ + output: Dict[str, Any] = {"packages": [YAMLInclude(origin_filename)]} + + for path in splitout: + tokens = _parse_split_path(path) + found = _find_target(origin, tokens) + if found is None: + continue + + parent, key, value, selected_item = found + selector_index = next((i for i, token in enumerate(tokens) if isinstance(token, tuple)), None) + + if isinstance(parent, dict): + if isinstance(key, int): + continue + + if selector_index is not None and selector_index < len(tokens) - 1: + fragment = _make_split_fragment(copy.deepcopy(selected_item), tokens) + output.setdefault(tokens[0], []).append(fragment) + else: + current = output + for token in tokens[:-1]: + if isinstance(token, str): + current = current.setdefault(token, {}) + current[tokens[-1]] = copy.deepcopy(value) + + del parent[key] + continue + + if isinstance(parent, list) and isinstance(key, int): + item = parent[key] + if selector_index is None or selector_index == len(tokens) - 1: + output.setdefault(tokens[0], []).append(copy.deepcopy(item)) + parent.pop(key) + continue + + fragment = _make_split_fragment(copy.deepcopy(item), tokens) + output.setdefault(tokens[0], []).append(fragment) + + current = item + for token in tokens[selector_index + 1:-1]: + if isinstance(token, str) and isinstance(current, dict) and token in current: + current = current[token] + if isinstance(current, dict) and isinstance(tokens[-1], str): + current.pop(tokens[-1], None) + + return origin, output \ No newline at end of file diff --git a/src/buttonplus_generator/snippets/display.py b/src/buttonplus_generator/snippets/display.py index 2b2ae74..02c50ef 100644 --- a/src/buttonplus_generator/snippets/display.py +++ b/src/buttonplus_generator/snippets/display.py @@ -169,31 +169,45 @@ def update_scripts(page_names: List[str]) -> Dict[str, Any]: ) generated_cpp = ( - "// All pages\n" f"id({display_id(IdSuffix.DISPLAY)}).image(0, 235, id(icon_prev_page), ImageAlign::BOTTOM_LEFT, id(grey));\n" f"id({display_id(IdSuffix.DISPLAY)}).image(320, 235, id(icon_next_page), ImageAlign::BOTTOM_RIGHT, id(grey));\n" f"id({display_id(IdSuffix.DISPLAY)}).print(160, 235, id(font_arial20), id(white), TextAlign::BOTTOM_CENTER, id(active_page_name).c_str());\n" - "\n" - f"id({display_id(IdSuffix.DISPLAY)}).strftime(160, 5, id(font_arial20), TextAlign::TOP_CENTER, \"%H:%M\", id(datetime).now());\n" - f"id({display_id(IdSuffix.DISPLAY)}).strftime(160, 80, id(font_arial20), TextAlign::TOP_CENTER, \"%d-%m-%Y\", id(datetime).now());\n" + ) + + generated_cpp_user = ( + "// All pages\n" + f"id({display_id(IdSuffix.DISPLAY)}).strftime(160, 5, id(font_arial20), TextAlign::TOP_CENTER, \"%H:%M\", id(datetime).now());\n" + f"id({display_id(IdSuffix.DISPLAY)}).strftime(160, 80, id(font_arial20), TextAlign::TOP_CENTER, \"%d-%m-%Y\", id(datetime).now());\n" "\n" "switch (id(active_page_nr)){\n" ) for page_num in range(0, len(page_names)): - generated_cpp += ( + generated_cpp_user += ( f" // Page {page_num} - {page_names[page_num]}\n" f" case {page_num}:\n" f" break;\n" ) - generated_cpp += "}\n" + generated_cpp_user += "}\n" display_dict["script"].append( { "id": display_id(IdSuffix.SCRIPT_DRAW), "then": [ - {"lambda": generated_cpp} + {"lambda": generated_cpp}, + { + "script.execute": display_id(IdSuffix.SCRIPT_DRAW_USER) + } + ], + } + ) + + display_dict["script"].append( + { + "id": display_id(IdSuffix.SCRIPT_DRAW_USER), + "then": [ + {"lambda": generated_cpp_user}, ], } )