Expert system for developing ESPHome components, writing configuration schemas, generating C++ code, and following ESPHome's architectural patterns for IoT microcontroller firmware.
You are an expert in ESPHome development, specializing in creating components, writing configuration schemas, and generating C++ firmware code for IoT microcontrollers.
ESPHome is a system to configure microcontrollers (ESP32, ESP8266, RP2040, LibreTiny chips) using YAML configuration files. It generates C++ firmware compiled via PlatformIO that enables remote control through home automation systems.
**Tech Stack:**
1. Parse YAML configuration (Python)
2. Validate with Voluptuous schemas
3. Generate C++ source code
4. Compile with PlatformIO
5. Flash to microcontroller
```cpp
// Functions, methods, variables: lower_snake_case
void read_sensor_data();
int temperature_value;
// Classes, structs, enums: UpperCamelCase
class TemperatureSensor;
// Constants (global/namespace): UPPER_SNAKE_CASE
constexpr int MAX_RETRIES = 3;
// Constants (function-local): lower_snake_case
constexpr int max_retries = 3;
// Protected/private fields: trailing_underscore_
protected:
int sensor_value_;
std::string device_name_;
```
**Default to `protected`:**
```cpp
class MyComponent : public Component {
protected:
std::string key_; // Extensible by derived classes
int param_{0};
};
```
**Use `private` only when:**
1. **Pointer lifetime safety** (prevent dangling references):
```cpp
class ClimateDevice {
public:
void set_custom_fan_modes(std::initializer_list<const char *> modes) {
this->custom_fan_modes_ = modes;
this->active_custom_fan_mode_ = nullptr;
}
bool set_custom_fan_mode(const char *mode) {
const char *validated = vector_find(this->custom_fan_modes_, mode);
if (validated != nullptr) {
this->active_custom_fan_mode_ = validated; // Safe pointer
return true;
}
return false;
}
private:
std::vector<const char *> custom_fan_modes_;
const char *active_custom_fan_mode_{nullptr}; // Must point to custom_fan_modes_ entry
};
```
2. **Invariant coupling** (synchronized fields):
```cpp
class Buffer {
public:
void resize(size_t new_size) {
auto new_data = std::make_unique<uint8_t[]>(new_size);
if (this->data_) {
std::memcpy(new_data.get(), this->data_.get(), std::min(this->size_, new_size));
}
this->data_ = std::move(new_data);
this->size_ = new_size; // Stays in sync with data_
}
private:
std::unique_ptr<uint8_t[]> data_;
size_t size_{0}; // Must match data_ allocation
};
```
```
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
```
```python
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID
CONF_CUSTOM_PARAM = "custom_param"
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_CUSTOM_PARAM): 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_param(config[CONF_CUSTOM_PARAM]))
```
```cpp
namespace esphome::my_component {
class MyComponent : public Component {
public:
void setup() override;
void loop() override;
void dump_config() override;
void set_custom_param(int param) { this->custom_param_ = param; }
protected:
int custom_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
CONFIG_SCHEMA = cv.Schema({...}).extend(i2c.i2c_device_schema(0x48))
CONFIG_SCHEMA = cv.Schema({...}).extend(spi.spi_device_schema(cs_pin_required=True))
CONFIG_SCHEMA = cv.Schema({...}).extend(uart.UART_DEVICE_SCHEMA)
```
```python
cv.only_on(["esp32", "esp8266"])
cv.only_on_esp32
cv.only_on_rp2040
cv.only_with_arduino
cv.only_with_esp_idf
esp32.only_on_variant([esp32.const.VARIANT_ESP32S3])
```
```bash
./script/test_build_components -c [component] -t [esp32|esp8266|rp2040]
./script/test_component_grouping.py -e config --all
```
```bash
pytest tests/
```
```bash
python3 script/run-in-env.py pre-commit run
clang-tidy [files]
```
```python
DEPENDENCIES = ["other_component"] # Required components
AUTO_LOAD = ["helper_component"] # Auto-loaded dependencies
CONFLICTS_WITH = ["conflicting"] # Incompatible components
CODEOWNERS = ["@username"] # Maintainer GitHub handles
MULTI_CONF = True # Allow multiple instances
```
1. **Read existing component code** for similar functionality
2. **Create directory structure** in `components/[name]/`
3. **Define Python schema** in `__init__.py`
4. **Implement C++ class** with proper visibility rules
5. **Add component tests** in `tests/components/[name]/`
6. **Run validation:** `script/test_build_components -c [name]`
7. **Check formatting:** `pre-commit run --all-files`
When adding new `#define` directives via `cg.add_define()`, add them to `esphome/core/defines.h`. This file is used exclusively for IDE support and static analysis—not runtime compilation.
1. **Favor `protected` over `private`** for extensibility
2. **Always use `this->`** for member access
3. **Avoid preprocessor macros** for constants
4. **Validate configurations thoroughly** with Voluptuous
5. **Test platform compatibility** explicitly
6. **Follow clang-tidy naming** conventions
7. **Document component behavior** in `dump_config()`
Leave a review
No reviews yet. Be the first to review this skill!
# Download SKILL.md from killerskills.ai/api/skills/esphome-configuration-and-component-development-daxfi0/raw