ESPHome Nest thermostat clone on cheap rotary display

Did you hold boot (or bridge IO0 to GND) while connecting the cable to get into flashing mode? I just soldered onto the exposed pads to connect my usb. This way I could flash it with https://web.esphome.io/

I haven’t flashed anything to it yet. I want to find how to change the graphics first.

I did find an online tool for designing the graphics. At least I think it is the right LCD for this knob. I haven’t tried using it yet.

Also found this YouTube tutorial.

Yeah that’s their crap design tool. Really awful.

Wrong video I think. This one is what you wanted to point to isn’t it?

Buuuuut we are in the esphome section of the forum, so not quite right.

Any chance of your code my friend?

True, but if I can figure out how to flash new code and graphics, connecting to ESPHome shouldn’t be too difficult.

Same as flashing any other esphome code I guess. You have a serial connection, use it.

I’ve probably flashed a hundred ESP devices. My stumbling block is which graphics library to use for the display.

If you want ESPHome, then i dont think theres an alternative to display…

@nickrout - pasted the hardware that needs to be worked out, display screens are from Figma, nothing finished in ESPHome yet, but nothing too hard:


font:
  - file: 'fonts/Roboto-Regular.ttf' 
    glyphs: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ°.:/+- ' #ABCDEFGHIJKLMNOPQRSTUVWXYZ°
    id: regular
    size: 24
  - file: 'fonts/Roboto-Regular.ttf' 
    glyphs: ' -0123456789°Cecohi' #C
    id: huge
    size: 80
  - file: 'fonts/Roboto-Regular.ttf' 
    glyphs: ' -0123456789°Cecohi' #C
    id: medium
    size: 60
  - file: "fonts/fa-light.ttf"
    glyphs: "\uf06c\uf7ae\uf79a\uf06d\uf1eb\ue137\uf011\ue487\uf293\ue00d\uf015\ue1b0\uf624\ue3b0\ue1a7\uf2dc\ue004\uf775\uf2f1\uf863\uf750\uf017"


display: # 240 x 320
  - platform: st7789v
    id: ${device_name}_display
    model: Custom
    width: 240
    height: 320
    offset_height: 0
    offset_width: 0
###
    eightbitcolor: true # OR IT BREAKS
###    backlight_pin: GPIO35 ## breaks color
    update_interval: 20s
    cs_pin: GPIO34
    dc_pin: GPIO13
    reset_pin: GPIO21
    on_page_change: 
      then: 
        - light.turn_on: ${device_name}_backlight

    pages:
    - id: page_boot
      lambda: |-
        int w = it.get_width();
        int h = it.get_height();
        auto icon = "\ue1a7"; // hand-wave //
        auto color = id(gray);
        it.print(w/2, h/2 , id(huge), random_color, TextAlign::CENTER, "hi");
        it.print(w/2, w, id(symbol), random_color, TextAlign::CENTER, icon);
        it.print(w/2, h - 20, id(regular), random_color, TextAlign::CENTER, "nESP");        

    - id: page_qr_wifi
      lambda: |-
        int w = it.get_width();
        int h = it.get_height();
        auto icon = "\uf1eb"; // \uf293 = bluetooth // \uf1eb = wifi
        auto color = id(gray);
        it.qr_code(w/3.5, h/4, id(qr_hotspot), random_color, 4);
        it.print(w/2, w, id(symbol), random_color, TextAlign::CENTER, icon);
        it.printf(w/2, h - 20, id(regular), random_color, TextAlign::CENTER, "nESP" );

    - id: page_climate
      lambda: |-
        // canvas
        int w = it.get_width();
        int h = it.get_height();
        int h2 = h / 2;
        int w2 = w / 2;
        int yheader = h / 3;
        int yfooter = h - 20;
        int radius = w2;
  
        // font sizes
        auto main_font = id(huge);
  
        // text values
        std::string huge_text = "x";
        auto get_mode = id(hvac_mode).state;
        auto get_hvac_action = id(hvac_action).state;
        auto get_preset_mode = id(preset_mode).state;
  
        // bools
        auto dual = false;
  
        // numeric
        float get_target_temp_low = id(target_temp_low).state;
        float get_target_temp_high = id(target_temp_high).state;
        float get_target_temperature = id(target_temperature).state; 

        // check for dual mode
        if (isnan(get_target_temperature) ){
          dual = true;
          get_target_temperature = round((get_target_temp_low + get_target_temp_high) / 2);
        }
  
        float get_current_temperature = id(current_temperature).state;
        float minTemp = 15.0;
        float maxTemp = 30.0;
  
        // COLORS
        auto color_neutral = id(graydark);
        auto color_main = id(graydark);
        auto color_accent = color_main;
        auto color_background = id(black);
        auto linecolor = id(graydarker);
  
        // ICON
        auto icon = "\uf624";
  
        // hvac_mode (state)
        if ( get_mode == "heat" ){
          color_main = id(mode_heat);
          icon = "\uf06d"; // fire
        }
        else if (get_mode == "cool") { 
          color_main = id(mode_cool);
          icon = "\uf2dc"; // snowflake
        }
        else if (get_mode == "dry") { 
          color_main = id(mode_dry);
          icon = "\uf750"; // droplet-percent
        }
        else if (get_mode == "fan_only") { 
          color_main = id(mode_fan);
          icon = "\uf863"; // fan
        }
        else if (get_mode == "heat_cool") { 
          color_main = id(mode_heat_cool);
          icon = "\uf2f1"; // rotate          
        }        

        // heat_cool hvac_action options: idle, cooling, heating
        if (get_hvac_action == "idle") { 
          if (get_mode == "heat_cool"){
            color_main = id(mode_heat_cool);
          }          
          else if (get_mode == "heat"){
            color_main = id(mode_heat);
          }          
          else if (get_mode == "cool"){
            color_main = id(mode_cool);
          }          
          else{
            color_main = id(mode_idle);
            icon = "\uf017"; // clock            
          }
          icon = "\uf017"; // clock            
          color_accent = id(mode_idle);
        }

        if ( (get_mode == "off") || (get_hvac_action == "off") ){ 
          color_main = id(graydarker);
          color_accent = id(graydark);
          linecolor = id(graydarker);
          icon = "\uf011"; // power-off
        }
  
        // Draw filled circle if not off or idle
        if ( (get_hvac_action == "heating") || (get_hvac_action == "cooling") || (get_hvac_action == "drying") || (get_hvac_action == "fan" ) ) { 
          // stripeLength = stripeLength + w / 40;
          radius = radius - w / 40;
          it.filled_circle(w2, h2, w2, color_main);
          linecolor = id(white);
          color_accent = color_main;
          it.print(w2, yheader, id(regular), color_background, TextAlign::CENTER, get_hvac_action.c_str() );
        }        

        if (get_preset_mode == "eco") {
          huge_text = "eco";
          color_main = id(mode_eco);
          } else  {
            char buffer[32];
            float value = round(get_target_temperature);
            if (value != 0 && !isnan(value) ){
              snprintf(buffer, sizeof(buffer), "%.0f", value); // Convert float to a C-style string
              huge_text = buffer;
            }                
          }
  
          if ( dual ){
            main_font = id(medium);
            float value_low = round(get_target_temp_low);
            float value_high = round(get_target_temp_high);
            std::string separator = " - ";
            huge_text = std::to_string((int)value_low) + separator + std::to_string((int)value_high);
          }  
          else {
            float value = round(get_target_temperature);
            if (value != 0 && !isnan(value) ){
              huge_text = std::to_string((int)value);
            }                
          }                    
  
  
          // Draw current temperature at bottom
          it.printf(w2, yfooter, id(regular), color_neutral, TextAlign::CENTER, "%.0f", get_current_temperature );
  
          // dual gauge mode:
          // it.printf(w2, yfooter, id(regular), color_main, TextAlign::CENTER, "%.0f - %.0f", get_target_temp_low, get_target_temp_high);
  
          // invert colors for filled background
          if ( (get_hvac_action == "heating") || (get_hvac_action == "cooling") || (get_hvac_action == "drying") || (get_hvac_action == "fan" ) ) { 
            auto hold = color_accent;
            color_accent = color_background;
            color_main = color_background;
            color_background = hold;
          }
  
          // Draw icon 
          it.print(w2, w, id(symbol), color_main, TextAlign::CENTER, icon);
  
          // draw set at center
          it.print(w2, h2 , main_font, color_accent, TextAlign::CENTER, huge_text.c_str() );
  

Thanks for the homework assignment.

Happy to help, together we might come to a final solution faster - i can finalize the display if someone helps figure out how to make the gpio sensors work reliably… I dont think you can use any other graphics “library” on st7789v… together with esphome.

Thank you!

It seems to me that openhasp would be ideal for this. Although it doesn’t have a touch screen, the interface of a rotary plus a push button could work with openhasp.

Did anybody have a look at this already?

https://aliexpress.com/item/1005004882568128.html

Information is a little bit sparse but it seems to be a commercial version of Smartknob

I just recently purchased one of these and am looking to do the same thing and would love to help in the development of this.

1 Like

Have a go, my enthusiasm tanked when the basic left-right-push sensors were out of whack.

2 Likes

It’s a dead link.

Works for me

Because it has been changed… :wink:

Hey, did you get any more information? I might buy that one and give it ago, made the mistake of only ordering the diag cable on another buy…