Expert guide for ESPHome IoT firmware development. Handles YAML configs, C++ codegen, component creation, and multi-platform microcontroller support (ESP32/8266/RP2040/LibreTiny).
Expert assistant for ESPHome - a system to configure microcontrollers (ESP32, ESP8266, RP2040, LibreTiny chips) using YAML configuration files that generate C++ firmware for home automation.
Code-generation architecture: Python parses YAML → generates C++ → PlatformIO compiles → flash to device
**Directory Structure:**
**Core Systems:**
1. **Configuration** (`esphome/config*.py`) - YAML parsing/validation with Voluptuous
2. **Code Generation** (`esphome/codegen.py`, `esphome/cpp_generator.py`) - Python → C++ conversion
3. **Component System** (`esphome/components/`) - Modular platform-specific implementations
4. **Core Framework** (`esphome/core/`) - Lifecycle, hardware abstraction, registration
5. **Dashboard** (`esphome/dashboard/`) - Web UI for device management
**Supported Platforms:**
- Functions/methods/variables: `lower_snake_case`
- Classes/structs/enums: `UpperCamelCase`
- Top-level constants: `UPPER_SNAKE_CASE`
- Function-local constants: `lower_snake_case`
- Protected/private fields: `lower_snake_case_with_trailing_underscore_`
1. Pointer lifetime issues (setters validate/store from known lists)
2. Invariant coupling (fields must stay synchronized)
3. Resource management (setters perform cleanup/registration)
- Conditional compilation (`#ifdef`, `#ifndef`)
- Compile-time sizes from Python codegen (e.g., `cg.add_define()` for `std::array` dimensions)
```
components/[component_name]/
├── __init__.py # Schema & code generation
├── [component].h # C++ header
├── [component].cpp # C++ implementation
└── [platform]/ # Platform-specific code
├── __init__.py
├── [platform].h
└── [platform].cpp
```
```python
DEPENDENCIES = ["required_component"]
AUTO_LOAD = ["auto_loaded_component"]
CONFLICTS_WITH = ["incompatible_component"]
CODEOWNERS = ["@github_username"]
MULTI_CONF = True # Allow multiple instances
```
```python
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_KEY, CONF_ID
CONF_PARAM = "param" # New constant not in esphome/const.py
my_component_ns = cg.esphome_ns.namespace("my_component")
MyComponent = my_component_ns.class_("MyComponent", cg.Component)
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(MyComponent),
cv.Required(CONF_KEY): cv.string,
cv.Optional(CONF_PARAM, default=42): cv.int_,
}).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_key(config[CONF_KEY]))
cg.add(var.set_param(config[CONF_PARAM]))
```
```cpp
namespace esphome::my_component {
class MyComponent : public Component {
public:
void setup() override;
void loop() override;
void dump_config() override;
void set_key(const std::string &key) { this->key_ = key; }
void set_param(int param) { this->param_ = param; }
protected:
std::string key_;
int param_{0};
};
} // namespace esphome::my_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)
```
```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)
```
```python
from esphome.components import switch
CONFIG_SCHEMA = switch.switch_schema().extend({ ... })
async def to_code(config):
var = await switch.new_switch(config)
```
```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))
```
Execute within project environment:
```bash
python3 script/run-in-env.py <command>
python3 script/run-in-env.py pre-commit run
```
**Python Tests:**
```bash
pytest
```
**C++ Static Analysis:**
```bash
clang-tidy
```
**Component Tests:**
```bash
./script/test_build_components -c <component>
./script/test_build_components -t <target>
./script/test_component_grouping.py -e config --all
```
**YAML test files** located in:
When working on ESPHome:
1. **Always prefix member access** with `this->` in C++ code
2. **Use `protected` fields by default**, only `private` when safety-critical (document why)
3. **Avoid `#define` for constants** - use `const` or enums
4. **Add new defines to `esphome/core/defines.h`** for static analysis
5. **Follow component structure** with `__init__.py` for schema and separate C++ files
6. **Extend appropriate schemas** (sensor, binary_sensor, switch, etc.)
7. **Use platform validators** to restrict components to compatible platforms
8. **Test component integration** with `test_component_grouping.py --all` before finalizing
9. **Run formatters** (`ruff`, `clang-format`) and linters (`flake8`, `clang-tidy`)
10. **Document metadata** (DEPENDENCIES, CODEOWNERS, CONFLICTS_WITH) in component `__init__.py`
Leave a review
No reviews yet. Be the first to review this skill!
# Download SKILL.md from killerskills.ai/api/skills/esphome-development-assistant-c83qir/raw