My ESPHOME Adventure

Back in 2021 I wrote about experimenting with the LILYGO TTGO ESP32 development board and my first attempts at using ESPHOME as against my firmware of choice Tasmota. Well, I then went heads-down for several days of learning – with several false starts I may add. The short version of this is, I’ve now made some significant progress in using ESPHOME on both ESP8266 and ESP32 (including finding the command to double the ESP8266 operating frequency from 80Mhz to 160Mhz – something often overlooked in the battle between the two chips). The point of writing this blog is to help save others some of my dead-ends and to serve as a reminder/prompt for me in the future.

See February 2023 update near the end, for using ESPHOME with non-ESP boards and my latest catch-up with ESPHOME.

Skip Background if you want the “shorter” (it’s not short) version – and head straight to The Meat but you may miss some useful context.

Background

Seasoned ESPHOME users may dismiss this, but this has been a challenge for me despite a history including decades of C programming and more recently delving into Javascript (NodeJS) thanks to Node-Red. Basically I started using machine code in the 1970s, moving on quickly through assembly language to BASIC, writing my own FORTH interpreter in a powerful BASIC version (Cromemco 23K Structured BASIC) sometime in 1980 or thereabouts. Around then I was convinced by an older friend that I was “barking up the wrong tree” and so came my introduction to C.

When I put together ESP-GO to program up ESP8266 chips for home control, I avoided C++ like the plague and stuck with plain old C (and the ESPRESSIF SDK) – not one of my better plans though I did manage to modify some Adafruit and other libraries to add LCD/OLED display, ws2812b RGB LEDs and various sensor controls to the ESP8266.

Mix in some simple Node-Red NodeJS coding on Raspberry Pi (RPi2 initially) and I was starting to see results and a following on the blog. The route I took combined with the ESP8266 meant I was constantly up against RAM limits to that route was never going to last – it still works and I have a pair of ESP8266+ILI941 wall thermostats back in the UK which stand as a testament to ESP-GO – complete with local buttons and of course WiFi/MQTT control. All blogged in here.

And then came Tasmota which enjoys a lot of support to this day and by 2021 its display handling exceeds that of ESP-GO – yet for me still leaves something to be desired in that area. In the process of using the Raspberry Pi as my home control centre – along with Node-Red, I’ve been introduced to JSON though I would not say I was an expert on the subject or anything like it.

So, last week I went off on a tangent, grabbed both ESP8266 and ESP32 boards and went off to the web to see what ESPHOME is all about. The ESPHOME software system uses YAML (a “digestible data serialization language” that is often utilized to create configuration files, works in concurrence with any programming language and is apparently a superset of JSON – it is also utterly alien to me). ESPHOME is usually associated with “Home Assistant” and I am not a fan of the latter, which makes learning about ESPHOME that much more difficult as HA appears frequently throughout ESPHOME documentation. My thanks to @ssieb, @WeekendWarrier and others in the ESPHOME Discord forum for their help.

The Meat

So, to the meat – with unprecidented help from a couple of individuals in the ESPHOME forum, I started from zero last week and worked my way up to a usefully working ESP32 (TTGO) board including built-in IPS ST7789V 1.14 Inch LCD display, some ws2812b RGB lighting, a simple LED, a ~BME280 multi-purpose sensor and use of the on-board TTGO buttons (shown on the display using simple filled, colour-changing circles) – all using ESPHOME software. That reversed out text is black on yellow – achieved with simple black text on top of a yellow filled rectangle.

TTGO board with ESPHOME - a work in progress

Above you see the hardware and below, the ESPHOME software in it’s current form. At the top of the panel, the internal chip temperature and WiFi connection state, then the output from the BME280 board (bottom left – I2C and 3v3), time, date and WiFi status. This will develop into something actually useful but I’m still learning – and it IS PRETTY. Shame the ws2812b LEDs don’t really show the constantly-cycling, individual-colour-per-LED rainbow effect in the photo. Trust me.

The code below (My First ESP32 ESPHOME Project) to go with the photo above, looks involved but I should point out here that it is built up largely from modified examples (I’ve left seemingly pointless comments in as reminders for my own future) pieced together and modified as I learn. My WiFi password is hidden to protect the innocent.

So, ESPHOME software appears to be built up in blocks (starting points for pretty much all of this can be seen spread around the ESPHOME website) – after installing the ESPHOME package on my PC – trivial – it’s all done for you and a plug-in called PILLOW for Python (again all automated setup), I started the ball rolling. ESPHOME works (in Windows) on the command line and using NotePad++ to edit the code the two of which that nearly put me off right at the start, until one of the people giving me advice said “No, what you need is this…. the new, free Windows Terminal – which pulls in Powershell and is EASY and pleasant to use, also the free Microsoft Visual Code for editing.”

With that starting point I made a start on ESPHOME.

Setup

I took a handy NODEMCU-type ESP8266 board and plugged it into a spare USB port on the PC using a standard USB lead.

At the prompt in Windows terminal – not paying any attention to folders though in fact I was sitting in the new”C:\Users\User\esphome” folder, I simply started by typing:

esphome wizard myesp8266.yaml

— which is how you start off a brand new ESPHOME project. Bear in mind that NodeMCU-type boards do not need you to mess about with reset and programming (GPIO0) buttons – others may. In short – a new project we’ll call “fred” starts with an automatically-created fred.yaml file in the automatically created esphome folder… and ends up with a “fred” folder underneath – along with folders for whatever projects you create.

This is how it all starts in my case:

Hi there!
I'm the wizard of ESPHome :)
And I'm here to help you get started with ESPHome.
In 4 steps I'm going to guide you through creating a basic configuration file for your custom ESP8266/ESP32 firmware. Yay!



============= STEP 1 =============
    _____ ____  _____  ______
   / ____/ __ \|  __ \|  ____|
  | |   | |  | | |__) | |__
  | |   | |  | |  _  /|  __|
  | |___| |__| | | \ \| |____
   \_____\____/|_|  \_\______|

===================================
First up, please choose a name for your node.
It should be a unique name that can be used to identify the device later.
For example, I like calling the node in my living room livingroom.

(name):myesp8266

At this point I gave the project a name “myesp8266” – hardly imaginative but there you go. I was then asked if I was using an ESP32 or ESP8266 – I responded with the latter. I was then asked what TYPE of ESP8266 board – I responded with nodemcuv2 (a guess).

The advice at the end, above, is invaluable – as https://esphome.io is FULL of useful reference material and examples. At this point the PC came back to the prompt. No chip flashing had been done – so I left the board plugged into USB and the PC and took a look at the new YAML file in VSC (Visual Studio Code editor). Don’t ask me why it was called “myesphome” when I’d used “myesp8266” – but I just checked the file name – sure enough.

Out-of-the-box YAML code

Anyway, here (minus my password which I’ve left here as xxxxxxx) is the content of that file…

esphome:
  name: myesp8266
  platform: ESP8266
  board: nodemcuv2

# Enable logging
logger:

# Enable Home Assistant API
api:
  password: ""

ota:
  password: ""

wifi:
  ssid: "xxxxxxx"
  password: "yyyyyyy"

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

captive_portal:

I’d already figured out at this point that I would NOT be needing the Home assistant API and WOULD be needing MQTT but that will follow:

After that initial setup, a slightly changed command-line instruction (wizard replaced by run) created the first actual FLASHED ESP8266:

esphome run myesp8266.yaml
C:\Users\User\esphome> esphome run myesphome.yaml
INFO Reading configuration myesphome.yaml...
INFO Generating C++ source...
INFO Compiling app...
INFO Running:  platformio run -d myesp8266
Processing myesp8266 (board: nodemcuv2; framework: arduino; platform: platformio/espressif8266@2.6.2)
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Verbose mode can be enabled via `-v, --verbose` option
CONFIGURATION: https://docs.platformio.org/page/boards/espressif8266/nodemcuv2.html
PLATFORM: Espressif 8266 (2.6.2) > NodeMCU 1.0 (ESP-12E Module)
HARDWARE: ESP8266 80MHz, 80KB RAM, 4MB Flash
PACKAGES:
 - framework-arduinoespressif8266 3.20704.0 (2.7.4)
 - tool-esptool 1.413.0 (4.13)
 - tool-esptoolpy 1.20800.0 (2.8.0)
 - toolchain-xtensa 2.40802.200502 (4.8.2)
LDF: Library Dependency Finder -> http://bit.ly/configure-pio-ldf
LDF Modes: Finder ~ chain, Compatibility ~ soft
Library Manager: Installing Update
Library Manager: Already installed, built-in library
Found 32 compatible libraries
Scanning dependencies...
Dependency Graph
|-- <ESPAsyncTCP-esphome> 1.2.3
|   |-- <ESP8266WiFi> 1.0
|-- <ESPAsyncWebServer-esphome> 1.3.0
|   |-- <ESPAsyncTCP-esphome> 1.2.3
|   |   |-- <ESP8266WiFi> 1.0
|   |-- <Hash> 1.0
|   |-- <ESP8266WiFi> 1.0
|   |-- <ArduinoJson-esphomelib> 5.13.3
|-- <ESP8266WiFi> 1.0
|-- <ESP8266mDNS> 1.2
|   |-- <ESP8266WiFi> 1.0
|-- <ArduinoJson-esphomelib> 5.13.3
|-- <DNSServer> 1.1.1
|   |-- <ESP8266WiFi> 1.0
Building in release mode
Retrieving maximum program size .pioenvs\myesp8266\firmware.elf
Checking size .pioenvs\myesp8266\firmware.elf
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM:   [====      ]  39.6% (used 32424 bytes from 81920 bytes)
Flash: [====      ]  37.8% (used 394400 bytes from 1044464 bytes)
================================================================================= [SUCCESS] Took 3.98 seconds =================================================================================INFO Successfully compiled program.
Found multiple options, please choose one:
  [1] COM11 (USB-SERIAL CH340 (COM11))
  [2] Over The Air (myesp8266.local)
(number): 1

You won’t like the totally automated output that follows (above) as it is very long and took a minute or two on my PC to run – but did you notice above – I only typed in “1” – and that is the last time for that project that the board needs to be plugged into the PC unless you want o see serial logging – from there on any old USB hub or USB supply will do (not connected to the PC) and there is no menu. I typed in “1” and a torrent of information poured out to tell me that the board was being “Flashed” and then to tell me that it was connected to the WiFi – for the sake of it, here’s some of that output.

[I][app:106]: ESPHome version 2021.8.2 compiled on Sep 26 2021, 12:15:48
[12:27:57][C][wifi:499]: WiFi:
[12:27:57][C][wifi:359]:   SSID: 'xxxxxxx'
[12:27:57][C][wifi:360]:   IP Address: 192.168.1.238
[12:27:57][C][wifi:362]:   BSSID: 94:83:C4:01:84:6E
[12:27:57][C][wifi:363]:   Hostname: 'myesp8266'
[12:27:57][C][wifi:367]:   Signal strength: -59 dB ▂▄▆█
[12:27:57][C][wifi:371]:   Channel: 1
[12:27:57][C][wifi:372]:   Subnet: 255.255.255.0
[12:27:57][C][wifi:373]:   Gateway: 192.168.1.1
[12:27:57][C][wifi:374]:   DNS1: 192.168.1.1
[12:27:57][C][wifi:375]:   DNS2: (IP unset)
[12:27:57][C][logger:189]: Logger:
[12:27:57][C][logger:190]:   Level: DEBUG
[12:27:57][C][logger:191]:   Log Baud Rate: 115200
[12:27:57][C][logger:192]:   Hardware UART: UART0
[12:27:57][C][captive_portal:148]: Captive Portal:
[12:27:57][C][web_server:152]: Web Server:
[12:27:57][C][web_server:153]:   Address: myesp8266.local:90
[12:27:57][C][ota:029]: Over-The-Air Updates:
[12:27:57][C][ota:030]:   Address: myesp8266.local:8266
[12:27:57][C][api:095]: API Server:
[12:27:57][C][api:096]:   Address: myesp8266.local:6053

Once done, I removed the USB lead from the PC and stuck the lead into a USB hub. The board powered up (not entirely true for my FIRST time as I got things wrong and forgot to add a WEB SERVER block so the board could be visibly seen to be working without serial – so let’s cheat a little). With the board now plugged into non-PC USB for power only – I added THIS (below) to the very end of YAML file… formatting is important so “web server:” needs to be on the left and that tab before “port: 80” is important as is the space before the 80 – port 80 is usually a good choice – I could have used another other port (90 for example) but 80 is the default choice for web pages and saves keying in a port number in the browser

web_server:
    port: 80

I saved the file and used the RUN command as above.

At this point, here is a screenshot of myesp8266 in my browser (you might see :90 appended in the graphic – indeed ignore that top URL in the image below entirely – I got it wrong at first – your system may need myesp8266.local or myesp8266.broadband or similar. depends on the router or any customising that’s been done to it – in my case the line below is what I ended up keying into my browser…

 http://myesp8266.lan

The router will have picked the IP address – you could FIX the IP address of course. The web page doesn’t actually DO anything at this point – but in my first projects the web server proved very useful, particularly for on-screen buttons while figuring out how to wire up and use REAL inputs.

AS time goes on I’ll flesh this out and show other test projects as they mature.

The rest – as you will see below is largely a case of adding blocks and modifying them to suit. Below is my first functioning TTGO project – there is no other code than what you see below. As well as SPI for the display, I also needed I2c – that is SO easy to add – just specify GPIO pins. That complex-looking BME sensor was just copied directly from an example on the ESPHOME site… What makes it DO something? It reports all by itself at regular intervals.

Once I learned that the term LAMBDA (see below code Lambda |- and the C (ish) code that follows it to drive the display and which refers to items in the YAML largely by ID… I simply used three lines of screen space to display temperature, humidity and pressure on the built-in display.

If you read the code below you’ll see I refer to fonts and CSS colours – there is a fonts folder under ESPHOME and I simply downloaded a few standard TTF fonts into the fonts folder on my PC, the compiler using only what I needed – but this seems WAY more flexible than Tasmota font handling. Colours – I have a color folder with some simple definitions for useful colours… a file therein called COLOR_CSS_AQUAMARINE contains a little text only…

color:
  # name and values from https://www.w3.org/wiki/CSS/Properties/color/keywords

  # #7fffd4
  # 127,255,212
  - id: COLOR_CSS_AQUAMARINE
    red:   0.4980
    green: 1.0000
    blue:  0.8314
    white: 0.0000

and so here I went (not without a struggle) from never having taken any notice of YAML to having a working file including embedded C instructions for an ESP32+ILI9341-based board in a matter of days.. I will come across many more bottlenecks but something tells me it will be worth the effort. With MQTT I can integrate this stuff with Node-Red just as I always have but so much display handling is done entiely in the ESP32 itself – and could be in an ESP8266 though my experience to date suggests that the flexibility of ESPHOME means that ILI9341 and similar larger displays may not do so well on the latter (display buffer sizes and RAM)- as you’ll see later however, little OLED displays – no problem. I must stress that I HAVE had the ILI9341 running on ESP8266 using Tasmota.

On the lower part of the code block below – that simple term LAMBDA alows me to continue and achieve display actions in C with which I AM familiar. Again I stress what you see is what you get – ESPHOME + the file below is all there is to it – any libraries are not the concern of the user as ESPHOME handles all of that automatically along with display and sensor initialisation.

My First ESP32 ESPHOME Project – the TTGO Board

esphome:
  name: ttgo
  platform: ESP32
  board: ttgo-t7-v13-mini32
  on_boot:
    priority: 250
    then:
      - light.turn_on: 
          id: LLED
          effect: rainbow    
 #     - light.turn_on:
 #         id: LLED
 #         brightness: 50%
 #         red: 0%
 #         green: 30%
 #         blue: 100%    

packages:
  colors: !include color/COLOR_CSS

i2c:
  sda: GPIO21
  scl: GPIO22
  scan: True

sensor:
  - platform: bme280
    temperature:
      name: "BME280 Temperature"
      id: bme280_temperature
    pressure:
      name: "BME280 Pressure"
      id: bme280_pressure
    humidity:
      name: "BME280 Relative Humidity"
      id: bme280_humidity
    address: 0x77
    update_interval: 15s
  - platform: template
    name: "Altitude"
    lambda: |-
      const float STANDARD_SEA_LEVEL_PRESSURE = 1013.25; //in hPa, see note
      return ((id(bme280_temperature).state + 273.15) / 0.0065) *
        (powf((STANDARD_SEA_LEVEL_PRESSURE / id(bme280_pressure).state), 0.190234) - 1); // in meter
    update_interval: 15s
    icon: 'mdi:signal'
    unit_of_measurement: 'm'
  - platform: template
    name: "Absolute Humidity"
    lambda: |-
      const float mw = 18.01534;    // molar mass of water g/mol
      const float r = 8.31447215;   // Universal gas constant J/mol/K
      return (6.112 * powf(2.718281828, (17.67 * id(bme280_temperature).state) /
        (id(bme280_temperature).state + 243.5)) * id(bme280_humidity).state * mw) /
        ((273.15 + id(bme280_temperature).state) * r); // in grams/m^3
    accuracy_decimals: 2
    update_interval: 15s
    icon: 'mdi:water'
    unit_of_measurement: 'g/m³'
  - platform: template
    name: "Dew Point"
    lambda: |-
      return (243.5*(log(id(bme280_humidity).state/100)+((17.67*id(bme280_temperature).state)/
      (243.5+id(bme280_temperature).state)))/(17.67-log(id(bme280_humidity).state/100)-
      ((17.67*id(bme280_temperature).state)/(243.5+id(bme280_temperature).state))));
    unit_of_measurement: °C
    icon: 'mdi:thermometer-alert'

# Enable logging
logger:

# Enable Home Assistant API
#api:
# password: ""

ota:
  password: ""

wifi:
  ssid: "wififorus"
  password: "xxxxxxxxx"

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

captive_portal:

light:
  - platform: fastled_clockless
    chipset: WS2812B
    pin: GPIO32
    num_leds: 20
    rgb_order: GRB
    name: "LLED"
    id: LLED
    effects:
      - addressable_random_twinkle:
          name: "twinkle"
      - addressable_fireworks:
          name: "fireworks"
      - addressable_color_wipe:
          name: "wipe"
      - addressable_rainbow:
          name: rainbow

font:
  - file: "fonts/digital7.ttf"
    id: ubuntuBig
    size: 60
  - file: "fonts/ubuntu-r.ttf"
    id: ubuntuMedium
    size: 28
  - file: "fonts/ubuntu-r.ttf"
    id: ubuntuSmall
    size: 14
    glyphs: '♡ÆØÅæøå!"%()+,-_.:*=°?~#0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz'

binary_sensor:
  - platform: status
    name: "Node Status"
    id: system_status
  - platform: gpio
    pin:
      number: GPIO0
      inverted: true
      mode: INPUT_PULLUP
    name: "T-Display Button Input 0"
    id: tdisplay_button_input_0
    on_press:
      then:
        - light.turn_on: 
            id: LLED
            effect: rainbow
    on_state:
    - component.update: mydisplay

  - platform: gpio
    pin:
      number: GPIO35
      inverted: true
    name: "T-Display Button Input 1"
    id: tdisplay_button_input_1
    on_press:
      then:
        - light.turn_off: LLED
    on_state:
    - component.update: mydisplay

# Backlight control
switch:
  - platform: gpio
    pin: GPIO4
    name: "Backlight"
    id: backlight
  - platform: gpio
    pin: 17
    name: "WiFi LED"
    id: wifiLED

#image:
 # - file: "image.png"
 #   id: my_image
 #   resize: 200x200
 #   type: RGB24

#time:
#  - platform: homeassistant
#    id: esptime

time:
  - platform: sntp
    id: esptime

text_sensor:
  - platform: template
    internal: true
    name: "mydis"
    id: mydis

spi:
  clk_pin: GPIO18
  mosi_pin: GPIO19

mqtt:
  broker: 192.168.1.19
  username: admin
  password: T9quila123
  id: mqtt_client
  on_message:
    - topic: ttgo
      then:
         - text_sensor.template.publish:
            id: mydis
            state: !lambda return x;

interval:
  - interval: 1s
    then:
      if:
        condition:
          wifi.connected:
        then:
          - switch.turn_on: wifiLED
        else:
          - switch.turn_off: wifiLED

display:
  - platform: st7789v
    backlight_pin: GPIO4
    cs_pin: GPIO5
    dc_pin: GPIO16
    reset_pin: GPIO23
    rotation: 270
    id: mydisplay

    update_interval: 1s
    lambda: |-
      it.rectangle(0,  0, it.get_width(), it.get_height(), id(COLOR_CSS_LIGHTSKYBLUE));
      it.rectangle(0, 20, it.get_width(), it.get_height(), id(COLOR_CSS_LIGHTSKYBLUE));   // header bar

      if (id(system_status).state) {
        it.print(235, 3, id(ubuntuSmall), id(COLOR_CSS_LIGHTGREEN), TextAlign::TOP_RIGHT, "Online");
      }
      else {
        it.print(235, 3, id(ubuntuSmall), id(COLOR_CSS_RED), TextAlign::TOP_RIGHT, "Offline");
      }
      //it.strftime((240 / 2), (140 / 3) * 1 - 10, id(ubuntuMedium), id(COLOR_CSS_ORANGE), TextAlign::CENTER, "%A", id(esptime).now());
      //it.strftime((240 / 2), (140 / 3) * 1 + 20, id(ubuntuMedium), id(COLOR_CSS_SILVER), TextAlign::CENTER, "%d-%m-%Y", id(esptime).now());

      it.rectangle(170, 20, 70, 60, id(COLOR_CSS_LIGHTSKYBLUE));
      it.rectangle(00, 20, 171, 60, id(COLOR_CSS_LIGHTSKYBLUE));
      it.strftime((205), (140 / 3) * 1 - 10, id(ubuntuMedium), id(COLOR_CSS_ORANGE), TextAlign::CENTER, "%a", id(esptime).now());
      it.strftime((205), (140 / 3) * 1 + 16, id(ubuntuMedium), id(COLOR_CSS_SILVER), TextAlign::CENTER, "%d", id(esptime).now());

      it.filled_rectangle(1, 80, 238, 58,id(COLOR_CSS_YELLOW));
      it.strftime((240 / 2), (140 / 3) * 2 + 8, id(ubuntuBig), id(COLOR_CSS_BLACK), TextAlign::CENTER, "%H:%M:%S", id(esptime).now());
      // it.print(5, 3, id(ubuntuSmall), id(COLOR_CSS_YELLOW), TextAlign::TOP_LEFT, "ESPHome");
      it.printf(5, 3, id(ubuntuSmall),id(COLOR_CSS_YELLOW),"%s",(id(mydis).state).c_str());
 
      if (id(bme280_temperature).has_state()) 
        {
        it.printf(5, 25, id(ubuntuSmall),id(COLOR_CSS_GOLDENROD),"BME280 T: %.*f°c",1,id(bme280_temperature).state);
        it.printf(5, 41, id(ubuntuSmall),id(COLOR_CSS_GREENYELLOW),"BME280 H: %.*ff",1,id(bme280_humidity).state);
        it.printf(5, 57, id(ubuntuSmall),id(COLOR_CSS_HOTPINK),"BME280 P: %.*f°hPA",1,id(bme280_pressure).state);
        }

      if (id(tdisplay_button_input_0).state==0)
        it.filled_circle(155, 40, 6, id(COLOR_CSS_BLUE));
        else
        it.filled_circle(155, 40, 6, id(COLOR_CSS_HOTPINK));

      if (id(tdisplay_button_input_1).state==0)
        it.filled_circle(155, 60, 6, id(COLOR_CSS_BLUE));
        else
        it.filled_circle(155, 60, 6, id(COLOR_CSS_HOTPINK));
        
      // it.image(0, 0, id(my_image));

web_server:
    port: 80

Coming Up

I now have an ESP8266 running a Dallas temperature chip (in stainless tube), a set of ws2812b RGB LEDs and an SH1106 128×64 1.2″ OLED display which is my second project having realised that ILI9341 on ESP8266 using ESPHOME isn’t a great idea – I also have project #3, a little ATOM LITE ESP32 board which seems to have some issues with RGB LEDs so that is currently running an SSD1306 128×32 display – on this board I learned how to use PAGES of displays – currently cycled using web page template buttons as I concentrate on developing the pages and my WiFi graphical signal display – ultimately I’ll have the pages self-timed. Ignore the board NAME below – I originally went for an S1351 display…

esphome:
  name: s13512
  platform: ESP8266
  board: nodemcuv2
  #on_boot:
   # priority: 250
   # then:
   #     - light.turn_on: 
   #         id: LLED
   #         effect: rainbow
  platformio_options:
    board_build.f_cpu: 160000000L
     # then:
     #   - light.turn_on:
     #       id: LLED
     #       brightness: 50%
     #       red: 0%
     #       green: 30%
     #       blue: 100%    

# Enable logging
logger:

switch:
  - platform: template
    name: "Rainbow"
    optimistic: true
    turn_on_action:
      - light.turn_on: 
          id: LLED
          effect: rainbow
    turn_off_action:
      - light.turn_off: 
          id: LLED
  - platform: template
    name: "Cyan"
    optimistic: true
    turn_on_action:
      - light.turn_on: 
          id: LLED
          brightness: 100%
          red: 0%
          green: 100%
          blue: 100%    
    turn_off_action:
      - light.turn_off: 
          id: LLED
  - platform: template
    name: "Magenta"
    optimistic: true
    turn_on_action:
      - light.turn_on: 
          id: LLED
          brightness: 100%
          red: 100%
          green: 0%
          blue: 100%    
    turn_off_action:
      - light.turn_off: 
          id: LLED

mqtt:
  broker: 192.168.1.19
  username: admin
  password: T9quila123
  id: mqtt_client

sensor:
  - platform: dallas
    address: 0xD000000628016C28
    name: "Test Temperature"
    id: testtem

dallas:
  - pin: GPIO2

# Enable Home Assistant API
#api:
#  password: ""

ota:
  password: ""

binary_sensor:
  - platform: status
    name: "Node Status"
    id: system_status

wifi:
  ssid: "wififorus"
  password: "xxxxxxxx"

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

#captive_portal:
web_server:
    port: 80

font:
  - file: "fonts/digital7.ttf"
    id: sseg
    size: 33
  - file: "fonts/ubuntu-r.ttf"
    id: tiny
    size: 10
   
time:
  - platform: sntp
    id: esptime

# Example configuration entry
i2c:
  sda: D1
  scl: D2

light:
  - platform: fastled_clockless
    chipset: WS2812B
    pin: D8
    num_leds: 5
    rgb_order: GRB
    name: "LLED"
    id: LLED
    effects:
      - addressable_random_twinkle:
          name: "twinkle"
      - addressable_fireworks:
          name: "fireworks"
      - addressable_color_wipe:
          name: "wipe"
      - addressable_rainbow:
          name: rainbow

display:
  - platform: ssd1306_i2c
    model: "SH1106_128X64"
    address: 0x3C
    lambda: |-
      it.rectangle(0,  0, 128, 64);
      it.rectangle(0,  0, 128, 16);
      if (id(system_status).state) {
        it.print(124, 2, id(tiny), TextAlign::TOP_RIGHT, "Online");
      }
      else {
        it.print(124, 2, id(tiny), TextAlign::TOP_RIGHT, "Offline");
      }
      if (id(testtem).has_state()) 
        it.printf(3, 2, id(tiny),"Dallas %.*f°c",1,id(testtem).state);

      it.strftime(3, 18, id(tiny), TextAlign::LEFT, "%A", id(esptime).now());
      it.strftime(124, 18, id(tiny), TextAlign::RIGHT, "%d-%m-%Y", id(esptime).now());

      it.strftime((128 / 2),40, id(sseg), TextAlign::CENTER, "%H:%M:%S", id(esptime).now());

I’ve had the latter board (LITE + SSD1306) out on the hill above our place running on a USB battery pack to check for range limits and to ensure my signal indicator works (4 simple rectangles, filled or not as needs be by reading an internal value from the YAML code). The other two boards merely say online or offline as appropriate – but in reality I’ve now updated all of them to the graphical indicator).

SH1106 display and ssd1306 display (3 PAGES)

And the code for the smaller SD1306-based board… in the YAML code the hash is a comment line – in the C section, usual C single-line comments apply – but be wary about indents – you can’t put C comments any more left than the code itself – YAML is, it seems as bad as PYTHON for being picky about indents.

esphome:
  name: atomlite
  platform: ESP32
  board: m5stack-core-esp32
 
mqtt:
  broker: 192.168.1.19
  username: admin
  password: T9quila123
  id: mqtt_client

i2c:
  sda: GPIO21
  scl: GPIO22

globals:
  - id: mypages
    type: int
    restore_value: no
    initial_value: '0'

# Enable logging
logger:

# Enable Home Assistant API
#api:
#  password: ""

ota:
  password: ""

wifi:
  ssid: "wififorus"
  password: "xxxxxxxxxx"

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

captive_portal:

time:
  - platform: sntp
    id: esptime

output:
  - platform: gpio
    pin: GPIO33
    id: leds

light:
  - platform: binary
    name: "mylight"
    id: mylight
    output: leds

binary_sensor:
  - platform: status
    name: "Node Status"
    id: system_status

sensor:
  - platform: wifi_signal
    name: "WiFi Signal Sensor"
    update_interval: 15s
    id: sstrength
  - platform: uptime
    name: Uptime Sensor
    id: upt

web_server:
    port: 80

font:
  - file: "fonts/digital7.ttf"
    id: sseg
    size: 26
  - file: "fonts/ubuntu-r.ttf"
    id: tiny
    size: 12
  - file: "fonts/ubuntu-r.ttf"
    id: teeny
    size: 10

switch:
  - platform: template
    name: "Simple LED"
    optimistic: true
    turn_on_action:
      - light.turn_on: 
          id: mylight
    turn_off_action:
      - light.turn_off: 
          id: mylight
  - platform: template
    name: "next page"
    #optimistic: true   - use this if using on_turn_on but turn_on_action seems better.
    turn_on_action:
      - display.page.show_next: the_display

text_sensor:
  - platform: wifi_info
    ip_address:
      name: ESP IP Address
      id: ipaddr
    ssid:
      name: ESP Connected SSID
    bssid:
      name: ESP Connected BSSID
    mac_address:
      name: ESP Mac Wifi Address
      id: mac

display:
  - platform: ssd1306_i2c
    model: "ssd1306_128X32"
    id: the_display
    address: 0x3C
    pages:
      - id: page1
        lambda: |-
              int x=(int)id(sstrength).state; 
              if (x<0)
              {
                if ((x>-90) && (x<0)) it.filled_rectangle( 0,14,5,4); else  it.rectangle( 0,14,5,4);          
                if ((x>-70) && (x<0)) it.filled_rectangle( 6,11,5,7); else  it.rectangle( 6,11,5,7);   
                if ((x>-50) && (x<0)) it.filled_rectangle( 12, 8,5,10); else it.rectangle( 12, 8,5,10);
                if ((x>-30) && (x<0)) it.filled_rectangle( 18,5,5,13); else it.rectangle( 18,5,5,13); 
              }

              it.strftime(0, 18, id(tiny), TextAlign::LEFT, "%A", id(esptime).now());
              it.strftime(127, 18, id(tiny), TextAlign::RIGHT, "%d-%m-%Y", id(esptime).now());
              it.strftime(127, 0, id(sseg), TextAlign::RIGHT, "%H:%M:%S", id(esptime).now());

      - id: page2
        lambda: |-
              int x=(int)id(sstrength).state; 
              it.printf(0,0,id(teeny), "WiFi: %idb",x);
              it.printf(0,10,id(teeny),"Addr: %s",(id(ipaddr).state).c_str());
              it.printf(0,20,id(teeny),"MAC:  %s",(id(mac).state).c_str());             

      - id: page3
        lambda: |-
              //it.printf(0,0,id(teeny),"UP: %0.0fs",(id(upt).state));
              int seconds = round(id(upt).state);
              int days = seconds / (24 * 3600);
              seconds = seconds % (24 * 3600);
              int hours = seconds / 3600;
              seconds = seconds % 3600;
              int minutes = seconds /  60;
              seconds = seconds % 60;
              it.printf(0,0,id(teeny),"Uptime: %d days, %02d:%02d:%02d",days,hours,minutes,seconds);


Atom Lite ESPHOME code

If I’ve done anything the hard way around, feel free to tell me how to make a better job. If you don’t understand the code, feel free to ask in here but don’t expect miracles yet – I’m still learning.

On the left you see the web server page generated by the Atom lite – rather handy for testing eithout having to wire up actual buttons – just as well, considering my new discovery that 4 of the ESP32 GPIOS can’t use internal pullups (that kept me going for a while until a kind chat in the ESPHOME forums enlightened me)

And for me the best way to learn is iterative small changes to hardware and software.

Good job I have a constant supply of new boards to test and play with – most of which are on my limited-space desk right now. At this rate I’m going to need a smaller keyboard.

See on the left where it say “next page”? See the SWITCH section above and the platform: template named “next page” – well that controls the pages defined in the display section of the above code.

It’s all a bit of a challenge the first time around but then you quickly get used to the idea, like much of ESPHOME.

AND NOW – the M5Stack Stamp Pico board

I didn’t even bother with other environments for this board (I could have tried UIFLOW or Arduino) – I went straight in at the deep end with ESPHOME.

I picked ESP32 as the target and m5stack-atom as the board, as this was the nearest available option – well – it seems to work.

I repeated steps as earlier, using the ESPHOME wizard and serial port to start the ball rolling, then added:

web_server:
    port: 80

And then with the PICO board + supplied FTDI (USB-C) plugged into a normal USB charger I was ready to rock – I went to http://stamp.lan on my network… by now I knew how to get my credentials from the SECRETS.YAML file and started to copy over useful functions to the Stamp – but first…

Below, the initial YAML file for the STAMP (which doesnt actually DO anything other than connect to WiFi and show that “stamp Web Server” page) – soon I’ll start pushing this little board to see just what it can do. As it has a few more pins than the Atom Lite – I’m hopeful.

Soon I can see an attempt coming up to run a pair of OLED displays off thge same I2C – pins – it should be possible to change the default address of 3C to 3D easily enough – it remains to be seen if ESPHOME handles this or not.

Time will tell.

esphome:
  name: stamp
  platform: ESP32
  board: m5stack-atom

# Enable logging
logger:

ota:
  password: ""

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Stamp Fallback Hotspot"
    password: !secret wifi_password
  
mqtt:
  broker: !secret mqtt_broker
  username: !secret mqtt_user
  password: !secret mqtt_password
  id: mqtt_client

captive_portal:

web_server:
    port: 80

February 2023 Update – Non-ESP boards, ESPHOME and More

My thanks to blog subscriber Rogan Dawes for an exciting update on ESPHOME – two things – despite the title – ESPHOME has apparently been ported to the Saspberry Pi Pico and Pico W boards and there’s a fork for use with other chips.. See below:

There is a new project called LibreTuya-ESPHome, which aims to port ESPHome to run on the various Tuya modules. Would be interesting to know which module is in use inside this device.

Rogan has managed to get two Tuya devices reflashed, one a RTL8710BN-based smart switch, and the other a BK7231T-based dimmer.

The cloud cutter project aims to be Tuya-convert for the non-ESP modules.

The Tuya implementation is a fork with the intention of a future merge to add to that.

Cloud cutter is here: https://github.com/tuya-cloudcutter/tuya-cloudcutter

Before I get into that, having not touched ESPHOME for quite some time, I’m having to remember (thank heavens for the blog and ESPHOME’s helpful DISCORD channel) how to use this great free software – you might see some repetition below.

So, new PC, ESP board with Tasmota on it and a blank mind… I went off to the ESPHOME site – which tends to favour non-Windows setups. Non-the-less – this time I installed ESPHOME on my new Windows 64 PC, blindly following site instructions.

Once I got over the slight hitch about the windows PATH not including the horrendously complex path that ESPHOME sits in, I was up and running or so I thought. You ready for this?

C:\Users\pete\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\Scripts&gt;esphome run configuration.yaml

Yes, exactly. Anyway, adding that mess to the Windows path is easy at the command line (remember that?)

set PATH=%PATH%;C:\Users\pete\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\Scripts>esphome run configuration.yaml

Ok, that worked. Now I plugged in my NodeMCU-type ESP8266 board into a handy PC USB socket (no need to press any program or reset button with these boards which have built in microUSB socket).

First things first – I went off to put the standard basic test program from the ESPHOME site into my NotePad++ editor. For reasons beyond me (a) they don’t include the web server section by default and (b) they kind of assume you are into Home Assistant – I am definitely not – I’m happy with my MQTT over Node-Red control which lets me use Node-Red Dashboard to control everything around the house.

So I removed the HA section, added the webserver section, added in a couple of SWITCH items (actually outputs but never mind) and an MQTT section. See the code below..

esphome:
  name: testing

esp8266:
  board: nodemcuv2

# Enable logging
logger:

mqtt:
  broker: 192.168.1.19
  username: admin
  password: myMQTTPassword
  
  on_message:
    - topic: testing/ledA
      payload: "1"
      then:
       - switch.turn_on: ledA
    
    - topic: testing/ledA
      payload: "0"
      then:
       - switch.turn_off: ledA
 
    - topic: testing/ledB
      payload: "1"
      then:
       - switch.turn_on: ledB
    
    - topic: testing/ledB
      payload: "0"
      then:
       - switch.turn_off: ledB

ota:
  password: ""

wifi:
  ssid: "wififorus"
  password: "myWIFIPassword"

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Testing Fallback Hotspot"
    password: "myWiFiPassword"
    
switch:
 - platform: gpio
   name: "LED A"
   id: ledA
   pin: 5  

 - platform: gpio
   name: "LED B"
   id: ledB
   pin: 4


web_server:
 port: 80
    
captive_portal:

I simply modified the default configuration.yaml to the above – using NotePad++ – and saved that the folder mentioned above – you’re supposed to end up with your project in it’s own folder but one thing at a time. Using the Windows command line I punched in:

esphome run configuration.yaml

First time around after the automated compile stage I seleted the pptionto put this onto my ESP board by serial port.

As you can imagine – I wanted this first test to be a simple as possible to ensure I could turn a couple of LEDs on and off by the web interface and by MQTT. Indeed yes and here’s the resulting web interface at the IP address (port 80) of my ESP board.

Easy (well, I had a couple of attempts to get here but in the end it really IS easy as long as you remember that YAML is picky about spacing). That third slider below is some DARK scheme introduced not too long ago into the web interface – all of that sitting on the ESP board. The reference to PIN4 and 5 are GPIO4 and 5, not the Arduino style D4 and D5.

As for the MQTT – you can see that flow on my Node-RED control panel (left). I hope that is of use to someone.

The nodes simply contain topics as seen on the left and payload strings “1” and “0” as needed. The effect is the same as those built-in sliders on the web interface.

If this February update looks simple compared to the code higher up, hopefully it might help other people with memories like sieves like mine. Given a gap of well over a year I’d forgotten all about ESPHOME – with these notes stored here, hopefully soon I’ll get around to playing with some non-ESP chips and ESPHOME. Meanwhile with the Chinese back from holiday and businesses picking up, I’ll soon have plenty to write about. Feel free to comment.

13 thoughts on “My ESPHOME Adventure

  1. Wanted to share my thanks for this blog post and your other post covering the basics of the TTGO and ESPHOME – this was entered what I needed to get started on this project.
    I now have a functional time, date and weather display and am looking at extending this further!

    1. Hi Gary – that’s the crappy WordPress Editor at work – sorry about that. I’m no Wordperss styling editor – if anyone knows how I can pick a better set of sdefaults for code styling do let me know.

      Pete

  2. I’ve also recently discovered esphome. After writing all my own code for sensors and other devices for so long it’s really amazing how easy and functional esphome is without getting in the way. I really like the balance.

    I’m glad you’ve discovered it!

  3. BTW I recommend keeping usernames and passwords in a separate global secrets.yaml. They are no longer visible in the code that you post and it makes updating the password on all units much easier.

  4. ESPHome really shines as an addon-on for Home Assistant. Installation requires local network access via HTTPS (which may be a real pain to set up if you do not kno how to) but after that it is a breeze.
    I can create a new unit with the ‘Add’ button, plug any ESP board into a USB port on my PC and see the board appear in a port list to pick. ESPHome will recognize the correct chip/board so it can at least flash a first bootloader. This is a great help. After that my PC usually loses contact with the board via USB but the new unit is already on WiFi and ready for OTA firmware flashing with a YAML file.

    I have not tried too many boards (ESP32-Cam, several nodemcus) so I do not know how good that identification is, especially about details like memory size and CPU speed.

    No idea why that workflow is not available as a standalone program.

    1. I’m currently working on the Dashboard that goes on my PC…. I’ve used the command line up to now – and when I started up the graphical PC Dashboard, it wanted me to ADD a unit (ie it looks like you have no units yet) – I have been onto the ESPHOME forums with no luck up to now – I cannot get that graphical interface to do anything without the console showing errors – secrets comes up as blank…

      1. Is this (https://www.youtube.com/watch?v=luE1hjCf3HI) the PC Dashboard you are using? Looks very similar to the HA add-on.

        In the ESPHome HA add-on each unit has its config file stored in /config/esphome/unitname.yaml. Unitname has a limited choice of characters for use in a network, for example there is no “_”.
        Maybe you can check that PC Dashboard and the command line tool store the files in the same place.

  5. The great thing about ESPHome is that you can add your own (or third party) components using a simple URL to a git repo, and they become first class citizens.
    And since only the components you select are compiled in to the image, you don’t have the complexity and/or tradeoffs of Tasmota – this image has these components built in, and that image has these. Sorry if you want a mix of the two, build your own firmware, if you can figure out how to!
    Also, you don’t need the web server to do a firmware update OTA. That listens on a dedicated port as almost the first thing the firmware does. So even if the rest of the firmware is a mess, the OTA service is still available. Also, if there are repeating boot failures, ESPHome falls back to a simple config, with just the OTA service running, so that you can at least upload a new firmware.

    1. Hi
      I realised the web server is not OTA – using the web server to make simply buttons without having to use GPIOs while testing… while I get my head around this – do you have a specific example of a goit repo that could bne added to expriment – I assume it has to be in a specific format….

Leave a Reply

Your email address will not be published. Required fields are marked *

Leave the field below empty!


The maximum upload file size: 512 MB. You can upload: image, audio, video, document, spreadsheet, interactive, text, archive, code, other. Links to YouTube, Facebook, Twitter and other services inserted in the comment text will be automatically embedded. Drop file here