Installing ESPhome on GEEKMAGIC Smart Weather Clock (smalltv/pro)

Implementation is really tied to your HA entities, but In case it could serve your inspiration, here’s what I end up with

Note the native display page feature of ESP Home is not working on this display type. had to rebuild it manually

esphome:
  name: smalltv-esp32
  friendly_name: smalltv_esp32

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

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

ota:
  password: !secret ota_paswd
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Smalltv-Esp32 Fallback Hotspot"
    password: "NOtnhLeYNfPB"

captive_portal:
    

external_components:
  - source:
      type: git
      url: https://github.com/rletendu/esphome.git
      ref: st7789_no_frame_buffer
    refresh: 0s
    components: [st7789v]



#captive_portal:
debug:
  update_interval: 5s

sensor:
  - platform: homeassistant
    entity_id: sensor.abri_bois_am2301_temperature
    id: abri_bois

  - platform: homeassistant
    entity_id: sensor.atc_mez41e_641e_temperature
    id: mezzanine

  - platform: homeassistant
    entity_id: sensor.piscine_temperature
    id: piscine

  - platform: homeassistant
    entity_id: sensor.atc_sal505_6505_temperature
    id: salon

  - platform: homeassistant
    entity_id: sensor.atc_julb28_3b28_temperature
    id: juliette

  - platform: homeassistant
    entity_id: sensor.atc_vic77a_d77a_temperature
    id: victorien

  - platform: homeassistant
    entity_id: sensor.atc_cle63e_a63e_temperature
    id: clemence

  - platform: homeassistant
    entity_id: sensor.atc_sdb80a_880a_temperature
    id: sdb_etage

  - platform: homeassistant
    entity_id: sensor.atc_cui76c_576c_temperature
    id: cuisine

  - platform: homeassistant
    entity_id: sensor.atc_cab2c2_temperature
    id: cabane_ext

  - platform: homeassistant
    entity_id: sensor.garage_temp
    id: garage

  - platform: homeassistant
    entity_id: sensor.nodered_f80113f7800c75ed
    id: girouette



  - platform: homeassistant
    entity_id: weather.varades
    attribute: pressure
    id: pressure_weather

  - platform: homeassistant
    entity_id: sensor.girouette_pression
    id: pressure

  

  - platform: homeassistant
    name: "T_max_day0"
    entity_id: sensor.T_max_day0
    id: T_max_day0
  - platform: homeassistant
    name: "T_max_day1"
    entity_id: sensor.T_max_day1
    id: T_max_day1
  - platform: homeassistant
    name: "T_max_day2"
    entity_id: sensor.T_max_day2
    id: T_max_day2
  - platform: homeassistant
    name: "T_max_day3"
    entity_id: sensor.T_max_day3
    id: T_max_day3
  - platform: homeassistant
    name: "T_max_day4"
    entity_id: sensor.T_max_day4
    id: T_max_day4

  - platform: homeassistant
    name: "T_min_day0"
    entity_id: sensor.T_min_day0
    id: T_min_day0
  - platform: homeassistant
    name: "T_min_day1"
    entity_id: sensor.T_min_day1
    id: T_min_day1
  - platform: homeassistant
    name: "T_min_day2"
    entity_id: sensor.T_min_day2
    id: T_min_day2
  - platform: homeassistant
    name: "T_min_day3"
    entity_id: sensor.T_min_day3
    id: T_min_day3    
  - platform: homeassistant
    name: "T_min_day4"
    entity_id: sensor.T_min_day4
    id: T_min_day4  

  - platform: homeassistant
    name: "Precipitation_day0"
    entity_id: sensor.Precipitation_day0
    id: Precipitation_day0
  - platform: homeassistant
    name: "Precipitation_day1"
    entity_id: sensor.Precipitation_day1
    id: Precipitation_day1
  - platform: homeassistant
    name: "Precipitation_day2"
    entity_id: sensor.Precipitation_day2
    id: Precipitation_day2
  - platform: homeassistant
    name: "Precipitation_day3"
    entity_id: sensor.Precipitation_day3
    id: Precipitation_day3    
  - platform: homeassistant
    name: "Precipitation_day4"
    entity_id: sensor.Precipitation_day4
    id: Precipitation_day4   

  - platform: homeassistant
    name: "index_day0"
    entity_id: sensor.index_day0
    id: index_day0

    

text_sensor:
  - platform: homeassistant
    name: "Condition_day0"
    entity_id: sensor.Condition_day0
    id: Condition_day0
  - platform: homeassistant
    name: "Condition_day1"
    entity_id: sensor.Condition_day1
    id: Condition_day1
  - platform: homeassistant
    name: "Condition_day2"
    entity_id: sensor.Condition_day2
    id: Condition_day2
  - platform: homeassistant
    name: "Condition_day3"
    entity_id: sensor.Condition_day3
    id: Condition_day3        
  - platform: homeassistant
    name: "Condition_day4"
    entity_id: sensor.Condition_day4
    id: Condition_day4   


# Define a PWM output on the ESP32
output:
  - platform: ledc
    pin: GPIO25
    inverted: True
    id: backlight_pwm
  - platform: ledc
    pin: GPIO12
    id: rtttl_out

rtttl:
  output: rtttl_out

# Define a monochromatic, dimmable light for the backlight
light:
  - platform: monochromatic
    output: backlight_pwm
    name: "Display Backlight"
    id: back_light
    restore_mode: ALWAYS_ON


esp32_touch:
 # setup_mode: true


globals:
- id: page
  type: int
  initial_value: "1"

interval:
  - interval: 20s
    then:
      - lambda: |-
          /*
          id(page) = (id(page) + 1);
          if (id(page) > 3) {
            id(page) = 1;
          } */


binary_sensor:

  - platform: homeassistant
    entity_id: binary_sensor.status_porte_garage
    id: porte_garage

  - platform: homeassistant
    entity_id: binary_sensor.portail_lock_sense
    id: portail  
  - platform: esp32_touch
    name: "ESP32 Touch Pad GPIO32"
    pin: GPIO32
    threshold: 1250
    id: gp32
    on_click:
    - min_length: 50ms
      max_length: 350ms
      then:
        - if:
            condition:
            - light.is_off: back_light
            then:
            - light.turn_on: 
                id: back_light
            else:
              - lambda: |-
                    id(page) = (id(page) + 1);
                    if (id(page) > 3) {
                      id(page) = 1;
                    }
              - component.update : disp

    - min_length: 1000ms
      max_length: 2000ms
      then:
        - light.turn_off: back_light
        - rtttl.play: 'two_short:d=4,o=5,b=100:16e6,16e6'

  - platform: esp32_touch
    name: "ESP32 Touch Pad GPIO33"
    pin: GPIO33
    threshold: 1375
    id: gp33
    on_click:
    - min_length: 50ms
      max_length: 350ms
      then:

        - if:
            condition:
            - light.is_off: back_light
            then:
            - light.turn_on: 
                id: back_light
            else:
              - lambda: |-
                    id(page) = (id(page) - 1);
                    if (id(page) < 1) {
                      id(page) = 3;
                    } 
              - component.update : disp
    - min_length: 1000ms
      max_length: 2000ms
      then:
        - light.turn_off: back_light


number:
  - platform: template
    name: "disp page"
    optimistic: true
    min_value: 0
    max_value: 4
    step: 1
    on_value:
      then:
        - lambda: |-
            id(page) = x;
        - component.update : disp



font:
  - file: 'OpenSans-Regular.ttf'
    id: font20
    size: 20

  - file: 'OpenSans-Regular.ttf'
    id: font60
    size: 50

  - file: 'fonts/materialdesignicons-webfont.ttf'
    id: fonticon
    size: 50
    glyphs: &mdi-weather-glyphs
      - "\U000F0590" # mdi-weather-cloudy
      - "\U000F0F2F" # mdi-weather-cloudy-alert
      - "\U000F0E6E" # mdi-weather-cloudy-arrow-right
      - "\U000F0591" # mdi-weather-fog
      - "\U000F0592" # mdi-weather-hail
      - "\U000F0F30" # mdi-weather-hazy
      - "\U000F0898" # mdi-weather-hurricane
      - "\U000F0593" # mdi-weather-lightning
      - "\U000F067E" # mdi-weather-lightning-rainy
      - "\U000F0594" # mdi-weather-night
      - "\U000F0F31" # mdi-weather-night-partly-cloudy
      - "\U000F0595" # mdi-weather-partly-cloudy
      - "\U000F0F32" # mdi-weather-partly-lightning
      - "\U000F0F33" # mdi-weather-partly-rainy
      - "\U000F0F34" # mdi-weather-partly-snowy
      - "\U000F0F35" # mdi-weather-partly-snowy-rainy
      - "\U000F0596" # mdi-weather-pouring
      - "\U000F0597" # mdi-weather-rainy
      - "\U000F0598" # mdi-weather-snowy
      - "\U000F0F36" # mdi-weather-snowy-heavy
      - "\U000F067F" # mdi-weather-snowy-rainy
      - "\U000F0599" # mdi-weather-sunny
      - "\U000F0F37" # mdi-weather-sunny-alert
      - "\U000F14E4" # mdi-weather-sunny-off
      - "\U000F059A" # mdi-weather-sunset
      - "\U000F059B" # mdi-weather-sunset-down
      - "\U000F059C" # mdi-weather-sunset-up
      - "\U000F0F38" # mdi-weather-tornado
      - "\U000F059D" # mdi-weather-windy
      - "\U000F059E" # mdi-weather-windy-variant
      - "\U000F058C" # mdi-water
      - "\U000F17FB" # mdi-GarageLock
      - "\U000F06DA" # mdi-GarageOpen      
      - "\U000F116A" # mid-GateOpen
      - "\U000F0299" # mdi-GateClose
      - "\U000F06EE" # mdi-mailbox
      - "\U000F0A79" # mdi-trash-can
      - "\U000F001B" # mdi-Air-conditioner
      - "\U000F0D91" # mdi-MotionSensor-ON
      - "\U000F1435" # mdi-MotionSensor-Off

  - file: 'fonts/materialdesignicons-webfont.ttf'
    id: fonticon20
    size: 20
    glyphs: &mdi-weather-glyphs20
      - "\U000F058C" # mdi-water
      - "\U000F005C" # mdi-ArrowTopRight
      - "\U000F0043" # mdi-ArrowBottomRight"
      - "\U000F092B" # no-wifi
      - "\U000F092F" # low-wifi
      - "\U000F091F" # wifi-1
      - "\U000F0922" # wifi-2
      - "\U000F0925" # wifi-3
      - "\U000F0928" # wifi-4

color:
    - id: color_green
      red: 0%
      green: 100%
      blue: 0%
    - id: color_red
      red: 100%
      green: 0%
      blue: 0%
    - id: color_blue
      red: 0%
      green: 0%
      blue: 100%
    - id: color_orange
      red: 100%
      green: 64%
      blue: 0%

spi:
  clk_pin: GPIO18
  mosi_pin: GPIO23
  interface: hardware
  id: spihwd

graph:

  - id: pressure_graph
    sensor: pressure
    duration: 24h
    width: 180
    height: 100
    color: color_orange
  - id: multi_temperature_graph
    duration: 24h
    width: 180
    height: 100
    traces:
      - sensor: abri_bois
        line_type: DOTTED
        color: color_blue
      - sensor: salon
        line_type: SOLID
        color: color_green

  

display:
  - platform: st7789v
    model: "Custom"
    spi_id: spihwd
    height: 240
    width: 240
    offset_height: 0
    offset_width: 0
    dc_pin: GPIO02
    reset_pin: GPIO04
    #backlight_pin: GPIO25
    eightbitcolor: True
    #update_interval: never
    update_interval: 5s
    id: disp


    lambda: |-
          int y;
          int x;

          //--------------------------------------------------------------------------
          // Map weather states to MDI characters.
          //--------------------------------------------------------------------------
          std::map<std::string, std::string> weather_icon_map
            {
              {"cloudy", "\U000F0590"},
              {"cloudy-alert", "\U000F0F2F"},
              {"cloudy-arrow-right", "\U000F0E6E"},
              {"fog", "\U000F0591"},
              {"hail", "\U000F0592"},
              {"hazy", "\U000F0F30"},
              {"hurricane", "\U000F0898"},
              {"lightning", "\U000F0593"},
              {"lightning-rainy", "\U000F067E"},
              {"night", "\U000F0594"},
              {"clear-night", "\U000F0594"},
              {"night-partly-cloudy", "\U000F0F31"},
              {"partlycloudy", "\U000F0595"},
              {"partly-lightning", "\U000F0F32"},
              {"partly-rainy", "\U000F0F33"},
              {"partly-snowy", "\U000F0F34"},
              {"partly-snowy-rainy", "\U000F0F35"},
              {"pouring", "\U000F0596"},
              {"rainy", "\U000F0597"},
              {"snowy", "\U000F0598"},
              {"snowy-heavy", "\U000F0F36"},
              {"snowy-rainy", "\U000F067F"},
              {"sunny", "\U000F0599"},
              {"sunny-alert", "\U000F0F37"},
              {"sunny-off", "\U000F14E4"},
              {"sunset", "\U000F059A"},
              {"sunset-down", "\U000F059B"},
              {"sunset-up", "\U000F059C"},
              {"tornado", "\U000F0F38"},
              {"windy", "\U000F059D"},
              {"windy-variant", "\U000F059E"},
            };

          std::string weekdays[7]={"Lun","Mar","Mer","Jeu","Ven","Sam","Dim"};


          switch (id(page)){

            default:
            case 0:
            case 1:
              it.printf(0, 0, id(font60),  "%.1f°",id(abri_bois).state);
              if (id(Precipitation_day0).state) {
                it.printf(130, 5, id(fonticon20), id(color_blue),"\U000F058C");
                it.printf(145, 0, id(font20), id(color_blue),"%.0f",id(Precipitation_day0).state);
              }
              it.printf(130, 20, id(font20), id(color_red), "%.0f°",id(T_max_day0).state);
              it.printf(130, 40, id(font20), id(color_green), "%.0f°",id(T_min_day0).state);
              it.printf(190, 20, id(fonticon), "%s", weather_icon_map[id(Condition_day0).state.c_str()].c_str());

              it.printf(0, 60, id(font20), id(color_orange), "%.0fhPa",id(pressure).state);

              // Days name for forecast
              y = 100;
              x = 5; 
              it.printf(x, y, id(font20), "%s", weekdays[(int)(id(index_day0).state+1)%7].c_str());
              x += 60;
              it.printf(x, y, id(font20), "%s", weekdays[(int)(id(index_day0).state+2)%7].c_str());
              x += 60;
              it.printf(x, y, id(font20), "%s", weekdays[(int)(id(index_day0).state+3)%7].c_str());
              x += 60;
              it.printf(x, y, id(font20), "%s", weekdays[(int)(id(index_day0).state+4)%7].c_str());

              //Forecast 

              // Day +1
              y = 122;
              x = 5;
              it.printf(x, y, id(fonticon), "%s", weather_icon_map[id(Condition_day1).state.c_str()].c_str());
              it.printf(x, y+40, id(font20), id(color_red), "%.0f°",id(T_max_day1).state);
              it.printf(x, y+60, id(font20), id(color_green), "%.0f°",id(T_min_day1).state);
              if (id(Precipitation_day1).state) {
                it.printf(x, y+85, id(fonticon20), id(color_blue),"\U000F058C");
                it.printf(x+15, y+80, id(font20), id(color_blue),"%.0f",id(Precipitation_day1).state);
              }

              // Day + 2
              x += 60;
              it.printf(x, y, id(fonticon), "%s", weather_icon_map[id(Condition_day2).state.c_str()].c_str());
              it.printf(x, y+40, id(font20), id(color_red), "%.0f°",id(T_max_day2).state);
              it.printf(x, y+60, id(font20), id(color_green), "%.0f°",id(T_min_day2).state);
              if (id(Precipitation_day2).state) {
                it.printf(x, y+85, id(fonticon20), id(color_blue),"\U000F058C");
                it.printf(x+15, y+80, id(font20), id(color_blue),"%.0f",id(Precipitation_day2).state);
              }

              // Day + 3
              x += 60;
              it.printf(x, y, id(fonticon), "%s", weather_icon_map[id(Condition_day3).state.c_str()].c_str());
              it.printf(x, y+40, id(font20), id(color_red), "%.0f°",id(T_max_day3).state);
              it.printf(x, y+60, id(font20), id(color_green), "%.0f°",id(T_min_day3).state);
              if (id(Precipitation_day3).state) {
                it.printf(x, y+85, id(fonticon20), id(color_blue),"\U000F058C");
                it.printf(x+15, y+80, id(font20), id(color_blue),"%.0f",id(Precipitation_day3).state);
              }

              // Day + 4
              x += 60;
              it.printf(x, y, id(fonticon), "%s", weather_icon_map[id(Condition_day4).state.c_str()].c_str());
              it.printf(x, y+40, id(font20), id(color_red), "%.0f°",id(T_max_day4).state);
              it.printf(x, y+60, id(font20), id(color_green), "%.0f°",id(T_min_day4).state);
              if (id(Precipitation_day4).state) {
                it.printf(x, y+85, id(fonticon20), id(color_blue),"\U000F058C");
                it.printf(x+15, y+80, id(font20), id(color_blue),"%.0f",id(Precipitation_day4).state);
              }
              break;

            case 2:
              x = 5;
              y = 0;

              it.printf(x, y, id(font20),  "Abri");
              it.printf(x, y+20, id(font20), "%.1f°",id(abri_bois).state);
              x += 60;
              it.printf(x, y, id(font20),  "CabE.");
              it.printf(x, y+20, id(font20), "%.1f°",id(cabane_ext).state);
              x += 60;
              it.printf(x, y, id(font20),  "Gir.");
              it.printf(x, y+20, id(font20), "%.1f°",id(girouette).state);
              x += 60;
              it.printf(x, y, id(font20),  "Pisc.");
              it.printf(x, y+20, id(font20), "%.1f°",id(piscine).state);


              x = 5;
              y = 50;
              it.printf(x, y, id(font20),  "Juju");
              it.printf(x, y+20, id(font20), "%.1f°",id(juliette).state);
              x += 60;
              it.printf(x, y, id(font20),  "Clem");
              it.printf(x, y+20, id(font20), "%.1f°",id(clemence).state);
              x += 60;
              it.printf(x, y, id(font20),  "Vict");
              it.printf(x, y+20, id(font20), "%.1f°",id(victorien).state);
              x += 60;
              it.printf(x, y, id(font20),  "Sdb Et.");
              it.printf(x, y+20, id(font20), "%.1f°",id(sdb_etage).state);


              x = 5;
              y = 100;
              it.printf(x, y, id(font20),  "Mezz");
              it.printf(x, y+20, id(font20), "%.1f°",id(mezzanine).state);
              x += 60;
              it.printf(x, y, id(font20),  "Salon");
              it.printf(x, y+20, id(font20), "%.1f°",id(salon).state);
              x += 60;
              it.printf(x, y, id(font20),  "Cuis.");
              it.printf(x, y+20, id(font20), "%.1f°",id(cuisine).state);
              x += 60;
              it.printf(x, y, id(font20),  "Garge");
              it.printf(x, y+20, id(font20), "%.1f°",id(garage).state);

              x = 5;
              y = 150;
              it.printf(x, y, id(font20),  "Garge");
              if (id(porte_garage).state) {
                it.printf(x, y+20, id(fonticon),id(color_green), "\U000F06DA"); // Open
              } else {
                it.printf(x, y+20, id(fonticon),id(color_red), "\U000F17FB"); // Lock
              }
              x += 60;
              it.printf(x, y, id(font20),  "Portail");
              if (id(portail).state) {
                it.printf(x, y+20, id(fonticon),id(color_green), "\U000F116A"); // Gate Open
              } else {
                it.printf(x, y+20, id(fonticon),id(color_red), "\U000F0299"); // Gate Lock
              }


              break;

            case 3:
              
              it.graph(0, 0, id(pressure_graph));
              it.printf(190, 0, id(font20), id(color_orange), "%.0f",id(pressure).state);
              it.printf(190, 20, id(font20), id(color_orange), "hPa");


              it.graph(0, 120, id(multi_temperature_graph));
              it.printf(190, 120, id(font20), id(color_blue),  "Ext");
              it.printf(190, 140, id(font20), id(color_blue),  "%.1f°",id(abri_bois).state);

              it.printf(190, 160, id(font20), id(color_green),  "Int");
              it.printf(190, 180, id(font20), id(color_green),  "%.1f°",id(salon).state);
              break;


          }



12 Likes