Home » Softronics » CYD ESPHome Homeassistant Timer module

CYD ESPHome Homeassistant Timer module

Code and stuffs to create a homeassistant timer for the widely available Cheap Yellow Display (CYD) board.

When a timers starts in homeassistant the same timer is displayed on the screen with a timer bar at the bottom. It displays only minutes and seconds as this is used for a 60 minutes timer.

esphome:
  name: timer
  friendly_name: timer

esp32:
  board: esp32dev
  framework:
    type: arduino

logger:

api:
  encryption:
    key: "the-secret-api-key"

ota:
  - platform: esphome
    password: "this-is-a-password"
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password 
  ap:
    ssid: "Timer Fallback Hotspot"
    password: "this-is-a-password"

captive_portal:

Paste after captive_portal:


spi:
  - id: tft_spi
    clk_pin: GPIO14
    mosi_pin: GPIO13
    miso_pin: GPIO12

globals:
  - id: target_timestamp
    type: double
    initial_value: '0.0'

text_sensor:
  - platform: homeassistant
    id: s_timer_main_state
    entity_id: timer.s_timer
    on_value:
      then:
        - component.update: my_display
  - platform: homeassistant
    id: s_timer_state
    entity_id: timer.s_timer
    attribute: remaining
    on_value:
      then:
        - component.update: my_display
  - platform: homeassistant
    id: s_timer_duration
    entity_id: timer.s_timer
    attribute: duration
    on_value:
      then:
        - component.update: my_display

  - platform: homeassistant
    id: s_timer_finishes
    entity_id: timer.s_timer
    attribute: finishes_at
    on_value:
      then:
        - lambda: |-
            int yr = 0, mo = 0, day = 0, hr = 0, min = 0, sec = 0;
            if (sscanf(x.c_str(), "%d-%d-%dT%d:%d:%d", &yr, &mo, &day, &hr, &min, &sec) >= 6) {
              // 1. Directly normalize month and year fields
              mo -= 1;
              yr -= 1900;

              // 2. Pure programmatic epoch calculation (No external library dependencies)
              long days = day - 1;
              for (int i = 0; i < mo; ++i) {
                const int days_per_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
                days += days_per_month[i];
              }
              // Adjust for leap years
              days += (yr - 70) / 4;
              if (mo > 1 && (yr % 4 == 0)) days += 1;

              long long total_epoch_seconds = (yr - 70) * 365LL + days;
              total_epoch_seconds = total_epoch_seconds * 86400LL + hr * 3600LL + min * 60LL + sec;

              id(target_timestamp) = (double)total_epoch_seconds;
            } else {
              id(target_timestamp) = 0.0;
            }
        - component.update: my_display

font:
  - file: "gfonts://Roboto"
    id: timer_font
    size: 48

output:
  - platform: ledc
    id: backlight_pwm
    pin: GPIO21 

light:
  - platform: monochromatic
    name: "Display Backlight"
    id: tft_backlight
    output: backlight_pwm
    restore_mode: ALWAYS_ON

display:
  - platform: ili9xxx
    id: my_display
    spi_id: tft_spi
    model: ILI9342      
    cs_pin: GPIO15
    dc_pin: GPIO2
    color_palette: 8BIT
    dimensions:
      width: 320        
      height: 240       
    rotation: 180       
    invert_colors: true 
    update_interval: 1s
    data_rate: 8000000  
    lambda: |-
      Color bg_color       = Color(20, 20, 20);
      Color text_active    = Color(0, 255, 100);
      Color text_paused    = Color(255, 140, 0);
      Color text_idle      = Color(120, 120, 120);
      Color bar_fill_color = Color(0, 150, 255);
      Color bar_bg_color   = Color(60, 60, 60);

      it.fill(bg_color);

      std::string state        = id(s_timer_main_state).state;
      std::string remaining    = id(s_timer_state).state;
      std::string duration_str = id(s_timer_duration).state;

      long total_seconds = 0;
      long remaining_seconds = 0;

      int dur_h = 0, dur_m = 0, dur_s = 0;
      if (sscanf(duration_str.c_str(), "%d:%d:%d", &dur_h, &dur_m, &dur_s) >= 2) {
        total_seconds = (dur_h * 3600) + (dur_m * 60) + dur_s;
      }

      if (state == "active") {
        double target_epoch = id(target_timestamp);
        double now_epoch = id(homeassistant_time).now().timestamp;
        
        if (target_epoch > 0) {
          remaining_seconds = (long)(target_epoch - now_epoch);
          if (remaining_seconds < 0) remaining_seconds = 0;

          int total_m = remaining_seconds / 60;
          int s = remaining_seconds % 60;

          it.printf(160, 100, id(timer_font), text_active, TextAlign::CENTER, "%02d:%02d", total_m, s);
        } else {
          it.print(160, 100, id(timer_font), text_active, TextAlign::CENTER, remaining.c_str());
        }
      }
      else if (state == "paused") {
        int rem_h = 0, rem_m = 0, rem_s = 0;
        if (sscanf(remaining.c_str(), "%d:%d:%d", &rem_h, &rem_m, &rem_s) >= 2) {
          remaining_seconds = (rem_h * 3600) + (rem_m * 60) + rem_s;
        }
        int total_paused_m = (rem_h * 60) + rem_m;
        it.printf(160, 100, id(timer_font), text_paused, TextAlign::CENTER, "%02d:%02d", total_paused_m, rem_s);
      }
      else {
        it.print(160, 120, id(timer_font), text_idle, TextAlign::CENTER, "Idle");
      }

      // Maintained: Right-to-left draining progress bar format
      if ((state == "active" || state == "paused") && total_seconds > 0 && remaining_seconds <= total_seconds) {
        int bar_x = 0;
        int bar_y = 204; 
        int bar_width = 320;
        int bar_height = 36;  

        float progress = (float)remaining_seconds / (float)total_seconds;
        if (progress > 1.0f) progress = 1.0f;
        if (progress < 0.0f) progress = 0.0f;

        int fill_width = (int)(bar_width * progress);

        it.rectangle(bar_x, bar_y, bar_width, bar_height, bar_bg_color);
        if (fill_width > 0) {
          it.filled_rectangle(bar_x, bar_y, fill_width, bar_height, bar_fill_color);
        }
      }

time:
  - platform: homeassistant
    id: homeassistant_time

To customize the timer bar position:

if ((state == "active" || state == "paused") && total_seconds > 0 && remaining_seconds <= total_seconds) {
        int bar_x = 40;
        int bar_y = 160;
        int bar_width = 240;
        int bar_height = 16;