### Install Dependencies and Start Development Server Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/contributing/docs.md Install project dependencies and start a local development server with hot-reloading. Requires Node.js v18 or later. ```bash npm install npm run dev ``` -------------------------------- ### Run ESPHome Setup Script Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/contributing/development-environment.md Execute the setup script to create a Python virtual environment and install necessary dependencies for ESPHome development. ```bash script/setup ``` -------------------------------- ### Migrate Climate Custom Modes Setup Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2026-04-09-climate-fan-custom-mode-storage.md This migration guide shows how to move from setting custom fan modes and presets on traits within the `traits()` override to setting them directly on the Climate entity in `setup()`. ```cpp // Before — in traits() override, called on every publish_state() climate::ClimateTraits traits() override { auto traits = climate::ClimateTraits(); traits.set_supported_custom_fan_modes({"Low", "Medium", "High"}); traits.set_supported_custom_presets({"Eco", "Sleep"}); // ... other traits return traits; } // After — set once, traits() gets them automatically void setup() override { this->set_supported_custom_fan_modes({"Low", "Medium", "High"}); this->set_supported_custom_presets({"Eco", "Sleep"}); } climate::ClimateTraits traits() override { auto traits = climate::ClimateTraits(); // custom_fan_modes and custom_presets are wired automatically // ... other traits return traits; } ``` -------------------------------- ### Implement Component Setup Priority Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/architecture/components/index.md Implement `get_setup_priority` to define the component's initialization order. Higher return values indicate later setup. ```cpp float ExampleComponent::get_setup_priority() const { // Return the setup priority of this component // Higher values mean this component will be set up later return setup_priority::DATA; } ``` -------------------------------- ### Set Select Options in setup() (C++) Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2025-11-07-select-entity-index-operations.md Shows how to set options for a Select entity within the `setup()` method. The new approach uses an initializer list with string literals directly. ```cpp // OLD std::vector options = {"Low", "Medium", "High"}; this->traits.set_options(options); // NEW - use initializer list with string literals this->traits.set_options({"Low", "Medium", "High"}); ``` -------------------------------- ### Migrate Fan Preset Modes Setup Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2026-04-09-climate-fan-custom-mode-storage.md This migration guide illustrates updating Fan components to set preset modes in `setup()` and call `wire_preset_modes_()` within the `get_traits()` override, replacing the older method of passing modes to `FanTraits` constructor. ```cpp // Before fan::FanTraits get_traits() override { return fan::FanTraits(true, true, true, this->preset_modes_); } // After — get_traits() override must call wire_preset_modes_() to expose them void setup() override { this->set_supported_preset_modes({"Auto", "Sleep", "Nature"}); } fan::FanTraits get_traits() override { fan::FanTraits traits(true, true, true); this->wire_preset_modes_(traits); return traits; } ``` -------------------------------- ### Install ESPHome using UV Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/contributing/development-environment.md Install a development version of ESPHome using the 'uv' package manager, simplifying the installation process. Replace 'username' and 'branch' with your details. The --system flag is required on Windows. ```bash uv pip install git+https://github.com/username/esphome.git@branch --system ``` -------------------------------- ### Example SERVER_HELLO Hex Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/architecture/api/protocol_details.md An example of a SERVER_HELLO message represented in hexadecimal format, illustrating the byte structure. ```hex Hex: 01 00 1C 01 65 73 70 68 6F 6D 65 00 31 32 3A 33 34 3A 35 36 3A 37 38 3A 39 41 3A 42 43 00 ^ ^^^^^ ^ ^--------node------^ ^ ^-----------------MAC address--------------------^ ^ | | | | | | | Protocol (0x01) null null | Size (28 bytes, big-endian) Indicator (0x01) ``` -------------------------------- ### Install ESPHome from GitHub Fork (Git) Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/contributing/development-environment.md Install a development version of ESPHome directly from a Git branch in your GitHub fork. Replace 'username' and 'branch' with your details. ```bash pip install git+https://github.com/username/esphome.git@branch ``` -------------------------------- ### C++ ifdef Migration Example Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2026-01-12-use-esp-idf-to-use-esp32.md Migrate C++ code from checking `USE_ESP_IDF` to `USE_ESP32`. This example shows how to include `esp_wifi.h` and initialize Wi-Fi for ESP32 builds. ```cpp // Before #ifdef USE_ESP_IDF #include "esp_wifi.h" void init_wifi() { esp_wifi_init(...); } #endif // After #ifdef USE_ESP32 #include "esp_wifi.h" void init_wifi() { esp_wifi_init(...); } #endif ``` -------------------------------- ### Run ESPHome Development Server Source: https://github.com/esphome/developers.esphome.io/blob/main/README.md Starts the ESPHome development server. This command builds and serves the documentation locally. ```bash mkdocs serve ``` -------------------------------- ### Install ESPHome from GitHub Fork (ZIP) Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/contributing/development-environment.md Install a development version of ESPHome from a ZIP archive of your GitHub fork. Replace 'username' and 'branch' with your details. ```bash pip install --pre https://github.com/username/esphome/archive/branch.zip ``` -------------------------------- ### YAML Configuration Example Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/architecture/overview.md A simple YAML configuration snippet that ESPHome uses to identify and load components. ```yaml hello1: sensor: - platform: hello2 ``` -------------------------------- ### Install Requirements Source: https://github.com/esphome/developers.esphome.io/blob/main/README.md Installs the necessary Python packages for ESPHome development. Ensure you are in the project's root directory. ```bash pip install -r requirements.txt ``` -------------------------------- ### Component Setup Priority Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/architecture/components/index.md The `get_setup_priority()` method determines the order in which components are initialized. Higher values indicate later initialization. ```APIDOC ## GET /api/setup_priority ### Description Retrieves the setup priority for a component. ### Method GET ### Endpoint `/api/setup_priority` ### Parameters #### Query Parameters - **component_id** (string) - Required - The unique identifier of the component. ### Response #### Success Response (200) - **priority** (float) - The setup priority value for the component. ### Response Example ```json { "priority": 10.5 } ``` ``` -------------------------------- ### HomeKit Light Integration Migration Example Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2025-12-07-light-listener-pattern.md Demonstrates migrating a HomeKit light integration from old callbacks to the new listener pattern for state synchronization. ```cpp // Before: class HomeKitLight { public: void setup(light::LightState *light) { this->light_ = light; light->add_new_target_state_reached_callback([this]() { this->on_entity_update(); }); } protected: void on_entity_update() { // Sync state to HomeKit } light::LightState *light_; }; // After: #include "esphome/components/light/light_state.h" class HomeKitLight : public light::LightTargetStateReachedListener { public: void setup(light::LightState *light) { this->light_ = light; light->add_target_state_reached_listener(this); } void on_light_target_state_reached() override { this->on_entity_update(); } protected: void on_entity_update() { // Sync state to HomeKit } light::LightState *light_; }; ``` -------------------------------- ### Alarm Control Panel Unified Callback Example Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2026-01-12-listener-interface-pattern.md Demonstrates replacing multiple per-state callbacks with a single unified callback for the Alarm Control Panel. The example uses a switch statement to handle different alarm states. ```cpp // Before - multiple callbacks alarm->add_on_triggered_callback([]() { handle_triggered(); }); alarm->add_on_arming_callback([]() { handle_arming(); }); alarm->add_on_disarmed_callback([]() { handle_disarmed(); }); // After - single callback that checks get_state() alarm->add_on_state_callback([alarm]() { using namespace alarm_control_panel; switch (alarm->get_state()) { case ACP_STATE_TRIGGERED: handle_triggered(); break; case ACP_STATE_ARMING: handle_arming(); break; case ACP_STATE_DISARMED: handle_disarmed(); break; default: break; } }); ``` -------------------------------- ### Directory Structure Example Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/architecture/overview.md Illustrates the typical directory layout for ESPHome components, including Python and C++ source files. ```tree esphome ├── __main__.py ├── automation.py ├── codegen.py ├── config_validation.py ├── components │ ├── __init__.py │ ├── dht12 │ │ ├── __init__.py │ │ ├── dht12.cpp │ │ ├── dht12.h │ │ ├── sensor.py │ ├── restart │ │ ├── __init__.py │ │ ├── restart_switch.cpp │ │ ├── restart_switch.h │ │ ├── switch.py │ ... ├── tests │ ├── components │ │ ├── dht12 │ │ │ ├── common.yaml │ │ │ ├── test.esp32-ard.yaml │ │ │ ├── test.esp32-c3-ard.yaml │ │ │ ├── test.esp32-c3-idf.yaml │ │ │ ├── test.esp32-idf.yaml │ │ │ ├── test.esp8266-ard.yaml │ │ │ ├── test.rp2040-ard.yaml │ ... ``` -------------------------------- ### Migration Guide Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2026-01-12-build-info-compilation-time.md Guidance on how to update external components to use the new build info API. ```APIDOC ## Migration Guide ### 1. Displaying Build Time **Before:** ```cpp ESP_LOGI(TAG, "Built: %s", App.get_compilation_time()); ``` **After:** ```cpp char build_time[Application::BUILD_TIME_STR_SIZE]; App.get_build_time_string(build_time); ESP_LOGI(TAG, "Built: %s", build_time); ``` ### 2. Getting Build Time as a Timestamp **Before:** ```cpp const char *time_str = App.get_compilation_time(); // ... parse time_str to get time ``` **After:** ```cpp time_t build_time = App.get_build_time(); // Direct timestamp (constexpr) ``` ### 3. Hashing for Preferences **Before:** ```cpp uint32_t hash = fnv1_hash(App.get_compilation_time_ref()); ``` **After (using config + version hash):** ```cpp uint32_t hash = App.get_config_version_hash(); // Includes ESPHome version ``` **After (using config hash only):** ```cpp uint32_t hash = App.get_config_hash(); // Config only ``` ### 4. Checking for Configuration Changes **Usage:** The new hashes are `constexpr`, providing a compile-time evaluated hash with no runtime overhead. This is ideal for invalidating preferences when the configuration changes. ```cpp constexpr uint32_t CONFIG_HASH = App.get_config_hash(); if (stored_hash != CONFIG_HASH) { // Configuration has changed, reset preferences } ``` ``` -------------------------------- ### Migrating from LogListener to LogCallback Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2026-03-12-log-callback-replaces-log-listener.md This example shows the migration from inheriting from LogListener to using add_log_callback with a lambda function. ```cpp // Before #include "esphome/components/logger/logger.h" class MyLogForwarder : public Component, public logger::LogListener { public: void setup() override { if (logger::global_logger != nullptr) logger::global_logger->add_log_listener(this); } void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override { // Forward log message this->send_log(level, tag, message, message_len); } }; // After #include "esphome/components/logger/logger.h" class MyLogForwarder : public Component { public: void setup() override { if (logger::global_logger != nullptr) logger::global_logger->add_log_callback( this, [](void *self, uint8_t level, const char *tag, const char *message, size_t message_len) { static_cast(self)->on_log( level, tag, message, message_len); }); } void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) { // Forward log message (no longer 'override') this->send_log(level, tag, message, message_len); } }; ``` -------------------------------- ### Python Code Generation Example Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/architecture/overview.md Example of a `to_code` method in Python used for ESPHome code generation. It demonstrates variable creation, component registration, and setting required keys. Coroutines with `await` are used to handle dependencies. ```python import esphome.codegen as cg async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) cg.add(var.set_my_required_key(config[CONF_MY_REQUIRED_KEY])) ``` -------------------------------- ### Implement ESPHome Component C++ Logic Source: https://context7.com/esphome/developers.esphome.io/llms.txt This C++ implementation file provides the runtime logic for the `ExampleComponent`. It includes the `setup`, `loop`, and `dump_config` methods, along with logging for initialization and configuration details. ```cpp // example_component.cpp - C++ implementation #include "esphome/core/log.h" #include "example_component.h" namespace esphome::example_component { static const char *TAG = "example_component"; void ExampleComponent::setup() { // Initialize hardware, validate communication ESP_LOGD(TAG, "Setting up component"); } void ExampleComponent::loop() { // Called every ~16ms - must NOT block // Process data, update state } void ExampleComponent::dump_config() { ESP_LOGCONFIG(TAG, "Example Component:\n" " Foo: %s\n" " Bar: %d", TRUEFALSE(this->foo_), this->bar_); } } // namespace esphome::example_component ``` -------------------------------- ### Migrate Climate Component: Before Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2025-11-07-climate-entity-optimizations.md Example of an older ESPHome climate component implementation using `std::set` for modes. ```cpp #include class MyClimate : public Climate { public: void set_modes(const std::set &modes) { this->modes_ = modes; } climate::ClimateTraits traits() override { auto traits = climate::ClimateTraits(); traits.set_supported_modes(this->modes_); traits.add_supported_custom_fan_mode("Turbo"); traits.add_supported_custom_fan_mode("Silent"); return traits; } void control(const climate::ClimateCall &call) override { if (call.get_custom_fan_mode().has_value()) { std::string mode = *call.get_custom_fan_mode(); if (mode == "Turbo") { this->custom_fan_mode = "Turbo"; } } } protected: std::set modes_; }; ``` -------------------------------- ### Example Usage of get_build_time_string() Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2026-01-12-build-info-compilation-time.md Demonstrates writing a formatted build time string to a character buffer using `App.get_build_time_string()`. The format is ISO 8601 style. ```cpp char buffer[Application::BUILD_TIME_STR_SIZE]; // 26 bytes App.get_build_time_string(buffer); // buffer now contains "2026-01-12 14:30:45 +0000" ``` -------------------------------- ### Migration Example for register_action Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2026-03-12-register-action-synchronous.md Demonstrates how to update an external component's register_action call to include the synchronous=True parameter, which is suitable for most actions. ```python # Before @automation.register_action("my_comp.do_thing", DoThingAction, DO_THING_SCHEMA) async def do_thing_to_code(config, action_id, template_arg, args): ... ``` ```python # After — most actions are synchronous @automation.register_action( "my_comp.do_thing", DoThingAction, DO_THING_SCHEMA, synchronous=True, ) async def do_thing_to_code(config, action_id, template_arg, args): ... ``` -------------------------------- ### Migrate Socket getpeername/getsockname Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2026-01-12-socket-clientinfo-string-removal.md Example demonstrating the migration from the old heap-allocating getpeername() to the new stack buffer-based getpeername_to(). ```cpp // Before - heap allocation std::string peer = socket->getpeername(); ESP_LOGD(TAG, "Connected from: %s", peer.c_str()); // After - stack buffer char peer[socket::SOCKADDR_STR_LEN]; socket->getpeername_to(peer); ESP_LOGD(TAG, "Connected from: %s", peer); ``` -------------------------------- ### Example Usage of get_build_time() Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2026-01-12-build-info-compilation-time.md Demonstrates how to retrieve the build time as a Unix timestamp using the `App.get_build_time()` method. This value is evaluated at compile time. ```cpp time_t build = App.get_build_time(); // Example: 1736694645 (Unix timestamp) ``` -------------------------------- ### MQTT Socket Consumption Example Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/architecture/components/socket_consumption_api.md Example of registering socket consumption for the MQTT component, which typically requires one socket for the broker connection. This should be part of the component's `CONFIG_SCHEMA`. ```python def _consume_mqtt_sockets(config): """MQTT needs one socket for broker connection.""" from esphome.components import socket socket.consume_sockets(1, "mqtt")(config) return config CONFIG_SCHEMA = cv.All( cv.Schema({ # MQTT configuration }).extend(cv.COMPONENT_SCHEMA), _consume_mqtt_sockets, ) ``` -------------------------------- ### Find PWM/Waveform Usage with grep Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2026-01-12-esp8266-build-optimizations.md Command-line examples using grep to find direct usage of PWM/waveform functions like 'analogWrite', 'tone', and 'startWaveform' in C++ code within a component directory. ```bash # Find PWM/waveform usage grep -rn "analogWrite" your_component/ grep -rn "tone(" your_component/ grep -rn "startWaveform" your_component/ ``` -------------------------------- ### Example Usage of get_config_version_hash() Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2026-01-12-build-info-compilation-time.md Illustrates obtaining a hash that combines both the configuration and the ESPHome version using `App.get_config_version_hash()`. This is useful for invalidating preferences when either the config or version changes. ```cpp uint32_t hash = App.get_config_version_hash(); ``` -------------------------------- ### dump_summary() with Mode Information Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2026-01-12-gpiopin-dump-summary-buffer.md An example of implementing the new dump_summary() method to include GPIO mode (INPUT/OUTPUT) and inversion status in the output string. ```cpp size_t MyGPIOPin::dump_summary(char *buffer, size_t len) const override { const char *mode = this->mode_ == gpio::FLAG_INPUT ? "INPUT" : "OUTPUT"; if (this->inverted_) { return snprintf(buffer, len, "MY_GPIO%02d (INVERTED, %s)", this->pin_, mode); } return snprintf(buffer, len, "MY_GPIO%02d (%s)", this->pin_, mode); } ``` -------------------------------- ### Example Usage of get_config_hash() Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2026-01-12-build-info-compilation-time.md Shows how to obtain a 32-bit FNV-1a hash of the configuration using `App.get_config_hash()`. This hash changes when the YAML configuration is modified and is evaluated at compile time. ```cpp uint32_t hash = App.get_config_hash(); // Example: 0xABCD1234 ``` -------------------------------- ### Config Validation Schema Example Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/architecture/overview.md Defines a configuration schema using ESPHome's validation system, specifying required and optional keys with default values. ```python import esphome.config_validation as cv CONF_MY_REQUIRED_KEY = 'my_required_key' CONF_MY_OPTIONAL_KEY = 'my_optional_key' CONFIG_SCHEMA = cv.Schema({ cv.Required(CONF_MY_REQUIRED_KEY): cv.string, cv.Optional(CONF_MY_OPTIONAL_KEY, default=10): cv.int_, }).extend(cv.COMPONENT_SCHEMA) ``` -------------------------------- ### Logging Application Name Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2026-03-12-application-name-stringref.md Example of logging the application name using .c_str(). This method is safe and efficient as it directly uses the null-terminated C string pointer provided by StringRef. ```cpp // Works — c_str() returns null-terminated string ESP_LOGD(TAG, "Name: %s", App.get_name().c_str()); ``` -------------------------------- ### Component API Example Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/contributing/code.md Illustrates the distinction between public API (documented on esphome.io) and internal implementation details within a component. Changing documented methods is a breaking change, while internal methods can be modified freely. ```cpp // In a sensor component class MySensorComponent : public PollingComponent, public sensor::Sensor { public: void set_update_interval(uint32_t interval); // Documented in esphome.io - PUBLIC API void set_internal_buffer_size(size_t size); // Not documented - INTERNAL, may change protected: size_t buffer_size_{256}; // Internal implementation detail }; ``` -------------------------------- ### Conditional Compilation for ESPHome Versions Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2026-01-12-socket-clientinfo-string-removal.md This example shows how to use preprocessor directives to support both older and newer versions of ESPHome, ensuring compatibility during the transition period. ```cpp #if ESPHOME_VERSION_CODE >= VERSION_CODE(2026, 1, 0) // New API - buffer-based char peer[socket::SOCKADDR_STR_LEN]; socket->getpeername_to(peer); ESP_LOGD(TAG, "Peer: %s", peer); #else // Old API - heap-allocating std::string peer = socket->getpeername(); ESP_LOGD(TAG, "Peer: %s", peer.c_str()); #endif ``` -------------------------------- ### Dynamically Consume Sockets in Python Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/architecture/components/socket_consumption_api.md Calculate and consume the required number of sockets dynamically based on configuration. This example includes a listener and a configurable number of clients. ```python def _consume_sockets(config): from esphome.components import socket # 1 listener + configured max_clients count = 1 + config.get(CONF_MAX_CLIENTS, 5) socket.consume_sockets(count, "my_component")(config) return config ``` -------------------------------- ### Inefficient Configuration Logging Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/architecture/logging.md This example shows inefficient configuration logging where each field generates a separate network packet, leading to high overhead for components with many settings. ```cpp void MyComponent::dump_config() { ESP_LOGCONFIG(TAG, "My Component:"); ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_); ESP_LOGCONFIG(TAG, " Update Interval: %ums", this->update_interval_); ESP_LOGCONFIG(TAG, " Samples: %d", this->samples_); ESP_LOGCONFIG(TAG, " Mode: %s", this->get_mode_str()); } ``` -------------------------------- ### Complete Migration Example: Custom Event Component (Before) Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2025-11-07-event-entity-optimizations.md This C++ code snippet shows the 'before' state of a custom event component using `std::set` for event types. ```cpp #include class MyEvent : public event::Event { public: void setup() override { std::set types = {"motion_detected", "motion_cleared"}; this->set_event_types(types); } void check_valid_type(const std::string &type) { auto types = this->get_event_types(); if (types.find(type) != types.end()) { ESP_LOGD("event", "Valid type: %s", type.c_str()); } } void iterate_types() { for (const std::string &type : this->get_event_types()) { ESP_LOGD("event", "Type: %s", type.c_str()); } } void log_last_event() { if (this->last_event_type != nullptr) { ESP_LOGD("event", "Last event: %s", this->last_event_type); } } }; ``` -------------------------------- ### Run ESPHome Development Server on a Specific Port Source: https://github.com/esphome/developers.esphome.io/blob/main/README.md Starts the ESPHome development server on a custom port. Useful if the default port (8000) is already in use. ```bash mkdocs serve -a localhost:8001 ``` -------------------------------- ### Finding Overridden Methods with Grep Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2026-03-12-devirtualize-call-loop-mark-failed.md Command-line examples using grep to locate occurrences of overridden methods (call_loop(), mark_failed(), call_dump_config()) within a component's source code, aiding in the migration process. ```bash # Find call_loop() overrides grep -rn 'call_loop().*override' your_component/ # Find mark_failed() overrides grep -rn 'mark_failed().*override' your_component/ # Find call_dump_config() overrides grep -rn 'call_dump_config().*override' your_component/ ``` -------------------------------- ### Activate Python Virtual Environment (Linux/macOS) Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/contributing/development-environment.md Activate the Python virtual environment created by the setup script. This command needs to be run in each new terminal session. ```bash source venv/bin/activate ``` -------------------------------- ### Using set_interval with lambda capture Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/architecture/components/advanced.md Demonstrates `set_interval` usage with lambda functions. The first example is SBO-safe, while the second may cause heap allocation due to larger capture sizes. ```cpp this->set_interval(1000, [this]() { this->tick_(); }); // SBO-safe (one pointer) this->set_interval(1000, [this, addr, name]() { this->ping_(...); }); // heap-allocated (> 8 bytes) ``` -------------------------------- ### Support Multiple ESPHome Versions for Light Callbacks Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2025-12-07-light-listener-pattern.md Provides a conditional compilation example to support both old and new ESPHome versions for light state change handling, ensuring backward compatibility. ```cpp #include "esphome/components/light/light_state.h" #if ESPHOME_VERSION_CODE >= VERSION_CODE(2025, 12, 0) class MyLightComponent : public light::LightTargetStateReachedListener { #else class MyLightComponent { #endif public: void setup(light::LightState *light) { this->light_ = light; #if ESPHOME_VERSION_CODE >= VERSION_CODE(2025, 12, 0) light->add_target_state_reached_listener(this); #else light->add_new_target_state_reached_callback([this]() { this->on_light_target_state_reached(); }); #endif } #if ESPHOME_VERSION_CODE >= VERSION_CODE(2025, 12, 0) void on_light_target_state_reached() override { #else void on_light_target_state_reached() { #endif // Handle state change } protected: light::LightState *light_; }; ``` -------------------------------- ### Wire Format Example: Temperature Reading Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/architecture/api/protocol_details.md Illustrates the wire format for sending a temperature reading, including the Noise indicator, encrypted size, message type, data, and MAC. ```hex Hex: 01 00 0E 00 08 00 06 12 04 08 96 42 10 B4 46 [C H A C H A 2 0 - P O L Y M A C - 1 6 bytes] ``` -------------------------------- ### Generated C++ Code Example Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/architecture/components/index.md This is an example of the C++ code that might be generated by the Python configuration processing. It shows a method call on a variable. ```c++ var->set_foo(true); ``` -------------------------------- ### Preview Production Build Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/contributing/docs.md Preview the production build of the documentation locally. Requires Node.js v18 or later. ```bash npm run preview ``` -------------------------------- ### Web Server Socket Consumption Example Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/architecture/components/socket_consumption_api.md Example of registering socket consumption for a web server component, which requires one listener socket and typically two concurrent client connections. This is registered within the `CONFIG_SCHEMA`. ```python def _consume_web_server_sockets(config): """Web server needs 1 listener + 2 concurrent clients.""" from esphome.components import socket socket.consume_sockets(3, "web_server")(config) return config CONFIG_SCHEMA = cv.All( cv.Schema({ # Web server configuration }).extend(cv.COMPONENT_SCHEMA), _consume_web_server_sockets, ) ``` -------------------------------- ### Update Light Effect Constructor: std::string to const char* (Migration Guide) Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2025-11-07-light-entity-optimizations.md This section from the migration guide shows the required change for custom `LightEffect` constructors, moving from `std::string` to `const char*`. ```cpp // OLD #include class MyEffect : public LightEffect { public: explicit MyEffect(const std::string &name) : LightEffect(name) {} }; // NEW - use const char* class MyEffect : public LightEffect { public: explicit MyEffect(const char *name) : LightEffect(name) {} }; ``` -------------------------------- ### Build Production Documentation Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/contributing/docs.md Build the documentation for production, outputting to the 'dist/' directory. Requires Node.js v18 or later. ```bash npm run build ``` -------------------------------- ### Multi-Instance Camera Web Server Socket Consumption Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/architecture/components/socket_consumption_api.md Example for a multi-instance component like a camera web server. Each instance requires 3 sockets and a unique consumer name including the instance ID is used for registration within the `CONFIG_SCHEMA`. ```python def _consume_camera_sockets(config): """Each camera web server needs 3 sockets.""" from esphome.components import socket # Use unique consumer name per instance socket.consume_sockets(3, f"camera_{config[CONF_ID]}")(config) return config CONFIG_SCHEMA = cv.All( cv.Schema({ cv.GenerateID(): cv.declare_id(ESP32Camera), # Camera configuration }).extend(cv.COMPONENT_SCHEMA), _consume_camera_sockets, ) ``` -------------------------------- ### Clone ESPHome Repository and Set Up Remote Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/contributing/development-environment.md Clone your fork of the ESPHome repository and add the upstream repository as a remote. This is the first step in setting up your local development environment. ```bash git clone https://github.com/YOUR_GITHUB_USERNAME/NAME_OF_FORK.git cd NAME_OF_FORK git remote add upstream https://github.com/esphome/esphome.git ``` -------------------------------- ### Deprecated set_retry API Usage Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2026-02-12-set-retry-deprecation.md These are examples of the deprecated set_retry, cancel_retry, and RetryResult APIs. Using them will produce compiler warnings. ```cpp this->set_retry("name", 100, 5, [](uint8_t) { return RetryResult::RETRY; }); ``` ```cpp this->set_retry(100, 5, [](uint8_t) { return RetryResult::RETRY; }); ``` ```cpp this->cancel_retry("name"); ``` ```cpp RetryResult result = RetryResult::DONE; ``` -------------------------------- ### Finding get_loop_priority() Overrides Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2026-03-12-conditional-get-loop-priority.md A command-line example to locate all occurrences of get_loop_priority() overrides within a component's source code. ```bash # Find get_loop_priority overrides grep -rn 'get_loop_priority' your_component/ ``` -------------------------------- ### Example Handshake Rejection Hex Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/architecture/api/protocol_details.md A hexadecimal representation of a handshake rejection message, demonstrating the structure and content of an error response. ```hex Hex: 01 00 17 01 48 61 6E 64 73 68 61 6B 65 20 4D 41 43 20 66 61 69 6C 75 72 65 ^ ^^^^^ ^ ^----------------------Error message------------------------^ | | | | | Error flag (0x01) | Size (23 bytes, big-endian) Indicator (0x01) ``` -------------------------------- ### Ethernet Event Handler Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/architecture/components/advanced.md Handles Ethernet events such as START and DISCONNECTED. It updates component states and enables the loop when necessary. ```cpp void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event, void *event_data) { switch (event) { case ETHERNET_EVENT_START: global_eth_component->started_ = true; global_eth_component->enable_loop_soon_any_context(); break; case ETHERNET_EVENT_DISCONNECTED: global_eth_component->connected_ = false; global_eth_component->enable_loop_soon_any_context(); break; } } ``` -------------------------------- ### Wire Format Example Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/architecture/api/protocol_details.md Provides a hexadecimal representation of a temperature reading using the plaintext protocol, breaking down each byte's meaning. ```hex 00 06 08 12 04 08 96 42 10 ``` -------------------------------- ### Markdown Headers Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/contributing/docs.md Use hash marks for section headers in Markdown files, starting with H2 (##) as the page title is defined in frontmatter. ```markdown ## Configuration ### Advanced Options #### Specific Setting ``` -------------------------------- ### Conditional LogListener Inheritance for Version Compatibility Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2026-03-12-log-callback-replaces-log-listener.md This example shows how to conditionally inherit from LogListener based on the ESPHome version to maintain backward compatibility. ```cpp class MyLogForwarder : public Component #if ESPHOME_VERSION_CODE < VERSION_CODE(2026, 3, 0) , public logger::LogListener #endif { // ... }; ``` -------------------------------- ### Waking the main loop from a background thread Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/architecture/components/advanced.md Shows how to use `App.wake_loop_threadsafe()` to immediately wake the main loop from a background thread or FreeRTOS task for time-critical events. Include the necessary header. ```cpp #include "esphome/core/application.h" void MyComponent::background_callback(Event event) { this->pending_events_.push_back(event); // Only wake for time-critical events if (event.is_time_critical()) { App.wake_loop_threadsafe(); } } ``` -------------------------------- ### Update Register Access Macros Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2026-03-12-rp2040-pico-sdk-2.md Pico-SDK 2.0 renames hardware register accessor macros. The example shows the change from padsbank0_hw to pads_bank0_hw. ```cpp // Before padsbank0_hw->io[pin] = ...; // After pads_bank0_hw->io[pin] = ...; ``` -------------------------------- ### Disable Component Loop for Event-Driven Components Source: https://context7.com/esphome/developers.esphome.io/llms.txt Example of an interrupt-driven component that disables its loop participation when idle to save CPU. Uses `disable_loop()`. ```cpp // Interrupt-driven component example class GPIOSensor : public Component, public BinarySensor { public: void setup() override { this->pin_->attach_interrupt(gpio_interrupt_handler, this, gpio::INTERRUPT_ANY_EDGE); } void loop() override { if (this->interrupt_triggered_) { bool state = this->pin_->digital_read(); this->publish_state(state); this->interrupt_triggered_ = false; } else { this->disable_loop(); // Sleep until next interrupt } } protected: static void IRAM_ATTR gpio_interrupt_handler(void *arg) { GPIOSensor *sensor = static_cast(arg); sensor->interrupt_triggered_ = true; sensor->enable_loop_soon_any_context(); // Thread-safe wake } volatile bool interrupt_triggered_{false}; GPIOPin *pin_; }; ``` -------------------------------- ### Run Full CI Checks Locally Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/contributing/code.md Execute the complete lint and test suite using the ESPHome Docker image. Mounts the current directory to /esphome within the container. ```bash docker run --rm -v "${PWD}/":/esphome -it ghcr.io/esphome/esphome-lint script/fulltest ``` -------------------------------- ### OTA Listener Interface Implementation Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2026-01-12-listener-interface-pattern.md Example of implementing the OTAStateListener interface in a custom component. This involves inheriting from OTAStateListener and overriding the on_ota_state method. ```cpp #include "esphome/components/ota/ota_backend.h" static const char *const TAG = "my_component"; class MyComponent : public Component, public ota::OTAStateListener { public: void set_ota_parent(ota::OTAComponent *parent) { this->ota_parent_ = parent; } void setup() override { // Register with OTA component (parent set via Python codegen) if (this->ota_parent_ != nullptr) { this->ota_parent_->add_state_listener(this); } } void on_ota_state(ota::OTAState state, float progress, uint8_t error) override { switch (state) { case ota::OTA_STARTED: ESP_LOGI(TAG, "OTA started"); break; case ota::OTA_IN_PROGRESS: ESP_LOGI(TAG, "OTA progress: %.1f%%", progress * 100); break; case ota::OTA_COMPLETED: ESP_LOGI(TAG, "OTA completed"); break; case ota::OTA_ERROR: ESP_LOGE(TAG, "OTA error: %d", error); break; default: break; } } protected: ota::OTAComponent *ota_parent_{nullptr}; }; ``` -------------------------------- ### Run ESPHome with Device Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/contributing/development-environment.md Run an ESPHome configuration and flash it to a device, specifying the serial port. This command is executed from the activated virtual environment. ```bash esphome run some-config-file.yaml --device /dev/tty.your_usb_device ``` -------------------------------- ### Register Component with ESPHome Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/architecture/components/index.md Use `cg.register_component` to register a C++ variable with the ESPHome application. This ensures its setup, loop, and update methods are called. ```python await cg.register_component(var, config) ``` -------------------------------- ### Remove Wake Loop Guards (C++) Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2026-04-09-wake-loop-moved-to-core.md This C++ example shows how to remove preprocessor directives that guarded calls to `App.wake_loop_threadsafe()`. Wake is now always available. ```cpp // Before void some_background_thread() { #if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) App.wake_loop_threadsafe(); #endif } // After void some_background_thread() { App.wake_loop_threadsafe(); } ``` -------------------------------- ### Declare ESPHome Component C++ Header Source: https://context7.com/esphome/developers.esphome.io/llms.txt This C++ header file declares the `ExampleComponent` class, inheriting from `esphome::Component`. It defines the public interface, including methods for setup, loop, and configuration dumping, as well as setters for internal properties. ```cpp // example_component.h - C++ header #pragma once #include "esphome/core/component.h" namespace esphome::example_component { class ExampleComponent : public Component { public: void setup() override; void loop() override; void dump_config() override; void set_foo(bool foo) { this->foo_ = foo; } void set_bar(int bar) { this->bar_ = bar; } protected: bool foo_{false}; int bar_{0}; }; } // namespace esphome::example_component ``` -------------------------------- ### GPIO Component C++ Header Source: https://context7.com/esphome/developers.esphome.io/llms.txt C++ header for a custom GPIO component. It includes methods to set the pin, setup the pin, and toggle its state. ```cpp #pragma once #include "esphome/core/component.h" #include "esphome/core/hal.h" namespace esphome::my_gpio { class MyGPIOComponent : public Component { public: void set_pin(GPIOPin *pin) { this->pin_ = pin; } void setup() override { this->pin_->setup(); this->pin_->digital_write(false); } void toggle() { this->state_ = !this->state_; this->pin_->digital_write(this->state_); } protected: GPIOPin *pin_; bool state_{false}; }; } // namespace esphome::my_gpio ``` -------------------------------- ### Deprecate Configuration Key with Value Transformation Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/contributing/code.md Example of deprecating a configuration key that also involves transforming its value, similar to the ethernet component's approach. ```python import logging _LOGGER = logging.getLogger(__name__) CONF_OLD_MODE = "clk_mode" CONF_CLK = "clk" CONF_MODE = "mode" CONF_PIN = "pin" ``` -------------------------------- ### Linker Errors for Excluded Features Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2026-01-12-esp8266-build-optimizations.md Example linker errors indicating that Serial, Serial1, or waveform functions are being used without being explicitly enabled in ESPHome 2026.1.0+. ```text undefined reference to `Serial' undefined reference to `Serial1' undefined reference to `startWaveform' ``` -------------------------------- ### Define Public Python API for Component Configuration Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/contributing/code.md This example demonstrates how to define a public Python API for a component's configuration schema. Public API elements, like configuration keys and validators, are documented and must maintain backward compatibility. ```python # In esphome/components/my_component/__init__.py import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import CONF_ID # PUBLIC - documented configuration schema CONF_CUSTOM_PARAM = "custom_param" CONFIG_SCHEMA = cv.Schema({ cv.GenerateID(): cv.declare_id(MyComponent), cv.Required(CONF_CUSTOM_PARAM): cv.int_, # PUBLIC - documented config key }) async def to_code(config): # INTERNAL - can change implementation var = cg.new_Pvariable(config[CONF_ID]) cg.add(var.set_custom_param(config[CONF_CUSTOM_PARAM])) ``` -------------------------------- ### Update Capability Checks: Manual Loop vs supports_color_capability() Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2025-11-07-light-entity-optimizations.md While the old manual loop for checking color capabilities still works, using the `supports_color_capability()` method is recommended for cleaner code. ```cpp // OLD - manual loop (still works) bool has_brightness = false; for (auto mode : traits.get_supported_color_modes()) { if (mode & ColorCapability::BRIGHTNESS) { has_brightness = true; break; } } // NEW - recommended approach bool has_brightness = traits.supports_color_capability(ColorCapability::BRIGHTNESS); ``` -------------------------------- ### Find Serial Usage with grep Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2026-01-12-esp8266-build-optimizations.md Command-line examples using grep to find direct usage of 'Serial.' or 'Serial1.' in C++ code within a component directory. ```bash # Find Serial usage in C++ grep -rn "Serial\." your_component/ grep -rn "Serial1\." your_component/ ``` -------------------------------- ### Support Multiple ESPHome Versions with Macros Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2026-01-12-gpiopin-dump-summary-buffer.md Demonstrates how to use version macros to maintain compatibility with both older and newer ESPHome versions when overriding dump_summary(). ```cpp class MyGPIOPin : public GPIOPin { public: #if ESPHOME_VERSION_CODE >= VERSION_CODE(2026, 1, 0) size_t dump_summary(char *buffer, size_t len) const override { return snprintf(buffer, len, "MY_GPIO%02d", this->pin_); } #else std::string dump_summary() const override { char buffer[32]; snprintf(buffer, sizeof(buffer), "MY_GPIO%02d", this->pin_); return buffer; } #endif }; ``` -------------------------------- ### Safe Preset Mode Patterns Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2025-11-07-fan-entity-preset-modes.md Demonstrates safe ways to manage preset mode strings, ensuring pointers remain valid for the component's lifetime. ```cpp // 1. String literals (preferred) - stored in flash traits.set_supported_preset_modes({"Low", "Medium", "High"}); ``` ```cpp // 2. Static constants static const char *const PRESET_LOW = "Low"; traits.set_supported_preset_modes({PRESET_LOW}); ``` ```cpp // 3. C arrays static constexpr const char *const PRESETS[] = {"Low", "Medium", "High"}; traits.set_supported_preset_modes({PRESETS[0], PRESETS[1], PRESETS[2]}); ``` -------------------------------- ### Migrate Fan Preset Modes: Before Source: https://github.com/esphome/developers.esphome.io/blob/main/docs/blog/posts/2025-11-07-fan-entity-preset-modes.md This C++ code demonstrates the previous implementation for setting fan preset modes using std::set. ```cpp #include class MyFan : public fan::Fan { public: void set_preset_modes(const std::set &modes) { this->preset_modes_ = modes; } fan::FanTraits get_traits() override { auto traits = fan::FanTraits(); traits.set_supported_preset_modes(this->preset_modes_); return traits; } void control(const fan::FanCall &call) override { if (!call.get_preset_mode().empty()) { std::string mode = call.get_preset_mode(); if (this->preset_modes_.find(mode) != this->preset_modes_.end()) { // Set mode } } } protected: std::set preset_modes_; }; ```