Expert guide for ESPHome IoT firmware development. Helps with YAML config validation, C++ component creation, code generation patterns, and multi-platform microcontroller support (ESP32/ESP8266/RP2040).
Expert assistant for ESPHome - a system to configure microcontrollers (ESP32, ESP8266, RP2040, LibreTiny) using YAML configuration files that generate C++ firmware for home automation.
Provides comprehensive guidance for ESPHome development including:
**Core Architecture:** Code-generation system where Python parses YAML configs and generates C++ firmware compiled via PlatformIO.
**Key Technologies:**
**Directory Structure:**
**Platforms Supported:**
**Naming Conventions:**
**Field Visibility Rules:**
1. Pointer lifetime validation needed (setters validate against known lists)
2. Invariant coupling (e.g., buffer size must match allocated data)
3. Resource management (cleanup/registration in setters)
**Preprocessor Directives:**
- Conditional compilation (`#ifdef`, `#ifndef`)
- Compile-time sizes from Python codegen (e.g., `std::array` dimensions via `cg.add_define()`)
**Other C++ Conventions:**
**Standard Component Structure:**
```
components/[component_name]/
├── __init__.py # Config schema + code generation
├── [component].h # C++ header
├── [component].cpp # C++ implementation
└── [platform]/ # Platform-specific code
├── __init__.py
├── [platform].h
└── [platform].cpp
```
**Component Metadata:**
```python
DEPENDENCIES = ['required_component']
AUTO_LOAD = ['auto_loaded_component']
CONFLICTS_WITH = ['incompatible_component']
CODEOWNERS = ['@github_username']
MULTI_CONF = True # Allow multiple instances
```
**Python Schema Pattern:**
```python
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID
CONF_CUSTOM = "custom_param" # New constant not in esphome/const.py
my_ns = cg.esphome_ns.namespace("my_component")
MyComponent = my_ns.class_("MyComponent", cg.Component)
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(MyComponent),
cv.Required(CONF_CUSTOM): cv.string,
cv.Optional("param", default=42): cv.int_range(min=0, max=100),
}).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
cg.add(var.set_custom(config[CONF_CUSTOM]))
```
**C++ Implementation Pattern:**
```cpp
namespace esphome::my_component {
class MyComponent : public Component {
public:
void setup() override;
void loop() override;
void dump_config() override;
void set_custom(const std::string &val) { this->custom_ = val; }
protected:
std::string custom_;
int param_{42};
};
} // namespace esphome::my_component
```
**Sensor Component:**
```python
from esphome.components import sensor
CONFIG_SCHEMA = sensor.sensor_schema(MySensor).extend(
cv.polling_component_schema("60s")
)
async def to_code(config):
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
```
**Binary Sensor:**
```python
from esphome.components import binary_sensor
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema().extend({...})
async def to_code(config):
var = await binary_sensor.new_binary_sensor(config)
```
**Switch:**
```python
from esphome.components import switch
CONFIG_SCHEMA = switch.switch_schema().extend({...})
async def to_code(config):
var = await switch.new_switch(config)
```
**Common Validators:**
**Platform/Framework Filters:**
**Schema Extensions:**
```python
CONFIG_SCHEMA = cv.Schema({...}) \
.extend(cv.COMPONENT_SCHEMA) \
.extend(uart.UART_DEVICE_SCHEMA) \
.extend(i2c.i2c_device_schema(0x48)) \
.extend(spi.spi_device_schema(cs_pin_required=True))
```
**Python Tests:**
```bash
pytest
```
**C++ Static Analysis:**
```bash
clang-tidy
```
**Component Compilation Tests:**
```bash
script/test_build_components -c my_component -t esp32
./script/test_component_grouping.py -e config --all
```
**Linting:**
```bash
python3 script/run-in-env.py pre-commit run
```
1. **Define schema** in `__init__.py` with proper validation
2. **Add constants** to `esphome/const.py` if reusable, else define locally
3. **Create C++ class** inheriting from appropriate base (Component, Sensor, etc.)
4. **Use `protected` fields** by default for extensibility
5. **Document defines** in `esphome/core/defines.h` if using `cg.add_define()`
6. **Add platform checks** if component is platform-specific
7. **Write tests** in `tests/components/[component]/`
8. **Set CODEOWNERS** for maintenance responsibility
9. **Run linters** before committing
**YAML Config:**
```yaml
sensor:
- platform: my_sensor
name: "My Sensor"
update_interval: 60s
```
**Python (`components/my_sensor/__init__.py`):**
```python
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import CONF_ID
my_sensor_ns = cg.esphome_ns.namespace("my_sensor")
MySensor = my_sensor_ns.class_("MySensor", sensor.Sensor, cg.PollingComponent)
CONFIG_SCHEMA = sensor.sensor_schema(MySensor).extend(
cv.polling_component_schema("60s")
)
async def to_code(config):
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
```
**C++ (`components/my_sensor/my_sensor.h`):**
```cpp
#pragma once
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
namespace esphome::my_sensor {
class MySensor : public sensor::Sensor, public PollingComponent {
public:
void update() override;
void dump_config() override;
protected:
float last_value_{0.0f};
};
} // namespace esphome::my_sensor
```
Leave a review
No reviews yet. Be the first to review this skill!
# Download SKILL.md from killerskills.ai/api/skills/esphome-development-assistant-ienh11/raw