mikosoft83 / pithy_screen_menu_system

GNU General Public License v3.0
56 stars 9 forks source link

Issue/help with minimal setup #6

Open ha88rgc opened 1 year ago

ha88rgc commented 1 year ago

Hello,

In hopes that is is still maintained, I'm reaching out for some advice. I generated a minimal example with the menu generator and filled in my creds, etc. It is running on an ESP8266 dev board with rotary encoder attached as well as an external button at D8. The SHT and PIR are not included and I commented-out those code sections.

Unfortunately, all I can see on the screen is the screensaver loading bar. The rotary push button responds in the log output. The rotary itself only returns 0 or 1. button_1 does not respond.

Any advice/insights on what I'm missing here to get a minimal example set up (see config below)? Any help would be greatly appreciated!

### Pithy Screen Menu System for controlling Home Assistant
###
### Created by: Milan Korenica
### Licensed under GNU General Public License v3.0

esphome:
  name: ${unitName}
  platform: ${boardPlatform}
  board: ${boardName}
#set default for rotary
  on_boot:
    priority: 250
    then:
      - sensor.rotary_encoder.set_value:
          id: rotary_dial
          value: 0
      - binary_sensor.template.publish:
          id: api_connected
          state: OFF
      - wait_until:
          api.connected
      - sensor.rotary_encoder.set_value:
          id: rotary_dial
          value: 0
      - binary_sensor.template.publish:
          id: api_connected
          state: ON

wifi:
  ssid: !secret wifi_ssid          # Enter your WiFi SSID here. Example: `ssid: 'your_network_name'`
  password: !secret wifi_password      # Enter your wifi password here. Example: `password: 'abcde123456'`

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: pithy
    password: pithypithy

captive_portal:

# Enable logging
logger:

# Enable Home Assistant API
api:
  password: pithy

ota:
  password: pithy

#####
#####  CONFIGURATION BLOCK HERE
#####
substitutions:
  boardPlatform: ESP8266
  boardName: d1_mini
  unitName: pithy_screen_
  friendlyName: Pithy Screen 

  encoderPinA: D5
  encoderPinB: D6
  encoderSwitch: D7
  i2cData: D1
  i2cClock: D2

  button: D8

  # Menu system
  menuDepth: '1'
  menuSize: '2'

#####  END OF CONFIGURATION BLOCK 

globals:
# Screensaver
   - id: ss_x
     type: signed char
     initial_value: '0'

   - id: ss_y
     type: signed char
     initial_value: '8'

   - id: ss_vx
     type: signed char
     initial_value: '1'

   - id: ss_vy
     type: signed char
     initial_value: '1'

   - id: wi
     type: unsigned char
     initial_value: '0'

# Blank screen
   - id: s_blank
     type: bool
     initial_value: 'false'

# Menu helpers

   - id: menu_max_level
     type: unsigned char
     initial_value: '${menuDepth}'

   - id: menu_level
     type: unsigned char

   - id: menu_position
     type: unsigned char[${menuDepth}]

   - id: menu_parent
     type: unsigned char[${menuDepth}]

   - id: menu_current_node
     type: unsigned char

   - id: menu_current_label
     type: char *

   - id: menu_current_value
     type: float

   - id: menu_set_mode
     type: bool
     initial_value: 'false'

#####
#####  CONFIGURATION BLOCK HERE
#####

     # Set these to labels you want to display
   - id: menu_labels
     type: char * [${menuSize}]
     initial_value: '{"Saver","show_value"}'

     # Set these to functions of particular menu items
   - id: menu_functions
     type: unsigned char[${menuSize}]
     initial_value: '{0,1}'

     # For submenu items, what are the children?
   - id: menu_child
     type: unsigned char[${menuSize}]
     initial_value: '{0,0}'

     # How many items are there for each submenu? For continuous settings, what is the range?
   - id: menu_length
     type: unsigned char[${menuSize}]
     initial_value: '{2,0}'

script:
# What value to display for each menu item
  - id: menu_values
    then:
      - lambda: |-
          switch(id(menu_current_node)) {
            case 1: id(menu_current_value) = id(sensor_bed_temperature).state; break;

          }

# What value to set rotary encoder to for each menu item setting
  - id: menu_set_rotary
    then:
      - lambda: |-
          switch(id(menu_current_node)) {

          }

# Actions for each menu item setting
  - id: menu_actions
    then:

# Action when dial is pressed on screensaver screen
  - id: dial_ss_press
    then:

# Action when button is pressed on screensaver screen
  - id: button_1_ss_press
    then:

#####  END OF CONFIGURATION BLOCK 

  - id: ss_timeout
    mode: restart
    then:
      - delay: 1min
      - lambda: >-
          id(menu_level) = 0;
          id(menu_position)[0] = 0;
          id(menu_parent)[0] = 0;
          id(menu_current_node) = 0;
          id(menu_set_mode) = false;
          id(rotary_dial).set_value(0); 

interval:
# Screen saver logic & wait indicator
  - interval: 0.2s
    then:
      - lambda: |-
          id(ss_x) += id(ss_vx);
          id(ss_y) += id(ss_vy);
          if(id(ss_x)>45 || id(ss_x)<1) id(ss_vx) *= -1;
          if(id(ss_y)>29 || id(ss_y)<6) id(ss_vy) *= -1;
          id(wi) = ++id(wi) > 15 ? 0 : id(wi); // wait indicator logic

i2c:
  sda: ${i2cData}
  scl: ${i2cClock}
  frequency: 200kHz
  scan: True
  id: bus_a

time:
  - platform: homeassistant
    id: ha_time

font:
  - file: "fonts/OpenSans-Regular.ttf"
    id: big_font
    size: 31
    glyphs: ":0123456789"

  - file: "fonts/OpenSans-Light.ttf"
    id: small_font
    size: 19
    glyphs: ">-:/&!°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz ."

  - file: "fonts/OpenSans-Light.ttf"
    id: tiny_font
    size: 12
    glyphs: ":0123456789Conectig."

display:
  - platform: ssd1306_i2c
    model: "SH1106 128x64"
    address: 0x3C
    update_interval: 0.1s
    id: idisplay
    lambda: |-
      if (!id(api_connected).state){

        // Little animation before API is online
        it.line(56, 32, 56+id(wi), 32);

      } else {

      // Blank screen if PIR off
      if (id(s_blank)) { it.fill(COLOR_OFF); return; }

      // Screensaver is always node 0
      if(id(menu_current_node)==0) {

        // Bouncy time
        it.strftime(id(ss_x), id(ss_y), id(big_font), "%H:%M", id(ha_time).now());
      }
      else {

        // Header time
        it.strftime(0, 6, id(tiny_font), COLOR_ON, TextAlign::CENTER_LEFT, "%H:%M", id(ha_time).now());
        // Header wifi
        for(int i=0; i<5; i++) if(i<id(wifistep).state) it.filled_rectangle(102+i*5,8-i*2,4,i*2+4); else it.rectangle(102+i*5,8-i*2,4,i*2+4);
        // Header menu level
        if(id(menu_level)>0){

          // y-ccord start of the level display
          int b = 64 - (4 * id(menu_level));
          for(int i=0; i<id(menu_level); i++) {

            // Start of this symbol
            int itb = b + i*8;
            // Top line
            it.line(itb, 2, itb+6, 6);
            // Bottom line
            it.line(itb+6, 6, itb, 10);

          };
        }
        // Footer menu position
        // y-coord start of the menu pos display
        int b = 64 - (2 * id(menu_length)[id(menu_parent)[id(menu_level)]]);
        for(int i=0; i<id(menu_length)[id(menu_parent)[id(menu_level)]]; i++) {

          // Start of this symbol
          int itb = b + i*4;
          if(id(menu_position)[id(menu_level)] == i) it.filled_rectangle(itb, 61, 3, 3);
          else it.draw_pixel_at(itb+1, 62);

        };

        // Show label
        bool s = !id(menu_set_mode) &&
                (id(menu_functions)[id(menu_current_node)] == 2 || 
                 id(menu_functions)[id(menu_current_node)] == 3 || 
                 id(menu_functions)[id(menu_current_node)] == 4 || 
                 id(menu_functions)[id(menu_current_node)] == 6 );
        it.printf(64, 33, id(small_font), TextAlign::TOP_CENTER, "%s%s", id(menu_labels)[id(menu_current_node)], s ? " >" : "" );

        // Show value
        if(id(menu_set_mode)) {

          switch(id(menu_functions)[id(menu_current_node)]){

            case 3:
            case 6:
              // Value
              it.printf(127, 12, id(small_font), TextAlign::TOP_RIGHT, "%.2f", id(menu_current_value));
              // Setting progressbar, outline rectangle
              it.rectangle(0, 20, 54, 12);
              // Inner fill
              it.filled_rectangle(2, 22, round(id(rotary_dial).state*0.50), 8);
              break;
            case 4:
              // Labels
              it.printf(42, 12, id(small_font), TextAlign::TOP_RIGHT, "Off");
              it.printf(86, 12, id(small_font), TextAlign::TOP_LEFT, "On");
              // Switch, outline rectangle
              it.rectangle(52, 20, 24, 12);
              // Inner toggle
              it.filled_rectangle(54 + id(rotary_dial).state*10, 22, 10, 8);

          }

        } else {

          // Show value only if Display or Setting
          switch(id(menu_functions)[id(menu_current_node)]){

            case 1:
            case 3:
            case 6:
              it.printf(64, 12, id(small_font), TextAlign::TOP_CENTER, "%.2f", id(menu_current_value));
              break;
            case 4:
            case 7:    
              it.printf(64, 12, id(small_font), TextAlign::TOP_CENTER, "%s", id(menu_current_value) == 0 ? "Off" : "On");
              break;
            case 5: // show button
              it.rectangle(52, 19, 24, 14);
              if(id(rotary_dial_push).state == 0){
                // horizontal shades
                it.line(53, 20, 73, 20);
                it.line(54, 31, 72, 31);
                // vertical shades
                it.line(53, 20, 53, 30);
                it.line(74, 21, 74, 29);

              }
              break;

          }
        }
      }

      }

sensor:
# Do I need to pull this sensor in from HA? It threw an error when not available during push to ESP.
  - platform: homeassistant
    name: "Bed Temp"
    id: sensor_bed_temperature
    entity_id: sensor.bed_temperature

  - platform: rotary_encoder
    id: rotary_dial
    pin_a:
      number: ${encoderPinA}
      inverted: true
      mode: INPUT_PULLUP
    pin_b:
      number: ${encoderPinB}
      inverted: true
      mode: INPUT_PULLUP
    filters:
      - lambda: |-
          unsigned char a;
          if(id(menu_set_mode)) { //if set mode, rotary should go from 0 to according setting

            switch(id(menu_functions)[id(menu_current_node)]){
              case 3: 
              case 6:
                a = id(menu_length)[id(menu_current_node)] + 1; break; // continuous
              case 4: a = 2; break;  // binary
            }

          } else { //if not, it should go to level length

            //get current menu length
            a = id(menu_length)[id(menu_parent)[id(menu_level)]];

          }

          //if rotary is over length, set to length
          if(x >= a) {

            id(rotary_dial).set_value(a-1);
            return a-1;

          }
          else return x;
      - debounce: 0.02s
    resolution: 1
    min_value: 0
    on_value:
      then:
        - if:
            condition:
              api.connected
            then:
              - if:
                  condition:
                    # Are we setting or browsing?
                    lambda: 'return id(menu_set_mode);'
                  then:
                   # Change setting only if not in continuous + confirm
                    - if:
                        condition:
                          lambda: 'return !(id(menu_functions)[id(menu_current_node)] == 6);'
                        then:
                          - script.execute: menu_actions
                  else:
                    # Browsing mode, set menu position
                    - lambda: |-
                        //set current node to start of current child + rotary position
                        id(menu_current_node) = id(menu_child)[id(menu_parent)[id(menu_level)]] + x;
                        //set current level position
                        id(menu_position)[id(menu_level)] = x;
                # Update value
              - script.execute: menu_values
              - script.execute: ss_timeout
  - platform: wifi_signal
    id: wifisignal
    update_interval: 20s

  - platform: template
    id: wifistep
    update_interval: 20s
    lambda: |-
      if(isnan(id(wifisignal).state)) return 0;
      else return round((id(wifisignal).state+100)/10);

  # - platform: sht3xd
  #   temperature:
  #     name: "${friendlyName} Temperature"
  #     id: ${unitName}_temperature
  #     filters:
  #       - offset: -4
  #     on_value:
  #       then:
  #         # Logic to correctly update menu values
  #         - script.execute: menu_values
  #         - if:
  #             condition:
  #               lambda: 'return id(menu_set_mode);'
  #             then:
  #               - script.execute: menu_set_rotary
  #         # End of menu values logic
  #   humidity:
  #     name: "${friendlyName} Humidity"
  #     id: ${unitName}_humidity
  #     on_value:
  #       then:
  #         # Logic to correctly update menu values
  #         - script.execute: menu_values
  #         - if:
  #             condition:
  #               lambda: 'return id(menu_set_mode);'
  #             then:
  #               - script.execute: menu_set_rotary
  #         # End of menu values logic
  #   address: 0x44
  #   update_interval: 15s

#####
#####  CONFIGURATION BLOCK HERE
#####

# Sensors from Home Assistant

#####  END OF CONFIGURATION BLOCK 

binary_sensor:
  - platform: gpio
    id: rotary_dial_push
    pin:
      number: ${encoderSwitch}
      inverted: true
      mode: INPUT_PULLUP
    on_press:
      then:
        - if:
            condition:
              api.connected
            then:
              # Execute screensaver action
              - if:
                  condition: 
                    lambda: 'return id(menu_current_node) == 0;'
                  then:
                    script.execute: dial_ss_press

              - if:
                  condition:
                    # Execute continuous setting with confirmation when returning from set mode
                    lambda: 'return id(menu_functions)[id(menu_current_node)] == 6 && id(menu_set_mode) == true;'
                  then:
                     - script.execute: menu_actions

              - if:
                  condition:
                    # Set mode for continuous or binary setting
                    lambda: 'return (id(menu_functions)[id(menu_current_node)] == 3 || id(menu_functions)[id(menu_current_node)] == 4  || id(menu_functions)[id(menu_current_node)] == 6);'
                  then:
                    - lambda: |-
                        //toggle set mode
                        if(id(menu_set_mode)) {

                          id(menu_set_mode) = false;
                          // restore rotary value to position
                          id(rotary_dial).set_value(id(menu_position)[id(menu_level)]);

                        } else id(menu_set_mode) = true;

              - if:
                  condition:
                    # Button action
                    lambda: 'return id(menu_functions)[id(menu_current_node)] == 5;'
                  then:
                     - script.execute: menu_actions

              # If set mode, set rotary
              - if:
                  condition:
                    lambda: 'return id(menu_set_mode);'
                  then:
                    - script.execute: menu_set_rotary

              - if:
                  condition:
                    # Go to submenu. This needs to be last to prevent setting new item and also executing it
                    lambda: 'return id(menu_functions)[id(menu_current_node)] == 2;'
                  then:
                    - lambda: |-
                        //raise level up to max level
                        id(menu_level) = ++id(menu_level) > id(menu_max_level) ? id(menu_max_level) : id(menu_level);
                        //set parent node for new level
                        id(menu_parent)[id(menu_level)] = id(menu_current_node);
                        //set new current node
                        id(menu_current_node) = id(menu_child)[id(menu_parent)[id(menu_level)]];
                        //reset rotary to 0
                        id(rotary_dial).set_value(0);
                        //reset position in current level to 0
                        id(menu_position)[id(menu_level)] = 0;

              # Display entities value for each menu item
              - script.execute: menu_values
              - script.execute: ss_timeout

  - platform: gpio
    id: button_1
    pin:
      number: ${button}
      inverted: true
      mode: INPUT_PULLUP
    on_press:
      then:
        # Execute screensaver action
        - if:
            condition: 
              and:
                - lambda: 'return id(menu_current_node) == 0;'
                - api.connected
            then:
              script.execute: button_1_ss_press

        - lambda: |-
            if(id(menu_set_mode)) {

              id(menu_set_mode) = false;
              id(rotary_dial).set_value(id(menu_position)[id(menu_level)]);
            }
            else {

            if(id(menu_level) > 0) { //if we have anywhere to return

              //set new current node to current parent
              id(menu_current_node) = id(menu_parent)[id(menu_level)];
              //return to previous level
              --id(menu_level);
              //reset rotary to position for current level
              id(rotary_dial).set_value(id(menu_position)[id(menu_level)]);
            } else {

            if(id(menu_level) == 0) { //if we are at level 0, jump to saver

              //set new current node to 0
              id(menu_current_node) = 0;
              //reset rotary to 0
              id(rotary_dial).set_value(0);
              //reset menu position for current level to 0
              id(menu_position)[id(menu_level)] = 0;
            } } }

        - script.execute: ss_timeout

  - platform: template
    id: api_connected

#####
#####  CONFIGURATION BLOCK HERE
#####

  # Binary sensors from HomeAssistant

#####  END OF CONFIGURATION BLOCK
mikosoft83 commented 1 year ago

Hi

This project is in kind of a limbo as I would like to return to it some day but a little mini me is heavily limiting my time :D

Anyway, if you're stuck on loading screen, that means your device either can't connect to wifi or to home assistant. Since you mentioned that you added credentials (I assume it's wifi credentials), did you add the device to home assistant under Integrations?

ha88rgc commented 1 year ago

Hey, thanks for the quick reply!

Yeah, it's a great project!!! Thanks a lot for this. I hope you can build on it in the future! I added an encryption key to the API, which is apparently better and works better with the recent HA update. It's successfully connected to HA and I can see the text-sensors (wifi, etc) I added

# Enable Home Assistant API
api:
  encryption: 
    key: XXXX

I'm now seeing the clock screensaver and not just the loading bar, and when I turn the rotary it goes to a show_value screen that6 shows 69.40 (which is the correct temperature from the HA sensor) and says verbatim show_value. I guess that is a step forward.

The rotary only goes back and forth between the screensaver and the show_value screen. The rotary button does not do anything, neither does button_1 ...

Where do I go from here? I.e. why are the buttons not doing anything?

mikosoft83 commented 1 year ago

So the reason why your buttons are not doing anything is that you didn't assign any action to any of them. In clock screensaver mode you can assign separate action to both encoder press and button press, which is in this section: image

Once you're in the menu itself every item has (or doesn't have) action associated with it when you press the button. What you have setup is just a simple value display (for the bed temp) and value display has no action associated (since there is nothing you can do with a sensor, it can't be modified).

If you use any other action, the encoder press will take action: image For Submenu, it will take you to the submenu (and button will take you back to previous level) For any of the Continuous or Binary it will take you to a screen where you can set the value via encoder (and button takes you back) For Action button pressing the encoder will perform the action. Additionally the button always acts as back button, so it takes you out of action screen, out of submenu and in the main level it takes you to screensaver

ha88rgc commented 1 year ago

@mikosoft83 thanks a lot!! I'll check that out!

ha88rgc commented 1 year ago

Hey @mikosoft83 I have a 'somewhat' working example now.

I included a brightness portion into the code just for a single light for testing. The display shows it and I can see the brightness value and the bar (not filled).

The only weird thing is that when I use the HA sensor that is output by the menu generator then the ESP logs say that it can't show/use "255" for example because it's a string. More recent changes to HA let you pull in the attribute of an entity directly, so that's what I did leaving all the other naming conventions the same so it wouldn't mess up the config.

I can see the value now, the only weird thing is that when I go on the brightness page with the lamp on, it gets turned off and when the lamp isn't on, then there is no state, i.e. no brightness (I assume that is expected and maybe necessitates a toggle screen to first turn the lamp on?)

# Sensors from Home Assistant
  - platform: homeassistant
    name: "HA sensor sensor.pithy_light_bar1_brightness"
    entity_id: light.bar1 #sensor.pithy_light_bar1_brightness
    attribute: brightness
    id: light_bar1_brightness
    internal: true
    on_value:
      then:
        # Logic to correctly update menu values
        - script.execute: menu_values
        - if:
            condition:
              lambda: 'return id(menu_set_mode);'
            then:
              - script.execute: menu_set_rotary

Anyway, maybe using attributes directly could be a cool feature in the future. Full config below if you have any insights as to why it's just switching off the lamp instead of using the brightness value that is sent for it to be manipulated.

### Pithy Screen Menu System for controlling Home Assistant
###
### Created by: Milan Korenica
### Licensed under GNU General Public License v3.0

esphome:
  name: ${unitName}
  platform: ${boardPlatform}
  board: ${boardName}
#set default for rotary
  on_boot:
    priority: 250
    then:
      - sensor.rotary_encoder.set_value:
          id: rotary_dial
          value: 0
      - binary_sensor.template.publish:
          id: api_connected
          state: OFF
      - wait_until:
          api.connected
      - sensor.rotary_encoder.set_value:
          id: rotary_dial
          value: 0
      - binary_sensor.template.publish:
          id: api_connected
          state: ON

wifi:
  ssid: !secret wifi_ssid          # Enter your WiFi SSID here. Example: `ssid: 'your_network_name'`
  password: !secret wifi_password      # Enter your wifi password here. Example: `password: 'abcde123456'`

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: pithy
    password: pithypithy

captive_portal:

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption: 
    key: !secret pithyKey

ota:
  password: pithy

#####
#####  CONFIGURATION BLOCK HERE
#####
substitutions:
  boardPlatform: ESP8266
  boardName: d1_mini
  unitName: pithy_
  friendlyName: Pithy

  encoderPinA: D5
  encoderPinB: D6
  encoderSwitch: D7
  i2cData: D1
  i2cClock: D2

  button: D8

  # Menu system
  menuDepth: '1'
  menuSize: '3'

#####  END OF CONFIGURATION BLOCK 

text_sensor:
  - platform: version
    hide_timestamp: true
    name: "${friendlyName} ESPHome Version"
    entity_category: diagnostic
  - platform: wifi_info
    ip_address:
      name: "${friendlyName} IP Address"
      icon: mdi:wifi
      entity_category: diagnostic
    ssid:
      name: "${friendlyName} Connected SSID"
      icon: mdi:wifi-strength-2
      entity_category: diagnostic

globals:
# Screensaver
   - id: ss_x
     type: signed char
     initial_value: '0'

   - id: ss_y
     type: signed char
     initial_value: '8'

   - id: ss_vx
     type: signed char
     initial_value: '1'

   - id: ss_vy
     type: signed char
     initial_value: '1'

   - id: wi
     type: unsigned char
     initial_value: '0'

# Blank screen
   - id: s_blank
     type: bool
     initial_value: 'false'

# Menu helpers

   - id: menu_max_level
     type: unsigned char
     initial_value: '${menuDepth}'

   - id: menu_level
     type: unsigned char

   - id: menu_position
     type: unsigned char[${menuDepth}]

   - id: menu_parent
     type: unsigned char[${menuDepth}]

   - id: menu_current_node
     type: unsigned char

   - id: menu_current_label
     type: char *

   - id: menu_current_value
     type: float

   - id: menu_set_mode
     type: bool
     initial_value: 'false'

#####
#####  CONFIGURATION BLOCK HERE
#####

     # Set these to labels you want to display
   - id: menu_labels
     type: char * [${menuSize}]
     initial_value: '{"Saver","Bed temp","Brightness"}'

     # Set these to functions of particular menu items
   - id: menu_functions
     type: unsigned char[${menuSize}]
     initial_value: '{0,1,3}'

     # For submenu items, what are the children?
   - id: menu_child
     type: unsigned char[${menuSize}]
     initial_value: '{0,0,0}'

     # How many items are there for each submenu? For continuous settings, what is the range?
   - id: menu_length
     type: unsigned char[${menuSize}]
     initial_value: '{3,0,255}'

script:
# What value to display for each menu item
  - id: menu_values
    then:
      - lambda: |-
          switch(id(menu_current_node)) {
            case 1: id(menu_current_value) = id(sensor_bed_temperature).state; break;

            case 2: id(menu_current_value) = id(light_bar1_brightness).state; break;

          }

# What value to set rotary encoder to for each menu item setting
  - id: menu_set_rotary
    then:
      - lambda: |-
          switch(id(menu_current_node)) {
            case 2: id(rotary_dial).set_value(round(id(light_bar1_brightness).state)); break;

          }

# Actions for each menu item setting
  - id: menu_actions
    then:
      - if:
          condition:
            lambda: 'return id(menu_current_node) == 2;'
          then:
            homeassistant.service:
              variables:
                x: 'return id(rotary_dial).state;'
              service: light.turn_on
              data_template:
                entity_id: light.bar1
                brightness: '{{ x }}'

# Action when dial is pressed on screensaver screen
  - id: dial_ss_press
    then:

# Action when button is pressed on screensaver screen
  - id: button_1_ss_press
    then:

#####  END OF CONFIGURATION BLOCK 

  - id: ss_timeout
    mode: restart
    then:
      - delay: 1min
      - lambda: >-
          id(menu_level) = 0;
          id(menu_position)[0] = 0;
          id(menu_parent)[0] = 0;
          id(menu_current_node) = 0;
          id(menu_set_mode) = false;
          id(rotary_dial).set_value(0); 

interval:
# Screen saver logic & wait indicator
  - interval: 0.2s
    then:
      - lambda: |-
          id(ss_x) += id(ss_vx);
          id(ss_y) += id(ss_vy);
          if(id(ss_x)>45 || id(ss_x)<1) id(ss_vx) *= -1;
          if(id(ss_y)>29 || id(ss_y)<6) id(ss_vy) *= -1;
          id(wi) = ++id(wi) > 15 ? 0 : id(wi); // wait indicator logic

i2c:
  sda: ${i2cData}
  scl: ${i2cClock}
  frequency: 200kHz
  scan: True
  id: bus_a

time:
  - platform: homeassistant
    id: ha_time

font:
  - file: "fonts/OpenSans-Regular.ttf"
    id: big_font
    size: 31
    glyphs: ":0123456789"

  - file: "fonts/OpenSans-Light.ttf"
    id: small_font
    size: 19
    glyphs: ">-:/&!°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz ._"

  - file: "fonts/OpenSans-Light.ttf"
    id: tiny_font
    size: 12
    glyphs: ":0123456789Conectig."

display:
  - platform: ssd1306_i2c
    model: "SH1106 128x64"
    address: 0x3C
    update_interval: 0.1s
    id: idisplay
    lambda: |-
      if (!id(api_connected).state){

        // Little animation before API is online
        it.line(56, 32, 56+id(wi), 32);

      } else {

      // Blank screen if PIR off
      if (id(s_blank)) { it.fill(COLOR_OFF); return; }

      // Screensaver is always node 0
      if(id(menu_current_node)==0) {

        // Bouncy time
        it.strftime(id(ss_x), id(ss_y), id(big_font), "%H:%M", id(ha_time).now());
      }
      else {

        // Header time
        it.strftime(0, 6, id(tiny_font), COLOR_ON, TextAlign::CENTER_LEFT, "%H:%M", id(ha_time).now());
        // Header wifi
        for(int i=0; i<5; i++) if(i<id(wifistep).state) it.filled_rectangle(102+i*5,8-i*2,4,i*2+4); else it.rectangle(102+i*5,8-i*2,4,i*2+4);
        // Header menu level
        if(id(menu_level)>0){

          // y-ccord start of the level display
          int b = 64 - (4 * id(menu_level));
          for(int i=0; i<id(menu_level); i++) {

            // Start of this symbol
            int itb = b + i*8;
            // Top line
            it.line(itb, 2, itb+6, 6);
            // Bottom line
            it.line(itb+6, 6, itb, 10);

          };
        }
        // Footer menu position
        // y-coord start of the menu pos display
        int b = 64 - (2 * id(menu_length)[id(menu_parent)[id(menu_level)]]);
        for(int i=0; i<id(menu_length)[id(menu_parent)[id(menu_level)]]; i++) {

          // Start of this symbol
          int itb = b + i*4;
          if(id(menu_position)[id(menu_level)] == i) it.filled_rectangle(itb, 61, 3, 3);
          else it.draw_pixel_at(itb+1, 62);

        };

        // Show label
        bool s = !id(menu_set_mode) &&
                (id(menu_functions)[id(menu_current_node)] == 2 || 
                 id(menu_functions)[id(menu_current_node)] == 3 || 
                 id(menu_functions)[id(menu_current_node)] == 4 || 
                 id(menu_functions)[id(menu_current_node)] == 6 );
        it.printf(64, 33, id(small_font), TextAlign::TOP_CENTER, "%s%s", id(menu_labels)[id(menu_current_node)], s ? " >" : "" );

        // Show value
        if(id(menu_set_mode)) {

          switch(id(menu_functions)[id(menu_current_node)]){

            case 3:
            case 6:
              // Value
              it.printf(127, 12, id(small_font), TextAlign::TOP_RIGHT, "%.2f", id(menu_current_value));
              // Setting progressbar, outline rectangle
              it.rectangle(0, 20, 54, 12);
              // Inner fill
              it.filled_rectangle(2, 22, round(id(rotary_dial).state*0.50), 8);
              break;
            case 4:
              // Labels
              it.printf(42, 12, id(small_font), TextAlign::TOP_RIGHT, "Off");
              it.printf(86, 12, id(small_font), TextAlign::TOP_LEFT, "On");
              // Switch, outline rectangle
              it.rectangle(52, 20, 24, 12);
              // Inner toggle
              it.filled_rectangle(54 + id(rotary_dial).state*10, 22, 10, 8);

          }

        } else {

          // Show value only if Display or Setting
          switch(id(menu_functions)[id(menu_current_node)]){

            case 1:
            case 3:
            case 6:
              it.printf(64, 12, id(small_font), TextAlign::TOP_CENTER, "%.2f", id(menu_current_value));
              break;
            case 4:
            case 7:    
              it.printf(64, 12, id(small_font), TextAlign::TOP_CENTER, "%s", id(menu_current_value) == 0 ? "Off" : "On");
              break;
            case 5: // show button
              it.rectangle(52, 19, 24, 14);
              if(id(rotary_dial_push).state == 0){
                // horizontal shades
                it.line(53, 20, 73, 20);
                it.line(54, 31, 72, 31);
                // vertical shades
                it.line(53, 20, 53, 30);
                it.line(74, 21, 74, 29);

              }
              break;

          }
        }
      }

      }

sensor:

  - platform: rotary_encoder
    id: rotary_dial
    pin_a:
      number: ${encoderPinA}
      inverted: true
      mode: INPUT_PULLUP
    pin_b:
      number: ${encoderPinB}
      inverted: true
      mode: INPUT_PULLUP
    filters:
      - lambda: |-
          unsigned char a;
          if(id(menu_set_mode)) { //if set mode, rotary should go from 0 to according setting

            switch(id(menu_functions)[id(menu_current_node)]){
              case 3: 
              case 6:
                a = id(menu_length)[id(menu_current_node)] + 1; break; // continuous
              case 4: a = 2; break;  // binary
            }

          } else { //if not, it should go to level length

            //get current menu length
            a = id(menu_length)[id(menu_parent)[id(menu_level)]];

          }

          //if rotary is over length, set to length
          if(x >= a) {

            id(rotary_dial).set_value(a-1);
            return a-1;

          }
          else return x;
      - debounce: 0.02s
    resolution: 1
    min_value: 0
    on_value:
      then:
        - if:
            condition:
              api.connected
            then:
              - if:
                  condition:
                    # Are we setting or browsing?
                    lambda: 'return id(menu_set_mode);'
                  then:
                   # Change setting only if not in continuous + confirm
                    - if:
                        condition:
                          lambda: 'return !(id(menu_functions)[id(menu_current_node)] == 6);'
                        then:
                          - script.execute: menu_actions
                  else:
                    # Browsing mode, set menu position
                    - lambda: |-
                        //set current node to start of current child + rotary position
                        id(menu_current_node) = id(menu_child)[id(menu_parent)[id(menu_level)]] + x;
                        //set current level position
                        id(menu_position)[id(menu_level)] = x;
                # Update value
              - script.execute: menu_values
              - script.execute: ss_timeout

  - platform: wifi_signal
    id: wifisignal
    update_interval: 20s

  - platform: template
    id: wifistep
    update_interval: 20s
    lambda: |-
      if(isnan(id(wifisignal).state)) return 0;
      else return round((id(wifisignal).state+100)/10);

  # - platform: sht3xd
  #   temperature:
  #     name: "${friendlyName} Temperature"
  #     id: ${unitName}_temperature
  #     filters:
  #       - offset: -4
  #     on_value:
  #       then:
  #         # Logic to correctly update menu values
  #         - script.execute: menu_values
  #         - if:
  #             condition:
  #               lambda: 'return id(menu_set_mode);'
  #             then:
  #               - script.execute: menu_set_rotary
  #         # End of menu values logic
  #   humidity:
  #     name: "${friendlyName} Humidity"
  #     id: ${unitName}_humidity
  #     on_value:
  #       then:
  #         # Logic to correctly update menu values
  #         - script.execute: menu_values
  #         - if:
  #             condition:
  #               lambda: 'return id(menu_set_mode);'
  #             then:
  #               - script.execute: menu_set_rotary
  #         # End of menu values logic
  #   address: 0x44
  #   update_interval: 15s

#####
#####  CONFIGURATION BLOCK HERE
#####

# Sensors from Home Assistant
  - platform: homeassistant
    name: "HA sensor sensor.pithy_light_bar1_brightness"
    entity_id: light.bar1 #sensor.pithy_light_bar1_brightness
    attribute: brightness
    id: light_bar1_brightness
    internal: true
    on_value:
      then:
        # Logic to correctly update menu values
        - script.execute: menu_values
        - if:
            condition:
              lambda: 'return id(menu_set_mode);'
            then:
              - script.execute: menu_set_rotary
        # End of menu values logic
  - platform: homeassistant
    name: "Bed Temp"
    id: sensor_bed_temperature
    entity_id: sensor.bed_temperature

#####  END OF CONFIGURATION BLOCK 

binary_sensor:
  - platform: gpio
    id: rotary_dial_push
    pin:
      number: ${encoderSwitch}
      inverted: true
      mode: INPUT_PULLUP
    on_press:
      then:
        - if:
            condition:
              api.connected
            then:
              # Execute screensaver action
              - if:
                  condition: 
                    lambda: 'return id(menu_current_node) == 0;'
                  then:
                    script.execute: dial_ss_press

              - if:
                  condition:
                    # Execute continuous setting with confirmation when returning from set mode
                    lambda: 'return id(menu_functions)[id(menu_current_node)] == 6 && id(menu_set_mode) == true;'
                  then:
                     - script.execute: menu_actions

              - if:
                  condition:
                    # Set mode for continuous or binary setting
                    lambda: 'return (id(menu_functions)[id(menu_current_node)] == 3 || id(menu_functions)[id(menu_current_node)] == 4  || id(menu_functions)[id(menu_current_node)] == 6);'
                  then:
                    - lambda: |-
                        //toggle set mode
                        if(id(menu_set_mode)) {

                          id(menu_set_mode) = false;
                          // restore rotary value to position
                          id(rotary_dial).set_value(id(menu_position)[id(menu_level)]);

                        } else id(menu_set_mode) = true;

              - if:
                  condition:
                    # Button action
                    lambda: 'return id(menu_functions)[id(menu_current_node)] == 5;'
                  then:
                     - script.execute: menu_actions

              # If set mode, set rotary
              - if:
                  condition:
                    lambda: 'return id(menu_set_mode);'
                  then:
                    - script.execute: menu_set_rotary

              - if:
                  condition:
                    # Go to submenu. This needs to be last to prevent setting new item and also executing it
                    lambda: 'return id(menu_functions)[id(menu_current_node)] == 2;'
                  then:
                    - lambda: |-
                        //raise level up to max level
                        id(menu_level) = ++id(menu_level) > id(menu_max_level) ? id(menu_max_level) : id(menu_level);
                        //set parent node for new level
                        id(menu_parent)[id(menu_level)] = id(menu_current_node);
                        //set new current node
                        id(menu_current_node) = id(menu_child)[id(menu_parent)[id(menu_level)]];
                        //reset rotary to 0
                        id(rotary_dial).set_value(0);
                        //reset position in current level to 0
                        id(menu_position)[id(menu_level)] = 0;

              # Display entities value for each menu item
              - script.execute: menu_values
              - script.execute: ss_timeout

  - platform: gpio
    id: button_1
    pin:
      number: ${button}
      inverted: true
      mode: INPUT_PULLUP
    on_press:
      then:
        # Execute screensaver action
        - if:
            condition: 
              and:
                - lambda: 'return id(menu_current_node) == 0;'
                - api.connected
            then:
              script.execute: button_1_ss_press

        - lambda: |-
            if(id(menu_set_mode)) {

              id(menu_set_mode) = false;
              id(rotary_dial).set_value(id(menu_position)[id(menu_level)]);
            }
            else {

            if(id(menu_level) > 0) { //if we have anywhere to return

              //set new current node to current parent
              id(menu_current_node) = id(menu_parent)[id(menu_level)];
              //return to previous level
              --id(menu_level);
              //reset rotary to position for current level
              id(rotary_dial).set_value(id(menu_position)[id(menu_level)]);
            } else {

            if(id(menu_level) == 0) { //if we are at level 0, jump to saver

              //set new current node to 0
              id(menu_current_node) = 0;
              //reset rotary to 0
              id(rotary_dial).set_value(0);
              //reset menu position for current level to 0
              id(menu_position)[id(menu_level)] = 0;
            } } }

        - script.execute: ss_timeout

  - platform: template
    id: api_connected

#####
#####  CONFIGURATION BLOCK HERE
#####

  # Binary sensors from HomeAssistant

#####  END OF CONFIGURATION BLOCK
mikosoft83 commented 1 year ago

To be honest, when I did the version of the script you're using I didn't yet have a dimmable lamp so I just did it on assumption how brightness works. I do have a dimmer now so I also noticed there is no brightness attribute once lamp is off. That probably messes up the ESP code, I don't know what value the sensor gets if there is no attribute. Using the attributes directly is on my (long standing) todo list. Actually, the attribute support in ESPhome is partly a result of the whole Pithy project of which I was a part and yet I never found time to actually implement it to my code. I do have a working code for attributes for another device that uses a LED ring instead of display and it shouldn't be that difficult to reuse it (it's the same code for the most part) but again, time is the enemy. It's mostly about "details" such as config import that needs to be most heavily reworked for the new code.

Anyway, one thing that you can try is change this block like this:

  - id: menu_actions
    then:
      - if:
          condition:
            lambda: 'return id(menu_current_node) == 2;'
          then:
            homeassistant.service:
              variables:
                x: 'stoi(return id(rotary_dial).state);'
              service: light.turn_on
              data_template:
                entity_id: light.bar1
                brightness: '{{ x }}'

Change is in this line of your config: x: 'return id(rotary_dial).state;'

In my version it should convert the string to integer. Why it passes the brightness value as string I have no idea but once I get to it I'll check it out with my dimmer.

ha88rgc commented 1 year ago

Hey @mikosoft83 thanks a lot for your input!

I tried 'x: stoi(return id(rotary_dial).state);' and x: 'std::stoi(return (id(rotary_dial).state));' ##FAILS this fails with the following error : pithy_screen_withsubmenu.yaml:193:17: error: expected primary-expression before 'return'

src/esphome/core/automation.h:33:3: error: 'esphome::TemplatableValue<T, X>::TemplatableValue(F) [with F = esphome::api::TemplatableStringValue<X>::TemplatableStringValue<setup()::<lambda()>, 0>::<lambda()>; typename std::enable_if<std::is_invocable<F, X ...>::value, int>::type <anonymous> = 0; T = std::__cxx11::basic_string<char>; X = {}]', declared using local type 'esphome::api::TemplatableStringValue<X>::TemplatableStringValue<setup()::<lambda()>, 0>::<lambda()>', is used but never defined [-fpermissive]

Any suggestions are greatly appreciated! I added std:: as well because not having it initially threw a different error. I assume error: expected primary-expression before 'return' means that somethings is referenced but not in the code?

One potential bug I found with a simple fix: In the glyphs below, the _ was missing, which threw an error reading/showing the numbers from the Display Value menu. Just thought you might want to make a note of that.

  - file: "fonts/OpenSans-Light.ttf"
    id: small_font
    size: 19
    glyphs: ">-:/&!°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz ._"

When you do get some time to do more dev on this project, I'd be happy to help QA and see if stuff breaks!

mikosoft83 commented 1 year ago

Interesting bit about that underscore. I wonder why would a number need an underscore? Maybe there lies the culprit. Can you please show me the value of whatever you're trying to display?

ha88rgc commented 1 year ago

@mikosoft83 I don't know. In the first pic, you can see test-value which is what I named it, before it was show_value and that wouldn't work. The current version, thus, probably doesn't need the _. The actual sensor value from HA is displaying correctly in the show value menu.

IMG_1884

The brightness value, however, is not pulling in when set to sensor.pithy_light_bar1_brightness in ESPHome.

HA config

  - platform: template
    sensors:

      pithy_light_bar1_brightness:
        value_template: >
          "{{ (state_attr('light.bar1', 'brightness')  | int) | round(0)}}"
#value_template: "{{ (states.light.bar1.attributes.brightness) | int }}"

IMG_1885

Currently, I'm getting the error(s) mentioned above because of 'x: stoi(return id(rotary_dial).state);' or 'x: 'std::stoi(return (id(rotary_dial).state));' . Everything else works fine, i.e. Value Display, Toggle, Action button.

IMG_1886

With

  - platform: homeassistant
    name: "HA sensor sensor.pithy_light_bar1_brightness"
    entity_id: light.bar1 #sensor.pithy_light_bar1_brightness
    attribute: brightness
    id: light_bar1_brightness
    internal: true
    on_value:
      then:
        # Logic to correctly update menu values
        - script.execute: menu_values
        - if:
            condition:
              lambda: 'return id(menu_set_mode);'
            then:
              - script.execute: menu_set_rotary

I'm getting the brightness value pulled in 255.00 IMG_1887

However, the actual manipulation of the value doesn't work. But, pressing the rotary button again here, will set the brightness to 0 and turn the lamp off. When I turn the rotary, the log doesn't show any values, just

[09:14:33][D][sensor:126]: 'rotary_dial': Sending state -1.00000 steps with 0 decimals of accuracy
[09:14:33][D][script:077]: Script 'ss_timeout' restarting (mode: restart)
[09:14:33][D][sensor:126]: 'rotary_dial': Sending state -1.00000 steps with 0 decimals of accuracy
[09:14:33][D][script:077]: Script 'ss_timeout' restarting (mode: restart)
[09:14:35][D][sensor:126]: 'rotary_dial': Sending state -1.00000 steps with 0 decimals of accuracy
[09:14:35][D][script:077]: Script 'ss_timeout' restarting (mode: restart)

IMG_1888

mikosoft83 commented 1 year ago

Okay, that was stupid of me. The correct fix for the line is x: 'return stoi(id(rotary_dial).state);'

Try with that and let me know although I'm still baffled why would the state of the rotary encoder be a string.

It seems to me the brightness sensor doesn't handle well the fact that there is no brightness attribute when turned off. Can you turn on the light in other way and watch what you see in the ESPhome device log when it turns on and off?

ha88rgc commented 1 year ago

@mikosoft83 Thanks! Okay, so when I have this

  - platform: homeassistant
    name: "HA sensor sensor.pithy_light_bar1_brightness"
    entity_id: sensor.pithy_light_bar1_brightness
    id: light_bar1_brightness
    internal: true
    on_value:
      then:
        # Logic to correctly update menu values
        - script.execute: menu_values
        - if:
            condition:
              lambda: 'return id(menu_set_mode);'
            then:
              - script.execute: menu_set_rotary

In the sensor section, and

              variables:
                x: 'return (id(rotary_dial).state);'

in the menu options, the display shows nan for the brightness value. The log-output then says [W][homeassistant.sensor:015]: Can't convert '"255"' to number! when I use anything else in the menu options, i.e. x: 'return stoi(id(rotary_dial).state);' or x: 'return stoi(id(rotary_dial).state);' or x: 'return std::stoi(id(rotary_dial).state);' I get a bunch of errors in the logs, for example

pithy_screen_withsubmenu.yaml: In function 'void setup()':
pithy_screen_withsubmenu.yaml:966:60: warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
pithy_screen_withsubmenu.yaml:966:68: warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
pithy_screen_withsubmenu.yaml:966:81: warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
pithy_screen_withsubmenu.yaml:966:94: warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
pithy_screen_withsubmenu.yaml:966:108: warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
pithy_screen_withsubmenu.yaml:966:119: warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
pithy_screen_withsubmenu.yaml:966:134: warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
pithy_screen_withsubmenu.yaml: In lambda function:
pithy_screen_withsubmenu.yaml:193:43: error: no matching function for call to 'stoi(float&)'
In file included from /home/mraess/.platformio/packages/toolchain-xtensa/xtensa-lx106-elf/include/c++/10.3.0/string:55,
                 from src/esphome/components/socket/socket.h:2,
                 from src/esphome/components/api/api_frame_helper.h:13,
                 from src/esphome/components/api/api_connection.h:3,
                 from src/esphome.h:3,
                 from src/main.cpp:3:

I'm wondering if there isn't a way to send the value from HA as an integer directly?

The other thing is that when I'm in the actual brightness menu (with the bar), then the rotary doesn't actually produce any values in the logs but rather seems to time out and reset itself??

[18:47:06][D][script:077]: Script 'ss_timeout' restarting (mode: restart)
[18:47:06][D][binary_sensor:036]: 'rotary_dial_push': Sending state OFF
mikosoft83 commented 1 year ago

You can try "atoi" instead of "stoi" Or you can remove the "stoi" from the menu script and add filter to the sensor:

  - platform: homeassistant
    name: "HA sensor sensor.pithy_light_bar1_brightness"
    entity_id: sensor.pithy_light_bar1_brightness
...
    filters:
     - lambda: return atoi(x);
...

Also yes, apparently the value is sent as string and when the old method of using template sensors was used it got converted to integer in template. Seems like a bug in HA and apparently it's been 2 years and it's not fixed.

ha88rgc commented 1 year ago

@mikosoft83 Thanks a lot for all your input! I've tried that as well and it didn't work :( I'll dabble around some more and report back.

HA and apparently it's been 2 years and it's not fixed That seems like an easier bug to fix and quite an important one. I'll try to somehow send the value out of HA as an int and report back.

ha88rgc commented 1 year ago

@mikosoft83 so unfortunately non of the permutations of stoi, std:stoi, atoi, or std:atoi worked. I have managed a simple setup where the brightness level of the lamp is at least shown in the display (see pics below), by using the entity light.bar1 directly with the attribute brightness. However, as soon as I go into the menu the light turns off immediately (see second pic) because it seems the rotary is not set to the actual value coming in. Could this be because it shows as a float instead of int ?

  - platform: homeassistant
    name: "HA sensor sensor.pithy_light_bar1_brightness"
    entity_id: light.bar1
    attribute: brightness
    filters:
     - lambda: return x;
    id: light_bar1_brightness
    internal: true
    on_value:
      then:
        # Logic to correctly update menu values
        - script.execute: menu_values
        - if:
            condition:
              lambda: 'return id(menu_set_mode);'
            then:
              - script.execute: menu_set_rotary
        # End of menu values logic

Any changes to the filter in the above code, such as lambda: return atoi(x) didn't work and caused errors during compile.

IMG_1895

IMG_1897

Could this portion

  - id: menu_set_rotary
    then:
      - lambda: |- # for some reason the rotary is not set to the right value maybe because it's float instead of int??
          switch(id(menu_current_node)) {
            case 2: id(rotary_dial).set_value(round(id(light_bar1_brightness).state)); break;

          }

be the reason for why the rotary value isn't updated?

mikosoft83 commented 1 year ago

That would be a weird behavior. It's true that the value is float but it's rounded in the rotary assignment and should get cast to int. If you wish you can try to cast it manually but it probably won't make much difference:

case 2: id(rotary_dial).set_value((int)round(id(light_bar1_brightness).state)); break;

ha88rgc commented 1 year ago

@mikosoft83 yeah that didn't work either unfortunately. The rotary doesn't respond at all in this menu.

mikosoft83 commented 1 year ago

I'll have to try this with my light one day but as I mentioned my time is extremely limited nowadays :/

ha88rgc commented 1 year ago

@mikosoft83 I hear you! Thanks for sticking with me for so long haha. Please let me know once you figure it out with your light.

ha88rgc commented 1 year ago

@mikosoft83 one more question. What would I have to do to show a pic as the screensaver similar to the original ioios code?

mikosoft83 commented 1 year ago

To be honest I haven't used pictures in ESPHome. So I guess look up how John dislays the picture in his code and then replace this line in my code with his (it's under the display lambda):

        // Bouncy time
        it.strftime(id(ss_x), id(ss_y), id(big_font), "%H:%M", id(ha_time).now());

If you want the pic to bounce around like my clock you can use the id(ss_x), id(ss_y) as coordinates but you need to fix the limits in this code:

interval:
# Screen saver logic & wait indicator
  - interval: 0.2s
    then:
      - lambda: |-
          id(ss_x) += id(ss_vx);
          id(ss_y) += id(ss_vy);
          if(id(ss_x)>45 || id(ss_x)<1) id(ss_vx) *= -1;
          if(id(ss_y)>29 || id(ss_y)<6) id(ss_vy) *= -1;
          id(wi) = ++id(wi) > 15 ? 0 : id(wi); // wait indicator logic

Numbers 45 and 29 are the rightmost and lowermost limits respectively of where the content (in my case clock) can go before it "bounces" back.

ha88rgc commented 1 year ago

@mikosoft83 Thanks, I'll have to check that out.