M5 Atom Lite, Home Assistant, ESPHome, and Capacitive Soil Sensor

By | January 31, 2021

Summary

Update: Tatham had a great thread of tweets about this posts where he filled several gaps in this post, including the need of the MQTT server. Check it out here: https://twitter.com/TathamOddie/status/1357904732027637760?s=20

In this post, I describe what I did to set up an M5 Atom Lite with a capacitive soil moisture sensor, configure it with ESPHome and connect it to Home Assistant. The Home Assistant is deployed in a Docker container on my Synology NAS (DS220+), and my dev machine is a MacbookPro.

Important: In this post, I rely on Tatham Oddie’s in-valuable video, which is a comprehensive guide and introduction to HomeAssistant, ESPHome, and M5 Atom Lite.

So I highly encourage you to check that video before you continue.

Disclaimer: I am not expert in IoT or electricity, please consider this context with this post!

 

Terminology and References

To understand the rest of the article, this is a quick run-through of the main terms:

  • M5 Atom Lite: an ESP32 device that is packaged nicely. You can program it with Arduino Framework (C++), or MicroPython.
  • Capacitive Soil Moisture Sensor: measures the moisture of the soil, and produces the readings as analog stream.
  • Home Assistant: A home automation server/tool. Connects to all the home-assistant-ready devices and presents a web dashboard where you can read and control these devices.
  • ESPHome: a project through which you can program your ESP device and make it home-assistant-ready.

Setting up Home Assistant with Docker

My Synology NAS DS220+ was a perfect candidate to host the Home Assistant server, this is a guide that will tell you how to set it up with Docker.

However, there is a small problem: if you checked Tatham’s video above, you will see that he added ESPHome to Home Assistant as an add-on. The problem is that the Supervisor menu item, through which you can add add-ons to Home Assistant, doesn’t exist in the Docker image of Home Assistant.

To solve this problem, I had to run an independent ESPHome instance in another Docker container to program my device.

To do that, I took the same steps in the guide I mentioned above for Home Assistant. The only difference is that:

  1. Used the image “esphome/esphome:latest” of course!
  2. I mounted to a different folder under “docker” I called “esphome”
  3. And I exposed port 6052

Now you have an ESPHome instance running, and can create the first Node and application on the device.

Creating the Node on ESPHome

To access ESPHome dashboard, I navigate to HTTP://[nas-ip-address]:6052. And to make things easier for me, and for my password manager, I use the awesome service https://nip.io to give this address a proper name like https://esphome-[nas-ip-address]:6052.

In the ESPHome dashboard, I follow the wizard to create my Node, which is a representation of my device. In there,  I can program my device using YAML, which will eventually generate C++ code.

The ESPHome editor already comes with a linter, so your mistakes will be corrected for you in the browser. However, if you want to have access to the generated C++ code, and examine the YAML file in VSCode, you can navigate to the code on the docker folder on your NAS.

Note: When you install Docker on your NAS, the “docker” folder will not be visible on the network. So you have to untick the box ‘Hide this shared folder in “My Network Places”‘.

Generating the code

I started with a very basic YAML to control the LED, I hit Compile menu item in the node to generate the C++ code that will be deployed to my device. Now I am ready to install the application on my device.

esphome:
  name: m5atomlite2
  platform: ESP32
  board: m5stack-core-esp32

wifi:
  ssid: "wifi-ssid"
  password: "wifi-password"

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

captive_portal:

# Enable logging
logger:

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

ota:
  password: "123"

light:
  - platform: fastled_clockless
    chipset: SK6812
    pin: 27
    num_leds: 1
    rgb_order: GRB
    name: "FastLED Light"


Note: Make sure to use the right password for your wifi, and make sure you pick the right configurations for your LED: the pin number on your board and the chipset number of the LED you have.

When you create the Node in the ESPHome dashboard and compile your YAML file, a folder with the same name will be created in the docker/esphome folder on your NAS (that is if you have followed the steps above, otherwise use the name of the folder you mapped).

Flashing the device for the first time

According to Tatham’s video, I need to use something like esphome-flasher, but there was no version for macOS, and I need to find an alternative.

Tatham mentioned that there are many ways to flash an ESP device, but I consulted with my friend @Amal Abeygunawardana and found his suggestion interesting! Use the PlatformIO IDE on VSCode. This also gave me the opportunity to learn about programming my device without ESPHome, using the Arduino Framework (another story).

Once I opened the folder using VSCode, the PlatformIO IDE extension discovered that this is a folder it understands, and it launched the PlatformIO IDE hello page.

I made sure my device is connected to my computer through USB and hit the PlatformIO: Upload command in VSCode. The command compiled and uploaded my firmware binary to the device.

Update the device over the wifi

Now that my device is set up for the first time, I can use ESPHome to upload to the device over the wifi.

However, in my case, I had a problem: the ESPHome cannot find the device over the network using its name devicename.local. When I check the devices connected to my network, I can see my  device and I can ping it! But the ESPHome is still blind to it.

As a workaround, I had to use the property use_address to give an explicit IP address to the node. I gave it the same IP address the DHCP already has given it before, so the wifi section of the YAML file became like this:

wifi:
  ssid: "wifi-ssid"
  password: "wifi-password"
  use_address: 192.168.0.21

After doing that I managed to upload new changes over the wifi. (Please if you know a better solution let me know :)).

Adding the device to Home Assistant

In the Home Assistant dashboard, I navigated to Configuration menu item on the left, hit Integrations, and then at the bottom right corner hit ADD INTEGRATION. Once I am represented with a dialogue I searched for ESPHome.

ESPHome Integration

I put the IP address of the device and magic happens! Under Devices I could see my device, and when I navigated to the details I saw the LED control Entity.

LED Control Display

Connecting the moisture sensor

Ok great, so far so good, but we should not forget what we are here for: a moisture sensor!

I followed this video, but since my device is not Arduino, I had to figure out which pin I should use, and it was pin 33. Thanks to the form factor for the M5 Atom Lite, I only needed jumper wire.

Soil Sensor with M5 Atom Lite

 

Programming for the moisture sensor

Now, all that I have to do is to search for how to configure my YAML file and add an Entity to read data from the moisture sensor. When I searched ESPHome, I couldn’t find a straightforward way to do that, but I stumbled upon the Analog to Digital Sensor, and it appeared to be the answer.

So I added the following segment to the YAML file (Valeria is the name of our plant :D):

sensor:
  - platform: adc
    pin: 33
    name: "Valeria"
    update_interval: 500ms
    attenuation: 11db
    filters:

Of course, the update interval is too excessive, but it is good for debugging purposes when you dip the sensor in a cup of water.

Important Note: depending on the voltage of the sensor, you need to tune the attenuation property, the default is 0db, and I had to change it to 11db. Read the documentation of the Analog to Digital Sensor above for more information.

In ESPHome, I compiled and uploaded the new code, and managed to see the voltage readings next to the LED Entity, success! However, it was basic readings, and I needed a percentage. I found this post on Reddit when I was trying to figure out the Entity, and they already solved it for me :).

So the code below uses the Filter attribute. It takes the raw value of the readings, and passes it as a parameter to the subsequent function to return the result accordingly:

sensor:
  - platform: adc
    pin: 33
    name: "Valeria"
    update_interval: 500ms
    attenuation: 11db
    filters:
    - lambda: |-
          if (x > 3.74) {
            return 0;
          } else if (x < 1.53) {
            return 100;
          } else {
            return (3.74-x) / (3.74-2.85) * 100.0;
          }

Of course, you have to find your lowest and highest raw readings to get the right formula for your sensor. In this case, the highest was 3.74, and the lowest was 1.53. (Update: these are not really accurate values, which explains why I get more than 100 in my video above, I also didn’t remove the label “v”, embarrassing!)

Too much power consumption, let’s use Deep Sleep

The M5 Atom Lite is a small microcontroller, but this doesn’t mean that it doesn’t consume a lot of power. Putting this in a plant pot powered by battery will not last long.

The good thing is that we can use the Deep Sleep mode, once the device is put in deep sleep mode, it will reduce power consumption and the battery will last longer depending on how long you put the device in this mode. For more information about ESP deep sleep, check the following article.

To put the device in deep sleep using ESPHome, we will update our YAML to include the Deep Sleep component:

deep_sleep:
  id: deep_sleep_1
  run_duration: 10s
  sleep_duration: 2min

This will put the device into deep sleep mode for 2 minutes, and then will wake up for 10 seconds to allow the other components to do their job, and then will sleep again for 2 minutes.

But Deep Sleep has a problem…

There is a small problem, though, when you put the device in deep sleep mode: the device will shut down a lot of its capabilities, including CPU and wifi.

This means that the device will not be reachable for two minutes, and will only stay awake for 10 seconds. So if we want to update the firmware, this will be challenging.

So how can we solve this problem? well, if we can prevent the deep sleep mode the FIRST thing when the device wakes up, then we can update its firmware. Once we update the firmware, we re-enable deep sleep (not my genius idea, this is a common practice :))

How to achieve this I hear you say? The answer is MQTT. MQTT is a lightweight protocol to transmit messages between devices. The biggest advantage of this protocol is the persisted message concept: a client can push a message to the broker (server), and the message will stay there until another client (or same client) sends a new value to overwrite it. (also not my idea :D)

So if we push a message to the broker with a value like “turn off deep sleep”, and we configure the device to read from this broker the first thing when it wakes up, then we can achieve our goal!

Luckily this is easy with ESPHome, we need to update our YAML file to use the MQTT component (God I love ESPHome!):

mqtt:
  broker: 192.168.0.231
  port: 1883
  on_message:
    - topic: ota_mode
      payload: 'ON'
      then:
        - deep_sleep.prevent: deep_sleep_1
    - topic: sleep_mode
      payload: 'ON'
      then:
        - deep_sleep.enter: deep_sleep_1

The above segment will program the device so that it will read a message from the server 192.168.0.231, specifically from the Topic “ota_mode” (the name of the topic can be anything you want). In the case there is a message, we check the payload, if it equals to ON, then we prevent the deep sleep component we configured above. However, if there is another message under the topic sleep_mode, then go back to sleep mode.

Ideally, you don’t want two messages to represent one state, but let’s just go with this flow for now. Check the documentation of the MQTT Component to see how you can use Lambdas for tighter control (not AWS Lambda!).

Oops, but we don’t have an MQTT server!

Did I mention I love Docker? We run an MQTT server in a container on NAS just like we did for Home Assistant and ESPHome above. For that, I chose the Mosquitto server, which already has a container image.

The only thing I want to bring your attention to is that I mapped a file to the container on the path /mosquitto/config/mosquitto.conf to host the configuration. And I had the following configuration content:

allow_anonymous true

listener 1883

If you don’t put the second line, the server will only accept messages from clients on the same machine. Please note as well that this is not a secure setup, so please be careful with your choices.

Now, once I want to put my device OUT of sleep mode, I just send a message to the topic “ota_mode” with the value ON. And make sure that the topic “sleep_mode” doesn’t have the value ON. To do that I use the MQTT client “MQTT Explorer“, but you can also run the following command on your NAS through SSH:

docker exec -it [nameOfMosquittoContainerOnNas] mosquitto_pub -V mqttv311 -h localhost -d -t ota_mode “ON”

 

Conclusion

That was actually a lot of fun, and it’s just astonishing how good Home Assistant and ESPHome is. I am usually suspecious of the quality and efficiency of any product that generates code to achieve something, especially from a DSL-like language. In this case, things look pretty solid!

Let me know if you have any questions about this setup, I’d love hear from you, and I hope this helps you in your journey.

6 thoughts on “M5 Atom Lite, Home Assistant, ESPHome, and Capacitive Soil Sensor

  1. Amal Abeygunawardana

    Awesome work Emad! Also, Great work documenting this. I am sure many will find it very useful.

  2. nortwood

    This is a great tutorial! I’ve followed it and have a capacitive sensor returning data. Can you potentially help with the conversion of voltage to a percentage code?

  3. nortwood

    I think I ironed it out. I needed to adjust the voltage ranges, and initially missed your link to this topic. Thanks for the tutorial!

    I thought the esp32 boards I’m using could utilize all of the 18 analog inputs but with adc apparently we can only use 32-39. Are you familiar with any methods to get these to function on the other pins?

  4. Emad Alashi Post author

    Hey Nortwood,

    Thanks for your comments, I am glad things worked for you :).

    For the pins between 32-39, I think this is ESPHome specific, I am not sure how we can use the other pins. The documentation seemed very clear about this so I am assuming workarounds are hard.

    As for the letter V, as you can see I haven’t really removed it :D. But according to the documentation, I think you can use the ‘unit_of_measurement’ option. Check the docs here: https://esphome.io/components/sensor/index.html.

    Good luck and keep your comments coming 🙂

Leave a Reply

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