Unfortunately, developer documentation for ESPHome is pretty much nonexistent. From what I can tell, there are different implementations of Custom Components. One style (older?) is basically a c++ file loaded into your project.
Another version of custom components (newer?) has your custom code located in config/custom_components/my_custom_component
. And within that directory, you’ll have .cpp
or .h
(or both) files, and at least one python file __init__.py
. This follows the standard structure for all ESPHome components (built-in and external). If you code this type of custom component properly, it’s easy to convert it to an external component, which can then be loaded from a git repo or locally anywhere on your build system. The ESPHome Contributing To guide shows the basics of that structure.
So, in building my external component, I first got it running from my custom_components
directory (as mentioned above). Then I converted it to a bonafide external component. When that was working, I pushed it up to github, and referenced it accordingly from my ESPHome project.
The difficult part here, for me at least, was figuring out what to do in the python file. That’s where documentation is fully missing, and you pretty much have to reverse engineer the ESPHome built-in components to see what’s going on and how it works. And it’s not at all obvious - there are heavy layers of abstraction in using one language to code another language. I’m an experienced software developer, and I spent most of a work week before I had a functioning __init__.py
file. I hope the devs put together some docs at some point.
As for build environment, I’m using the official Docker image esphome/esphome
. I think they call this “ESPHome CLI”, and HomeAssistant is not part of this and is not required to make use of this. Once you configure and boot up the esphome
container, you don’t need to use the CLI to run ESPhome or PlatformIO commands. Instead, use the web GUI to configure, build, and upload your ESPHome projects. For coding your C++ components, use whatever text editor (or fancy IDE) that you want.
EDIT: Regarding testing, I have no idea how to test external components, other than building and running on actual hardware. I’ve been experimenting with platformio unit tests, but I can’t get them to work natively on the desktop. If you want to test components that use ESPHome libraries, it looks like you have to build your tests as actual firmware and upload them to your devices.
UPDATE 2025-02-02: Since my above edit, I figured out how to code and run unit tests on my external component – both on the host system (docker esphome/esphome), and on the esp32 hardware. This is all done using the platformio test
on my external component. I have no vscode or any other IDE to make this work, just docker, terminal and a text editor. The basic requirements are:
- Writing tests in c++ (I’m using the Unity testing framework)
- Configuring the
platformio
project directory and platformio.ini
file
- Configuring a docker compose.yml file
- Installing
build-essential
within the esphome/esphome container. (I do this every time I bring up the container, but one could easily create a custom docker image based on esphome/esphome with build-essential
pre-installed).
As mentioned above, I have two testing environments: native and esp32. The native environment compiles the platform agnostic parts of my component and runs tests on them within the docker container. The esp32 environment compiles the entire component, including the esp32 and esphome code that will only run on the esp32 device. The result is the familiar directory of esphome firmware files. I manually upload the firmware to my device, and the testing output shows up in the log (terminal output, web.esphome.io, or whatever you’re using to view logging output from the esp32).
The difficult part here was figuring out how and where to put the esphome source code so platormio could use it to build the test firmware based on a platformio project. When you build esphome from esphome (cli, vscode, web, whatever), esphome assembles (copies source code into) the platformio project directory behind the scenes, and it dynamically copies only the esphome source that is actually needed for your build. Esphome also configures the platformio.ini
file and any env variables that need to be set.
If you manually assemble a platformio project directory and try to build esphome firmware, you have to know what esphome source code to copy into the src/
directory, and you have to manually configure the platformio.ini
file and/or env variables. To get the platformio project working correctly, I copied the src/
directory, platformio.ini
, post_build.py
, and partitions.csv
files from my .esphome/build/<my-component>/
directory, where esphome has already built a basic firmware using my component.
My external component directory doubles as a platformio project, so everything I need to build and test the component is in one place and committed to the git repository. My goal is to make this component a good example of how one might go about creating, coding, and testing an esphome external component. I’m pushing my dev branch to github every few days (when I’m working on this), so feel free to poke around.