Finally Killed The Last Circuit Python: Kitchen Clock

For a long time I have had multiple RGB panels in my house that are a clock and weather display. I have worked from home for 20 years and I need to know the time to jump on calls and such. It was based on a project I found on Adafruit but it proved to be really unreliable. They often dropped off the WiFi and when the mqtt server is down, they go down.

So then I found this AWESOME project that makes the ESP32-Trinity work with ESPHome.

I am almost feature complete - just have not nailed down the scrolling and message like the clock I am replacing. I use it to show caller id, license plates in the driveway, and other house alerts (like wild fire smoke and such). I just need to fine tune the sensor or something - so I can send in JSON with color and such. Oh and maybe use the light sensor they come with to control brightness locally.

Right now if you set the text sensor it scrolls that, then you clear it, it goes back to temp/aqi/humid.

Also the night mode boolean is because at 10pm and 45 min before sunrise all my lights shift from night-mode-red to day-mode-warm white.

I apologize that I lifted the scrolling text from somewhere but I forgot to note where, so you out there…your code worked!

Once I have a few things cleaned up I’ll drop this in my gh repo with my other yaml’s but in the meantime hack at it and make it yours!

text_sensor:
  - platform: homeassistant
    id: clock_scroll_text
    entity_id: sensor.clock_scrolling_text

binary_sensor:
  - platform: homeassistant
    id: household_night_mode
    entity_id: binary_sensor.night_light_mode
    internal: true
    publish_initial_state: true

sensor:
  - platform: uptime
    name: "Uptime Sensor"
    update_interval: 60s

  - platform: wifi_signal
    name: "WiFi Signal"
    update_interval: 60s

  - platform: homeassistant
    id: outside_average_temperature
    entity_id: sensor.outside_average_temperature
    unit_of_measurement: "°F"
    device_class: "temperature"
    internal: true
    accuracy_decimals: 0

  - platform: homeassistant
    id: outside_average_humid
    entity_id: sensor.outside_average_humidity
    unit_of_measurement: "%"
    device_class: "humidity"
    internal: true
    accuracy_decimals: 0

  - platform: homeassistant
    id: outside_average_aqi
    entity_id: sensor.outside_average_aqi_pm_2_5
    device_class: "aqi"
    internal: true
    accuracy_decimals: 0

  - platform: homeassistant
    id: inside_average_temperature
    entity_id: ${local_temp_sensor}
    unit_of_measurement: "°F"
    device_class: "temperature"
    internal: true
    accuracy_decimals: 0

  - platform: homeassistant
    id: inside_average_humid
    entity_id: ${local_humid_sensor}
    unit_of_measurement: "%"
    device_class: "humidity"
    internal: true
    accuracy_decimals: 0

  - platform: homeassistant
    id: inside_average_aqi
    entity_id: ${local_aqi_sensor}
    device_class: "aqi"
    internal: true
    accuracy_decimals: 0

# Define assets we need for using on the display
font:
  - file: "fonts/clock26.bdf"
    id: clock2
  - file: "fonts/6x12.bdf"
    id: temp2

# Display and pages configuration  
display:
  platform: hub75_default
  id: my_trinity_display
  auto_clear_enabled: true
  #pin_e: GPIO18
  brightness: 200
  max_brightness: 255
  min_brightness: 0
  rgb_order: GRB
  #update_interval: 5s
  pages:
    - id: normal_clock
      lambda: |-
        if (id(household_night_mode).state) {
          //night mode
          it.strftime(32, -5, id(clock2), id(COLOR_CSS_RED), TextAlign::TOP_CENTER, "%H:%M", id(esptime).now());
          it.printf(2, 20, id(temp2), id(COLOR_CSS_RED), TextAlign::TOP_LEFT, "%.0f%%", id(outside_average_humid).state);
          it.printf(36, 20, id(temp2), id(COLOR_CSS_RED), TextAlign::TOP_CENTER, "%.0f°", id(outside_average_temperature).state);
          it.printf(63, 20, id(temp2), id(COLOR_CSS_RED), TextAlign::TOP_RIGHT, "%.0f%", id(outside_average_aqi).state);

          auto tick_x = id(sntp_time).now().second;
          it.line(tick_x,1,tick_x+4,1,id(COLOR_CSS_RED));

          it.rectangle(0, 0, 64, 32, id(COLOR_CSS_RED));

        } else  {
          //day mode
          it.strftime(32, -5, id(clock2), id(COLOR_CSS_CLOCK), TextAlign::TOP_CENTER, "%H:%M", id(esptime).now());
          it.printf(2, 20, id(temp2), id(COLOR_CSS_CADETBLUE), TextAlign::TOP_LEFT, "%.0f%%", "%.0f%%", id(outside_average_humid).state);
          it.printf(36, 20, id(temp2), id(COLOR_CSS_CADETBLUE), TextAlign::TOP_CENTER, "%.0f°", id(outside_average_temperature).state);
          it.printf(63, 20, id(temp2), id(COLOR_CSS_CADETBLUE), TextAlign::TOP_RIGHT, "%.0f%", id(outside_average_aqi).state);
          it.rectangle(0, 0, 64, 32, id(COLOR_CSS_DARKOLIVEGREEN));

          auto tick_x = id(sntp_time).now().second;
          it.line(tick_x,1,tick_x+4,1,id(COLOR_CSS_DARKORANGE));

          it.rectangle(0, 0, 64, 32, id(COLOR_CSS_DARKOLIVEGREEN));
        }


    - id: scroll_clock
      lambda: |-
        if (id(household_night_mode).state) {
          //night mode
          it.strftime(32, -5, id(clock2), id(COLOR_CSS_RED), TextAlign::TOP_CENTER, "%H:%M", id(esptime).now());
          it.start_clipping(0, 20, 64, 32);
            std::string printout=id(clock_scroll_text).state.c_str();
            int clength = printout.length();
            id(scroll_lenght) = clength * 6;
            it.print(id(scroll_x), 20, id(temp2), id(COLOR_CSS_RED), printout.c_str());
          it.end_clipping();

          auto tick_x = id(sntp_time).now().second;
          it.line(tick_x,1,tick_x+4,1,id(COLOR_CSS_RED));

          it.rectangle(0, 0, 64, 32, id(COLOR_CSS_RED));

        } else  {
          //day mode
          it.strftime(32, -5, id(clock2), id(COLOR_CSS_CLOCK), TextAlign::TOP_CENTER, "%H:%M", id(esptime).now());
          it.start_clipping(0, 20, 64, 32);
            std::string printout=id(clock_scroll_text).state.c_str();
            int clength = printout.length();
            id(scroll_lenght) = clength * 6;
            it.print(id(scroll_x), 20, id(temp2), id(COLOR_CSS_CADETBLUE), printout.c_str());
          it.end_clipping();

          auto tick_x = id(sntp_time).now().second;
          it.line(tick_x,1,tick_x+4,1,id(COLOR_CSS_DARKORANGE));

          it.rectangle(0, 0, 64, 32, id(COLOR_CSS_DARKOLIVEGREEN));

        }  


interval:
  - interval: 1s
    then:
      - component.update: my_trinity_display

  - interval: 1s
    then:
      if:
        condition:
          lambda: |-
              return id(clock_scroll_text).state.length() > 0;
        then:
          - display.page.show: scroll_clock

  - interval: 75ms
    then:
      if:
        condition:
          lambda: |-
            return (id(my_trinity_display).get_active_page() == id(scroll_clock)) && id(clock_scroll_text).state.length() > 0;
        then:
          - component.update: my_trinity_display
          - display.page.show: scroll_clock
          - lambda: |-
              if (id(scroll_x) < -(id(scroll_lenght))) {
                // Start at right side of clipping area
                id(scroll_x) = 64; 
              }
              else  {
                id(scroll_x) -= 2;
              }
        else:
          if:
            condition:
              lambda: |-
                return (id(my_trinity_display).get_active_page() != id(normal_clock));
            then:
              - display.page.show: normal_clock
              
switch:
  - platform: template
    id: night_mode
    name: Night Mode
    internal: true
    optimistic: true
    lambda: |-
      if (id(household_night_mode).state) {
        return true;
      } else {
        return false;
      }

button:
  - platform: restart
    name: Restart

number:
  - platform: template
    id: brightness
    name: Brightness
    min_value: 0
    max_value: 255
    update_interval: 1s
    step: 5
    set_action: 
      then:
        - lambda: |-
            id(my_trinity_display).set_brightness(x);
    lambda: |-
      return int(id(my_trinity_display).get_brightness());

globals:
   - id: scroll_x
     type: int
     restore_value: no
     initial_value: '0'
   - id: scroll_lenght
     type: int
     restore_value: no
     initial_value: '0'

Glad you solved your problem.

There are many lovely dashboards being made with lvgl (either via esphome or openhasp). Screens up to 7 inches are comparatively cheap. Some such as the guition 4 inch fit in a wall box and can control mains circuits - and even if you don’t want to screw with that can get their power from the mains.

Exciting stuff.

Any chance of a video of it in action?

Yeah you can see a janky movie in my fork.

Nice. I can’t quite see how the case looks, ie spouse acceptance factor :slight_smile:

I am fortunate enough not to have a spouse and therefore not subject to much irrational behavior in my life HAHAHA But it is made with 45 degree mitered walnut which has been sanded and stained in the shou sugi ban style, connected with glue. The panel is recessed with a 1/4" lip and rim. Took minutes to make since I have a double bevel miter saw.

1 Like