Esphome

5 minute read

ESP32

Image

TFT 1.8” display - ST7735

The ST7735 TFT display is a 1.8″ display with a resolution of 128×160 pixels and can display a wide range of colors ( full 18-bit color, 262,144 shades!). The display uses the SPI protocol for communication and has its own pixel-addressable frame buffer which means it can be used with all kinds of microcontroller and you only need 4 i/o pins. To complement the display, it also comes with an SD card slot on which colored bitmaps can be loaded and easily displayed on the screen.

Other features of the display include:

  • 1.8″ diagonal LCD TFT display
  • 128×160 resolution, 18-bit (262,144) color
  • 4 or 5 wire SPI digital interface
  • Built-in microSD slot – uses 2 more digital lines
  • 5V compatible! Use with 3.3V or 5V logic
  • Onboard 3.3V @ 150mA LDO regulator
  • 2 white LED backlight, a transistor connected so you can PWM dim the backlight
  • 1×10 header for easy breadboarding
  • 4 x 0.9″/2mm mounting holes in corners
  • Overall dimensions: 1.35″ x 2.2″ x 0.25″ (34mm x 56mm x 6.5mm)
  • Current draw is based on LED backlight usage: with full backlight draw is ~50mA

The goal of this tutorial is to demonstrate the abilities of the TFT to display images and text in different colors and some animation.

Pinout

  • LED - 3.3v
  • SCK - D13
  • SDA - D11
  • DC - D9
  • Reset - D8
  • CS - D10
  • GND - GND
  • VCC - 5v

https://www.youtube.com/watch?v=boagCpb6DgY

Sample code

esphome:
  name: wind
  # Set inital value for idle timer
  on_boot:
    priority: -100
    then:
      - lambda: |-
          id(idle_time) = id(my_time).now().timestamp;
    
esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:

spi:
  clk_pin: GPIO18
  mosi_pin: GPIO23
  miso_pin: GPIO19

ota:
  password: "8aebd6eb0427929de523046d6773d66e"

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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Wind Fallback Hotspot"
    password: "sh1mZkRvphaJ"

captive_portal:

# All sensors sourced from HA - Ecowitt weather station sensors
text_sensor:
  - platform: homeassistant
    name: "Current Temp"
    entity_id: sensor.weather_station_temperature
    id: temp

  - platform: homeassistant
    name: "Current Wind Dir"
    entity_id: sensor.wind_direction
    id: wind_dir

  - platform: homeassistant
    name: "Current Wind Speed"
    entity_id: sensor.wind_speed
    id: wind_speed

  - platform: homeassistant
    name: "Named Wind Dir"
    entity_id: sensor.named_wind_direction
    id: named_dir
    
  - platform: homeassistant
    name: "Rain since 12am"
    entity_id: sensor.rain_accumulation
    id: total_rain

  - platform: homeassistant
    name: "Pressure MSL hPa"
    entity_id: sensor.atmospheric_pressure_msl
    id: pressure
    
  - platform: homeassistant
    name: "Wind gust"
    entity_id: sensor.wind_gust
    id: gust

# Stores timestamp of last motion detect    
globals:
  - id: idle_time
    type: long

font:
  - file: "fonts/calibri.ttf"
    id: calibri_20
    size: 20

  - file: "fonts/calibri.ttf"
    id: calibri_25
    size: 25
    
  - file: "fonts/calibri.ttf"
    id: calibri_40
    size: 40
    
time:
  - platform: homeassistant
    id: my_time

    on_time:
      # Every 1 minute check if no presence trigger for 5 min
      - seconds: 0
        minutes: /1
        then:
          - if:
              # more than 5 min with no motion? Exercise display then turn off backlight
              condition:
                lambda: |-
                  return id(my_time).now().timestamp-id(idle_time) > 300;
              then:
                - display.page.show: page2
                - delay: 1s
                - display.page.show: page3
                - delay: 1s
                - display.page.show: page4
                - delay: 1s
                - display.page.show: page5
                - light.turn_off: back_light
                - delay: 1s
                - display.page.show: page1

# PIR on GPIO33
binary_sensor:
  - platform: gpio
    pin: GPIO33
    id: pir
    device_class: motion
    # turn on display on motion detect   
    # store timestamp of last detection event
    on_press:
      then:
        - lambda: |-
            id(idle_time) = id(my_time).now().timestamp;
        - display.page.show: page1
        - light.turn_on: back_light
    on_release:
      then:
        - lambda: |-
            id(idle_time) = id(my_time).now().timestamp;

# backlight pin as PWM GPIO32
output:
  - platform: ledc
    pin: 32
    id: gpio_32_backlight_pwm

# define as light
light:
  - platform: monochromatic
    output: gpio_32_backlight_pwm
    name: "ILI9341 Display Backlight"
    id: back_light
    restore_mode: ALWAYS_ON
    
# 2.2" TFT display 320x240, portrait orientation
display:
  - platform: ili9341
    rotation: 0
    model: TFT 2.4
    dc_pin: GPIO21
    cs_pin: GPIO22
    led_pin: GPIO32
    reset_pin: GPIO17
    
    # Page 1 - main display.  Other pages or for anti burn-in
    pages:
      - id: page1
        lambda: |-
          // Colours - used for visiual indication of wind strength
          auto red = Color(255, 0, 0);
          auto green = Color(0, 255, 0);
          auto light_blue = Color(135, 237, 232);
          auto orange = Color(255, 170, 43);
          auto white = Color(255, 255, 255);
          auto purple = Color(97, 15, 219);
          auto grey = Color(100, 135, 135);
          // history arrays for trend display
          static float hist_wind[30];
          static int hist_dir[30];
          static int index=0;
          static int prev_index=29;
          // Convert wind direction to int, speed to float
          int dir = atoi(id(wind_dir).state.c_str());
          float speed = atof(id(wind_speed).state.c_str());
          // Calculate xy position to plot wind indicator
          int x = 120 + (90 * (cos((dir-90)*PI/180)));
          int y = 150 + (90 * (sin((dir-90)*PI/180)));
          // Store in array
          if (hist_wind[prev_index]!=speed || hist_dir[prev_index]!=dir) {
            hist_wind[index] = speed;
            hist_dir[index] = dir;
            index += 1;
            prev_index += 1;
            if (index==30) { index = 0; }
            if (prev_index==30) { prev_index = 0; }
          }
          // Trend maximum for last 30
          float max = hist_wind[0];
          for (size_t i = 0; i < 30; ++i) {
            if (hist_wind[i] > max) {
              max = hist_wind[i];
            }
          }
          // Weather data
          it.strftime(5, 10, id(calibri_25), TextAlign::TOP_LEFT, "%H:%M", id(my_time).now());
          it.printf(235, 10, id(calibri_25), TextAlign::TOP_RIGHT, "%s °C", id(temp).state.c_str());
          it.printf(5, 290, id(calibri_20), TextAlign::BOTTOM_LEFT, "G %s km/h", id(gust).state.c_str());
          it.printf(5, 310, id(calibri_20), TextAlign::BOTTOM_LEFT, "TM %2.1f km/h", max);
          it.printf(235, 290, id(calibri_20), TextAlign::BOTTOM_RIGHT, "%s mm", id(total_rain).state.c_str());
          it.printf(235, 310, id(calibri_20), TextAlign::BOTTOM_RIGHT, "%s hPa", id(pressure).state.c_str());
          // Display trend data
          for (size_t i = 0; i < 30; ++i) {
            if (hist_wind[i]>0) {
              int a = 120 + ((90-(70*hist_wind[i]/max)) * (cos((hist_dir[i]-90)*PI/180)));
              int b = 150 + ((90-(70*hist_wind[i]/max)) * (sin((hist_dir[i]-90)*PI/180)));
              int c = 120 + (90 * (cos((hist_dir[i]-90)*PI/180)));
              int d = 150 + (90 * (sin((hist_dir[i]-90)*PI/180)));
              it.line(a, b, c, d, grey);
            }
          }
          // Compass rose
          it.circle(120, 150, 90);
          it.print(120, 50, id(calibri_20), red, TextAlign::BOTTOM_CENTER, "N");
          it.print(120, 250, id(calibri_20), red, TextAlign::TOP_CENTER, "S");
          it.print(20, 150, id(calibri_20), red, TextAlign::CENTER_RIGHT, "W");
          it.print(220, 150, id(calibri_20), red, TextAlign::CENTER_LEFT, "E");
          // Wind pointer
          if (speed<=11) { it.filled_circle(x, y, 7, light_blue); }
            else {
          if (speed>11 && speed<=30) { it.filled_circle(x, y, 7, green); } 
            else {
          if (speed>30 && speed<=60) { it.filled_circle(x, y, 7, orange); } 
            else {
          if (speed>60) { it.filled_circle(x, y, 7, red); } 
            } } }
          // Wind details
          it.printf(120, 130, id(calibri_25), TextAlign::BOTTOM_CENTER, "%s", id(named_dir).state.c_str());
          if (speed<10) { it.printf(120, 175, id(calibri_40), TextAlign::BOTTOM_CENTER, "%2.1f", speed); }
            else { it.printf(120, 175, id(calibri_40), TextAlign::BOTTOM_CENTER, "%2.0f", round(speed));   
          }
          it.print(120, 175, id(calibri_20), TextAlign::TOP_CENTER, "km/h");
          
      - id: page2
        lambda: |-
          // fill screen red
          auto red = Color(255, 0, 0);
          it.fill(red);
          
      - id: page3
        lambda: |-
          // Fill screen green
          auto green = Color(0, 255, 0);
          it.fill(green);
          
      - id: page4
        lambda: |-
          // Fill screen white
          auto white = Color(255, 255, 255);
          it.fill(white);
          
      - id: page5
        lambda: |-
          it.fill(COLOR_OFF);

Source: https://community.home-assistant.io/t/mini-weather-station-display-esphome-updated-for-3-2-touchscreen-with-bom-forecast/390606

Updated:

Comments