I2C Sensor Data to MQTT
MCU: ESP32-WROOM-32-N4
Code can be found here for Adafruit STEMMA Soil Sensor. Code can be found here for Adafruit Si7021 Humidity & Temp Sensor.
I’ve combined these two projects into one post, because they are essentially doing the same thing, just for different sensors. The point of this project was to help automate my indoor gardening system, and ensure that when I traveled my plants would no longer die.
I wanted two different sensors for two different purposes: 1) A humidity sensor that would measure the level of humidity in my apartment. When it dropped below a certain level, the humidifier would be turned on. This would help ensure plant health, and also my health in avoiding dry air. 2) A soil moisture sensor that would measure the volumetric water content in the soil. This would be used to automate watering of the plant when the soil got below a certain level. Luckily, Adafruit sold both of these sensors and they were in stock at my local Micro Center! So I went ahead and made a purchase.
After buying these sensors, I initially wired them up to the ESP32 on a breadboard, and used the pre-written Arduino code to test whether my physical wiring abilities were working as expected. This was mainly to rule out any wiring errors so that I wouldn’t have to debug this later on, saving me the trouble. Once that was sorted out, it was code implementation time.
I’ll be honest–I had a hard time figuring out next steps. I was at a crossroads between two different approaches:
1) Automate locally using embedded logic alone. This path would have deepened my hands-on experience with soldering and low-level hardware control, and it was the direction I initially planned to take purely for the learning opportunity. However, as I continued researching, I began to question whether this was the best long-term implementation. Having only worked with bare-metal code, I looked into reverse-engineering Adafruit’s Arduino C++ abstractions. I dug into how Arduino handles I²C, starting with Wire.h and working down to twi.h, even considering reusing the TWI layer directly. The experience revealed just how deep the abstraction stack really was. Beyond complexity, reproducibility became a concern. If I added another plant to the garden, I’d have to rework the embedded logic. I couldn’t help but think–there’s got to be a better option than this, leading to my next approach:
2) Automate using a Home Assistant server. This option offered greater long-term reliability and scalability. Adding more plants would simply involve introducing new devices wired identically and running the same code, making future expansion straightforward. I’d also get to experiment with IoT concepts, and use the ESP32 devices I already had lying around to their advantage. Plus, I’d be able to check humidity and soil sensor stats on my phone, which was honestly very convenient.
As I researched further, I realized I could pair this approach with ESP-IDF, allowing me to work closer to the hardware without reinventing everything from scratch. Reviewing the ESP-IDF APIs and documentation made me realize I had been overcomplicating the problem. This approach aligned naturally with the Home Assistant architecture, supported scalability, and still allowed me to deepen my understanding of IoT systems. It also gave me the opportunity to finally build a Home Assistant server—something I had been wanting to do for a while.
With the direction clear, I moved forward with implementation. I chose TAPO smart plugs for the Home Assistant automation setup due to their reliability and ease of integration. I then deployed a Home Assistant server on my local network using Proxmox, which turned out to be relatively straightforward following an existing installation (guide)[https://community.home-assistant.io/t/installing-home-assistant-os-using-proxmox-8/201835]. I’m so grateful for the internet!
With the server ready, I began writing firmware using ESP-IDF and discovered PlatformIO as an alternative to the Arduino IDE. Its tooling and workflow were a clear improvement, and I don’t see myself going back. Access to clear ESP32 documentation and ESP-IDF APIs also made it significantly easier to develop the project and understand how the individual code segments fit together. With tutorials and online resources, I was able to figure it out and get it working (after various days of debugging, as any project calls for)!
Through the project, I learned:
- PlatformIO is great.
- When to use bare metal and when to use RTOS-based development dependent on the MCU and project requirements. Not every problem benefits from bare-metal control, and understanding when to leverage an RTOS like FreeRTOS is an important design decision.
- How to reverse-engineer existing C++ libraries. Tracing through abstracted codebases and reimplementing core logic deepened my understanding of how higher-level libraries are structured and how to design my own implementations.
- A deeper understanding of how the I2C protocol works under the hood to communicate between master and slave.
- Limitations of ESP32 I2C clock stretching. I had a rather nasty bug that took me a while to debug, The root cause was that the master would time out the transaction when the Si7021 sensor stretched the clock for too long. Initially, I used a holding command that allowed clock stretching, but the ESP32’s i2c_master_transmit_receive() API would terminate the transaction early. The best way to resolve the issue was to separate the write and read operations, with a manual delay inbetween.
- Using a breadboard for the development phase. I learned that breadboards aren’t permanent and should be used solely for development (I understand why now, the form factor is not there). I also reassured to myself that I could connect the wiring correctly between different devices.
- How to structure code to ensure it is modular. Don’t put everything into the main file. Separate functionality into modules for readability and maintainability.
- Home Assistant infrastructure setup involving an MQTT broker, Promox, and figuring out how to best assign IPs.
- Using an IoT device. It was very cool to use the ESP32 as an MQTT client and set it up for communication to Home Assistant via Wifi! We’ve come far as a society to be able to communicate wirelessly.
- AI is a nice tool to be used for learning, as long as you don’t use it as a replacement for thinking. Before this project, I had never really understood the use of AI in coding until now. I’ve always been very wary of using it, because I don’t want it to think for me; I want to be able to think for myself. However, it proved to be very useful through my research and implementation design development. I used AI to explore questions like: What’s the best way to structure this system? Which pros and cons might I be overlooking? How can I ensure the code stays modular and maintainable? Treating it almost like a peer was particularly helpful, since I was working solo.
Now that my code works, it’s onto the next step, which is to design a more portable system, either through 3d printing a case or designing my own PCB for the ESP32 & sensors (no more breadboard!). This definitely opens up a whole new can of worms, but I’m sure I’ll learn a lot and have fun along the way :)
A lot of helpful documentation I used:
MQTT & Home Assistant ESP-IDF MQTT Example ESP32 MQTT Client Tutorial Home Assistant MQTT Sensor Configuration MQTT Broker Set Up via Home Assistant
Wifi ESP-IDF Wifi Station Example
Si7021 I2C Protocol - SI7021 Example Adafruit Si7021 Docs
STEMMA Soil Sensor Adafruit Seesaw I2C Driver Adafruit Seesaw Docs