├── components ├── rgb │ ├── __init__.py │ ├── rgb_light_output.h │ └── light.py ├── kauf_hlw8012 │ └── __init__.py ├── monochromatic │ ├── __init__.py │ ├── monochromatic_light_output.h │ └── light.py ├── total_daily_energy │ ├── __init__.py │ ├── total_daily_energy.h │ └── total_daily_energy.cpp ├── template │ ├── __init__.py │ ├── event │ │ ├── template_event.h │ │ └── __init__.py │ ├── button │ │ ├── __init__.py │ │ └── template_button.h │ ├── lock │ │ ├── automation.h │ │ ├── template_lock.h │ │ ├── template_lock.cpp │ │ └── __init__.py │ ├── sensor │ │ ├── template_sensor.h │ │ ├── template_sensor.cpp │ │ └── __init__.py │ ├── text_sensor │ │ ├── template_text_sensor.cpp │ │ ├── template_text_sensor.h │ │ └── __init__.py │ ├── binary_sensor │ │ ├── template_binary_sensor.cpp │ │ ├── template_binary_sensor.h │ │ └── __init__.py │ ├── valve │ │ ├── automation.h │ │ └── template_valve.h │ ├── output │ │ ├── template_output.h │ │ └── __init__.py │ ├── fan │ │ ├── template_fan.h │ │ ├── template_fan.cpp │ │ └── __init__.py │ ├── switch │ │ ├── template_switch.h │ │ └── template_switch.cpp │ ├── text │ │ ├── template_text.cpp │ │ ├── template_text.h │ │ └── __init__.py │ ├── datetime │ │ ├── template_date.h │ │ ├── template_time.h │ │ ├── template_datetime.h │ │ ├── template_date.cpp │ │ └── template_time.cpp │ ├── number │ │ ├── template_number.cpp │ │ └── template_number.h │ ├── select │ │ ├── template_select.h │ │ └── template_select.cpp │ └── cover │ │ └── template_cover.h ├── gpio │ ├── __init__.py │ ├── output │ │ ├── gpio_binary_output.cpp │ │ ├── gpio_binary_output.h │ │ └── __init__.py │ ├── one_wire │ │ ├── __init__.py │ │ └── gpio_one_wire.h │ ├── switch │ │ ├── gpio_switch.h │ │ ├── gpio_switch.cpp │ │ └── __init__.py │ └── binary_sensor │ │ ├── gpio_binary_sensor.h │ │ └── gpio_binary_sensor.cpp ├── switch │ ├── automation.cpp │ ├── binary_sensor │ │ ├── switch_binary_sensor.cpp │ │ ├── switch_binary_sensor.h │ │ └── __init__.py │ └── automation.h ├── light │ ├── light_output.cpp │ ├── automation.cpp │ ├── light_json_schema.h │ ├── esp_color_correction.cpp │ ├── light_effect.cpp │ ├── esp_hsv_color.h │ ├── light_output.h │ ├── light_traits.h │ ├── light_effect.h │ ├── esp_hsv_color.cpp │ ├── light_transformer.h │ ├── esp_range_view.h │ ├── esp_range_view.cpp │ ├── types.py │ └── esp_color_correction.h ├── esp8266 │ ├── core.h │ ├── preferences.h │ ├── const.py │ ├── post_build.py.script │ ├── helpers.cpp │ ├── gpio.h │ └── core.cpp ├── safe_mode │ ├── automation.h │ ├── button │ │ ├── safe_mode_button.h │ │ ├── safe_mode_button.cpp │ │ └── __init__.py │ ├── switch │ │ ├── safe_mode_switch.h │ │ ├── safe_mode_switch.cpp │ │ └── __init__.py │ ├── safe_mode.h │ └── __init__.py ├── captive_portal │ ├── dns_server_esp32_idf.h │ ├── captive_portal.h │ └── __init__.py ├── web_server │ ├── ota │ │ ├── ota_web_server.h │ │ └── __init__.py │ └── list_entities.h └── ddp │ ├── ddp_light_effect.h │ ├── ddp_addressable_light_effect.h │ ├── ddp_light_effect_base.cpp │ ├── ddp.h │ ├── ddp_light_effect_base.h │ └── __init__.py ├── esphome-webserver ├── packages │ ├── v2 │ │ ├── src │ │ │ ├── main.ts │ │ │ ├── esp-logo.ts │ │ │ └── css │ │ │ │ ├── reset.ts │ │ │ │ └── button.ts │ │ ├── public │ │ │ ├── home.svg │ │ │ └── logo.svg │ │ ├── index.html │ │ ├── package.json │ │ └── vite.config.ts │ ├── v3 │ │ ├── src │ │ │ ├── main.ts │ │ │ ├── css │ │ │ │ ├── input.ts │ │ │ │ ├── reset.ts │ │ │ │ ├── tab.ts │ │ │ │ ├── button.ts │ │ │ │ ├── app.ts │ │ │ │ └── esp-entity-table.ts │ │ │ ├── esp-logo.ts │ │ │ ├── esp-entity-chart.ts │ │ │ └── main.css │ │ ├── index.html │ │ ├── public │ │ │ └── logo.svg │ │ ├── package.json │ │ └── vite.config.ts │ ├── v1 │ │ ├── package.json │ │ └── src │ │ │ ├── webserver-v1.min.js │ │ │ └── webserver-v1.js │ └── captive-portal │ │ ├── src │ │ ├── icon │ │ │ ├── wifi.svg │ │ │ └── lock.svg │ │ ├── main.ts │ │ └── stylesheet.css │ │ ├── public │ │ └── config.json │ │ ├── package.json │ │ ├── vite.config.ts │ │ ├── README.md │ │ └── index.html ├── .prettierrc.json ├── package.json ├── scripts │ └── make_header.sh ├── LICENSE ├── .gitignore └── README.md └── .gitignore /components/rgb/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/kauf_hlw8012/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/monochromatic/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/total_daily_energy/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /esphome-webserver/packages/v2/src/main.ts: -------------------------------------------------------------------------------- 1 | import "./esp-app" 2 | -------------------------------------------------------------------------------- /esphome-webserver/packages/v3/src/main.ts: -------------------------------------------------------------------------------- 1 | import "./esp-app" 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | _static 3 | node_modules 4 | esphome-webserver/captive-portal/package-lock.json 5 | notes.txt 6 | -------------------------------------------------------------------------------- /components/template/__init__.py: -------------------------------------------------------------------------------- 1 | import esphome.codegen as cg 2 | 3 | template_ns = cg.esphome_ns.namespace("template_") 4 | -------------------------------------------------------------------------------- /components/gpio/__init__.py: -------------------------------------------------------------------------------- 1 | import esphome.codegen as cg 2 | 3 | CODEOWNERS = ["@esphome/core"] 4 | gpio_ns = cg.esphome_ns.namespace("gpio") 5 | -------------------------------------------------------------------------------- /esphome-webserver/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "html.format.wrapAttributes": "auto", 3 | "html.format.wrapLineLength": 0, 4 | "printWidth": 80 5 | } 6 | -------------------------------------------------------------------------------- /esphome-webserver/packages/v2/public/home.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /esphome-webserver/packages/v3/src/css/input.ts: -------------------------------------------------------------------------------- 1 | import { css } from "lit"; 2 | 3 | export default css` 4 | input[type="text"] { 5 | width: 100% !important; 6 | height: 1rem !important; 7 | } 8 | `; 9 | -------------------------------------------------------------------------------- /esphome-webserver/packages/v1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@esphome-webserver/v1", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "build": "mkdir -p ../../_static/v1 && cp ./src/* ../../_static/v1/" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /components/switch/automation.cpp: -------------------------------------------------------------------------------- 1 | #include "automation.h" 2 | #include "esphome/core/log.h" 3 | 4 | namespace esphome { 5 | namespace switch_ { 6 | 7 | static const char *const TAG = "switch.automation"; 8 | 9 | } // namespace switch_ 10 | } // namespace esphome 11 | -------------------------------------------------------------------------------- /esphome-webserver/packages/v2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /esphome-webserver/packages/v3/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /esphome-webserver/packages/captive-portal/src/icon/wifi.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/light/light_output.cpp: -------------------------------------------------------------------------------- 1 | #include "light_output.h" 2 | #include "transformers.h" 3 | 4 | namespace esphome::light { 5 | 6 | std::unique_ptr LightOutput::create_default_transition() { 7 | return make_unique(); 8 | } 9 | 10 | } // namespace esphome::light 11 | -------------------------------------------------------------------------------- /components/template/event/template_event.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/component.h" 4 | #include "esphome/components/event/event.h" 5 | 6 | namespace esphome::template_ { 7 | 8 | class TemplateEvent final : public Component, public event::Event {}; 9 | 10 | } // namespace esphome::template_ 11 | -------------------------------------------------------------------------------- /components/esp8266/core.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef USE_ESP8266 4 | 5 | #include 6 | 7 | extern const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_MODE[16]; 8 | extern const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[16]; 9 | 10 | namespace esphome::esp8266 {} // namespace esphome::esp8266 11 | 12 | #endif // USE_ESP8266 13 | -------------------------------------------------------------------------------- /esphome-webserver/packages/captive-portal/public/config.json: -------------------------------------------------------------------------------- 1 | {"name":"My ESPhome", 2 | "aps":[ 3 | {} 4 | ,{"ssid":"Hermione","rssi":-50,"lock":0} 5 | ,{"ssid":"Neville","rssi":-65,"lock":1} 6 | ,{"ssid":"Gandalf the Grey","rssi":-85,"lock":1} 7 | ,{"ssid":"Hagrid","rssi":-95,"lock":0} 8 | ] 9 | } -------------------------------------------------------------------------------- /esphome-webserver/packages/v2/src/esp-logo.ts: -------------------------------------------------------------------------------- 1 | import { LitElement, svg } from "lit"; 2 | import { customElement } from "lit/decorators.js"; 3 | 4 | import logo from "/logo.svg?raw"; 5 | 6 | @customElement("esp-logo") 7 | export default class EspLogo extends LitElement { 8 | render() { 9 | return svg([logo]); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /esphome-webserver/packages/v3/src/esp-logo.ts: -------------------------------------------------------------------------------- 1 | import { LitElement, svg } from "lit"; 2 | import { customElement } from "lit/decorators.js"; 3 | 4 | import logo from "/logo.svg?raw"; 5 | 6 | @customElement("esp-logo") 7 | export default class EspLogo extends LitElement { 8 | render() { 9 | return svg([logo]); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /components/template/button/__init__.py: -------------------------------------------------------------------------------- 1 | from esphome.components import button 2 | 3 | from .. import template_ns 4 | 5 | TemplateButton = template_ns.class_("TemplateButton", button.Button) 6 | 7 | CONFIG_SCHEMA = button.button_schema(TemplateButton) 8 | 9 | 10 | async def to_code(config): 11 | await button.new_button(config) 12 | -------------------------------------------------------------------------------- /esphome-webserver/packages/v3/src/css/reset.ts: -------------------------------------------------------------------------------- 1 | import { css } from "lit"; 2 | 3 | export default css` 4 | :host, button, select, input { 5 | font-family: ui-monospace, system-ui, "Helvetica", "Roboto", 6 | "Oxygen", "Ubuntu", sans-serif; 7 | --primary-color: #03a9f4; 8 | transition: all 350ms !important; 9 | } 10 | `; 11 | -------------------------------------------------------------------------------- /esphome-webserver/packages/captive-portal/src/icon/lock.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /esphome-webserver/packages/v2/src/css/reset.ts: -------------------------------------------------------------------------------- 1 | import { css } from "lit"; 2 | 3 | export default css` 4 | :host { 5 | font-family: ui-monospace, system-ui, "Helvetica", "Arial Narrow", "Roboto", 6 | "Oxygen", "Ubuntu", sans-serif; 7 | color-scheme: light dark; 8 | --primary-color: #03a9f4; 9 | transition: all 350ms !important; 10 | } 11 | `; 12 | -------------------------------------------------------------------------------- /esphome-webserver/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "esphome-webserver", 3 | "version": "3.0.0", 4 | "license": "MIT", 5 | "workspaces": [ 6 | "packages/*" 7 | ], 8 | "scripts": { 9 | "build": "npm run build:packages && npm run deploy", 10 | "build:packages": "npm run build --workspaces --if-present", 11 | "deploy": "npm run deploy --workspaces --if-present" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /components/template/button/template_button.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/components/button/button.h" 4 | 5 | namespace esphome::template_ { 6 | 7 | class TemplateButton final : public button::Button { 8 | public: 9 | // Implements the abstract `press_action` but the `on_press` trigger already handles the press. 10 | void press_action() override{}; 11 | }; 12 | 13 | } // namespace esphome::template_ 14 | -------------------------------------------------------------------------------- /components/esp8266/preferences.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef USE_ESP8266 4 | 5 | #include "esphome/components/globals/globals_component.h" 6 | 7 | namespace esphome::esp8266 { 8 | 9 | void setup_preferences(uint32_t start_free); 10 | void preferences_prevent_write(bool prevent); 11 | 12 | void set_global_addr(globals::GlobalsComponent *ga_in); 13 | 14 | } // namespace esphome::esp8266 15 | 16 | #endif // USE_ESP8266 17 | -------------------------------------------------------------------------------- /components/esp8266/const.py: -------------------------------------------------------------------------------- 1 | import esphome.codegen as cg 2 | 3 | KEY_ESP8266 = "esp8266" 4 | KEY_BOARD = "board" 5 | KEY_PIN_INITIAL_STATES = "pin_initial_states" 6 | CONF_RESTORE_FROM_FLASH = "restore_from_flash" 7 | CONF_EARLY_PIN_INIT = "early_pin_init" 8 | KEY_FLASH_SIZE = "flash_size" 9 | 10 | # esp8266 namespace is already defined by arduino, manually prefix esphome 11 | esp8266_ns = cg.global_ns.namespace("esphome").namespace("esp8266") 12 | -------------------------------------------------------------------------------- /components/gpio/output/gpio_binary_output.cpp: -------------------------------------------------------------------------------- 1 | #include "gpio_binary_output.h" 2 | #include "esphome/core/log.h" 3 | 4 | namespace esphome { 5 | namespace gpio { 6 | 7 | static const char *const TAG = "gpio.output"; 8 | 9 | void GPIOBinaryOutput::dump_config() { 10 | ESP_LOGCONFIG(TAG, "Binary Output:"); 11 | LOG_PIN(" Pin: ", this->pin_); 12 | LOG_BINARY_OUTPUT(this); 13 | } 14 | 15 | } // namespace gpio 16 | } // namespace esphome 17 | -------------------------------------------------------------------------------- /components/light/automation.cpp: -------------------------------------------------------------------------------- 1 | #include "automation.h" 2 | #include "esphome/core/log.h" 3 | 4 | namespace esphome::light { 5 | 6 | static const char *const TAG = "light.automation"; 7 | 8 | void addressableset_warn_about_scale(const char *field) { 9 | ESP_LOGW(TAG, "Lambda for parameter %s of light.addressable_set should return values in range 0-1 instead of 0-255.", 10 | field); 11 | } 12 | 13 | } // namespace esphome::light 14 | -------------------------------------------------------------------------------- /components/safe_mode/automation.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "safe_mode.h" 3 | 4 | #include "esphome/core/automation.h" 5 | 6 | namespace esphome { 7 | namespace safe_mode { 8 | 9 | class SafeModeTrigger : public Trigger<> { 10 | public: 11 | explicit SafeModeTrigger(SafeModeComponent *parent) { 12 | parent->add_on_safe_mode_callback([this]() { trigger(); }); 13 | } 14 | }; 15 | 16 | } // namespace safe_mode 17 | } // namespace esphome 18 | -------------------------------------------------------------------------------- /components/template/lock/automation.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "template_lock.h" 4 | 5 | #include "esphome/core/automation.h" 6 | 7 | namespace esphome::template_ { 8 | 9 | template class TemplateLockPublishAction : public Action, public Parented { 10 | public: 11 | TEMPLATABLE_VALUE(lock::LockState, state) 12 | 13 | void play(const Ts &...x) override { this->parent_->publish_state(this->state_.value(x...)); } 14 | }; 15 | 16 | } // namespace esphome::template_ 17 | -------------------------------------------------------------------------------- /esphome-webserver/packages/v3/src/css/tab.ts: -------------------------------------------------------------------------------- 1 | import { css } from "lit"; 2 | 3 | export default css` 4 | .tab-header { 5 | display: inline-flex; 6 | min-height: 40px; 7 | font-weight: 400; 8 | padding-inline: 1.5em; 9 | align-items: center; 10 | border-radius: 12px 12px 0px 0px; 11 | background-color: rgba(127, 127, 127, 0.3); 12 | margin-top: 1em; 13 | user-select: none; 14 | } 15 | .tab-container { 16 | border: 2px solid rgba(127, 127, 127, 0.3); 17 | border-radius: 0px 12px 12px 12px; 18 | } 19 | `; 20 | -------------------------------------------------------------------------------- /components/switch/binary_sensor/switch_binary_sensor.cpp: -------------------------------------------------------------------------------- 1 | #include "switch_binary_sensor.h" 2 | #include "esphome/core/log.h" 3 | 4 | namespace esphome { 5 | namespace switch_ { 6 | 7 | static const char *const TAG = "switch.binary_sensor"; 8 | 9 | void SwitchBinarySensor::setup() { 10 | source_->add_on_state_callback([this](bool value) { this->publish_state(value); }); 11 | this->publish_state(source_->state); 12 | } 13 | 14 | void SwitchBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "Switch Binary Sensor", this); } 15 | 16 | } // namespace switch_ 17 | } // namespace esphome 18 | -------------------------------------------------------------------------------- /components/switch/binary_sensor/switch_binary_sensor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../switch.h" 4 | #include "esphome/core/component.h" 5 | #include "esphome/components/binary_sensor/binary_sensor.h" 6 | 7 | namespace esphome { 8 | namespace switch_ { 9 | 10 | class SwitchBinarySensor : public binary_sensor::BinarySensor, public Component { 11 | public: 12 | void set_source(Switch *source) { source_ = source; } 13 | void setup() override; 14 | void dump_config() override; 15 | 16 | protected: 17 | Switch *source_; 18 | }; 19 | 20 | } // namespace switch_ 21 | } // namespace esphome 22 | -------------------------------------------------------------------------------- /components/safe_mode/button/safe_mode_button.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/components/button/button.h" 4 | #include "esphome/components/safe_mode/safe_mode.h" 5 | #include "esphome/core/component.h" 6 | 7 | namespace esphome { 8 | namespace safe_mode { 9 | 10 | class SafeModeButton : public button::Button, public Component { 11 | public: 12 | void dump_config() override; 13 | void set_safe_mode(SafeModeComponent *safe_mode_component); 14 | 15 | protected: 16 | SafeModeComponent *safe_mode_component_; 17 | void press_action() override; 18 | }; 19 | 20 | } // namespace safe_mode 21 | } // namespace esphome 22 | -------------------------------------------------------------------------------- /components/safe_mode/switch/safe_mode_switch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/components/safe_mode/safe_mode.h" 4 | #include "esphome/components/switch/switch.h" 5 | #include "esphome/core/component.h" 6 | 7 | namespace esphome { 8 | namespace safe_mode { 9 | 10 | class SafeModeSwitch : public switch_::Switch, public Component { 11 | public: 12 | void dump_config() override; 13 | void set_safe_mode(SafeModeComponent *safe_mode_component); 14 | 15 | protected: 16 | SafeModeComponent *safe_mode_component_; 17 | void write_state(bool state) override; 18 | }; 19 | 20 | } // namespace safe_mode 21 | } // namespace esphome 22 | -------------------------------------------------------------------------------- /components/template/sensor/template_sensor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/component.h" 4 | #include "esphome/core/template_lambda.h" 5 | #include "esphome/components/sensor/sensor.h" 6 | 7 | namespace esphome::template_ { 8 | 9 | class TemplateSensor final : public sensor::Sensor, public PollingComponent { 10 | public: 11 | template void set_template(F &&f) { this->f_.set(std::forward(f)); } 12 | 13 | void update() override; 14 | 15 | void dump_config() override; 16 | 17 | float get_setup_priority() const override; 18 | 19 | protected: 20 | TemplateLambda f_; 21 | }; 22 | 23 | } // namespace esphome::template_ 24 | -------------------------------------------------------------------------------- /components/template/text_sensor/template_text_sensor.cpp: -------------------------------------------------------------------------------- 1 | #include "template_text_sensor.h" 2 | #include "esphome/core/log.h" 3 | 4 | namespace esphome::template_ { 5 | 6 | static const char *const TAG = "template.text_sensor"; 7 | 8 | void TemplateTextSensor::update() { 9 | if (!this->f_.has_value()) 10 | return; 11 | 12 | auto val = this->f_(); 13 | if (val.has_value()) { 14 | this->publish_state(*val); 15 | } 16 | } 17 | 18 | float TemplateTextSensor::get_setup_priority() const { return setup_priority::HARDWARE; } 19 | 20 | void TemplateTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Template Sensor", this); } 21 | 22 | } // namespace esphome::template_ 23 | -------------------------------------------------------------------------------- /components/template/event/__init__.py: -------------------------------------------------------------------------------- 1 | import esphome.codegen as cg 2 | from esphome.components import event 3 | import esphome.config_validation as cv 4 | from esphome.const import CONF_EVENT_TYPES 5 | 6 | from .. import template_ns 7 | 8 | CODEOWNERS = ["@nohat"] 9 | 10 | TemplateEvent = template_ns.class_("TemplateEvent", event.Event, cg.Component) 11 | 12 | CONFIG_SCHEMA = event.event_schema(TemplateEvent).extend( 13 | { 14 | cv.Required(CONF_EVENT_TYPES): cv.ensure_list(cv.string_strict), 15 | } 16 | ) 17 | 18 | 19 | async def to_code(config): 20 | var = await event.new_event(config, event_types=config[CONF_EVENT_TYPES]) 21 | await cg.register_component(var, config) 22 | -------------------------------------------------------------------------------- /components/template/binary_sensor/template_binary_sensor.cpp: -------------------------------------------------------------------------------- 1 | #include "template_binary_sensor.h" 2 | #include "esphome/core/log.h" 3 | 4 | namespace esphome::template_ { 5 | 6 | static const char *const TAG = "template.binary_sensor"; 7 | 8 | void TemplateBinarySensor::setup() { 9 | if (!this->f_.has_value()) { 10 | this->disable_loop(); 11 | } else { 12 | this->loop(); 13 | } 14 | } 15 | 16 | void TemplateBinarySensor::loop() { 17 | auto s = this->f_(); 18 | if (s.has_value()) { 19 | this->publish_state(*s); 20 | } 21 | } 22 | 23 | void TemplateBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "Template Binary Sensor", this); } 24 | 25 | } // namespace esphome::template_ 26 | -------------------------------------------------------------------------------- /components/template/sensor/template_sensor.cpp: -------------------------------------------------------------------------------- 1 | #include "template_sensor.h" 2 | #include "esphome/core/log.h" 3 | #include 4 | 5 | namespace esphome::template_ { 6 | 7 | static const char *const TAG = "template.sensor"; 8 | 9 | void TemplateSensor::update() { 10 | if (!this->f_.has_value()) 11 | return; 12 | 13 | auto val = this->f_(); 14 | if (val.has_value()) { 15 | this->publish_state(*val); 16 | } 17 | } 18 | 19 | float TemplateSensor::get_setup_priority() const { return setup_priority::HARDWARE; } 20 | 21 | void TemplateSensor::dump_config() { 22 | LOG_SENSOR("", "Template Sensor", this); 23 | LOG_UPDATE_INTERVAL(this); 24 | } 25 | 26 | } // namespace esphome::template_ 27 | -------------------------------------------------------------------------------- /esphome-webserver/packages/v2/public/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/light/light_json_schema.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/defines.h" 4 | 5 | #ifdef USE_JSON 6 | 7 | #include "esphome/components/json/json_util.h" 8 | #include "light_call.h" 9 | #include "light_state.h" 10 | 11 | namespace esphome::light { 12 | 13 | class LightJSONSchema { 14 | public: 15 | /// Dump the state of a light as JSON. 16 | static void dump_json(LightState &state, JsonObject root); 17 | /// Parse the JSON state of a light to a LightCall. 18 | static void parse_json(LightState &state, LightCall &call, JsonObject root); 19 | 20 | protected: 21 | static void parse_color_json(LightState &state, LightCall &call, JsonObject root); 22 | }; 23 | 24 | } // namespace esphome::light 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /components/captive_portal/dns_server_esp32_idf.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifdef USE_ESP_IDF 3 | 4 | #include 5 | #include "esphome/core/helpers.h" 6 | #include "esphome/components/network/ip_address.h" 7 | #include "esphome/components/socket/socket.h" 8 | 9 | namespace esphome::captive_portal { 10 | 11 | class DNSServer { 12 | public: 13 | void start(const network::IPAddress &ip); 14 | void stop(); 15 | void process_next_request(); 16 | 17 | protected: 18 | static constexpr size_t DNS_BUFFER_SIZE = 192; 19 | 20 | std::unique_ptr socket_{nullptr}; 21 | network::IPAddress server_ip_; 22 | uint8_t buffer_[DNS_BUFFER_SIZE]; 23 | }; 24 | 25 | } // namespace esphome::captive_portal 26 | 27 | #endif // USE_ESP_IDF 28 | -------------------------------------------------------------------------------- /components/template/text_sensor/template_text_sensor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/component.h" 4 | #include "esphome/core/automation.h" 5 | #include "esphome/core/template_lambda.h" 6 | #include "esphome/components/text_sensor/text_sensor.h" 7 | 8 | namespace esphome::template_ { 9 | 10 | class TemplateTextSensor final : public text_sensor::TextSensor, public PollingComponent { 11 | public: 12 | template void set_template(F &&f) { this->f_.set(std::forward(f)); } 13 | 14 | void update() override; 15 | 16 | float get_setup_priority() const override; 17 | 18 | void dump_config() override; 19 | 20 | protected: 21 | TemplateLambda f_{}; 22 | }; 23 | 24 | } // namespace esphome::template_ 25 | -------------------------------------------------------------------------------- /esphome-webserver/packages/v3/src/css/button.ts: -------------------------------------------------------------------------------- 1 | import { css } from "lit"; 2 | 3 | export default css` 4 | button, 5 | .btn { 6 | cursor: pointer; 7 | border-radius: 4px; 8 | color: rgb(3, 169, 244); 9 | border: none; 10 | background-color: unset; 11 | padding: 8px; 12 | font-weight: 500; 13 | font-size: 12.25px; 14 | letter-spacing: 1.09375px; 15 | text-transform: uppercase; 16 | margin-right: -8px; 17 | } 18 | 19 | button:active, 20 | .btn:active { 21 | background-image: rgba(127, 127, 127, 0.2); 22 | transition-duration: 1s; 23 | } 24 | 25 | button:hover, 26 | .btn:hover { 27 | background-color: rgba(127, 127, 127, 0.2); 28 | transition-duration: 1s; 29 | } 30 | `; 31 | -------------------------------------------------------------------------------- /components/web_server/ota/ota_web_server.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/defines.h" 4 | #ifdef USE_WEBSERVER_OTA 5 | 6 | #include "esphome/components/ota/ota_backend.h" 7 | #include "esphome/components/web_server_base/web_server_base.h" 8 | #include "esphome/core/component.h" 9 | 10 | namespace esphome { 11 | namespace web_server { 12 | 13 | class WebServerOTAComponent : public ota::OTAComponent { 14 | public: 15 | void setup() override; 16 | void dump_config() override; 17 | float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } 18 | 19 | protected: 20 | friend class OTARequestHandler; 21 | }; 22 | 23 | } // namespace web_server 24 | } // namespace esphome 25 | 26 | #endif // USE_WEBSERVER_OTA 27 | -------------------------------------------------------------------------------- /components/template/binary_sensor/template_binary_sensor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/component.h" 4 | #include "esphome/core/template_lambda.h" 5 | #include "esphome/components/binary_sensor/binary_sensor.h" 6 | 7 | namespace esphome::template_ { 8 | 9 | class TemplateBinarySensor final : public Component, public binary_sensor::BinarySensor { 10 | public: 11 | template void set_template(F &&f) { this->f_.set(std::forward(f)); } 12 | 13 | void setup() override; 14 | void loop() override; 15 | void dump_config() override; 16 | 17 | float get_setup_priority() const override { return setup_priority::HARDWARE; } 18 | 19 | protected: 20 | TemplateLambda f_; 21 | }; 22 | 23 | } // namespace esphome::template_ 24 | -------------------------------------------------------------------------------- /esphome-webserver/packages/v3/public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/template/valve/automation.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "template_valve.h" 4 | 5 | #include "esphome/core/automation.h" 6 | 7 | namespace esphome::template_ { 8 | 9 | template class TemplateValvePublishAction : public Action, public Parented { 10 | TEMPLATABLE_VALUE(float, position) 11 | TEMPLATABLE_VALUE(valve::ValveOperation, current_operation) 12 | 13 | void play(const Ts &...x) override { 14 | if (this->position_.has_value()) 15 | this->parent_->position = this->position_.value(x...); 16 | if (this->current_operation_.has_value()) 17 | this->parent_->current_operation = this->current_operation_.value(x...); 18 | this->parent_->publish_state(); 19 | } 20 | }; 21 | 22 | } // namespace esphome::template_ 23 | -------------------------------------------------------------------------------- /components/ddp/ddp_light_effect.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef USE_ARDUINO 4 | 5 | #include "esphome/core/component.h" 6 | #include "esphome/components/light/light_effect.h" 7 | #include "esphome/components/light/light_output.h" 8 | #include "ddp_light_effect_base.h" 9 | 10 | namespace esphome { 11 | namespace ddp { 12 | 13 | class DDPLightEffect : public DDPLightEffectBase, public light::LightEffect { 14 | public: 15 | DDPLightEffect(const char *name); 16 | 17 | const char *get_name() override; 18 | 19 | void start() override; 20 | void stop() override; 21 | void apply() override; 22 | 23 | protected: 24 | uint16_t process_(const uint8_t *payload, uint16_t size, uint16_t used) override; 25 | }; 26 | 27 | } // namespace ddp 28 | } // namespace esphome 29 | 30 | #endif // ESPHOME_DDP_LIGHT_EFFECT_H 31 | -------------------------------------------------------------------------------- /components/esp8266/post_build.py.script: -------------------------------------------------------------------------------- 1 | import shutil 2 | 3 | # pylint: disable=E0602 4 | Import("env") # noqa 5 | 6 | 7 | def esp8266_copy_factory_bin(source, target, env): 8 | firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin") 9 | new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.factory.bin") 10 | 11 | shutil.copyfile(firmware_name, new_file_name) 12 | 13 | 14 | def esp8266_copy_ota_bin(source, target, env): 15 | firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin") 16 | new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.ota.bin") 17 | 18 | shutil.copyfile(firmware_name, new_file_name) 19 | 20 | 21 | # pylint: disable=E0602 22 | env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp8266_copy_factory_bin) # noqa 23 | env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp8266_copy_ota_bin) # noqa 24 | -------------------------------------------------------------------------------- /components/gpio/output/gpio_binary_output.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/component.h" 4 | #include "esphome/core/hal.h" 5 | #include "esphome/components/output/binary_output.h" 6 | 7 | namespace esphome { 8 | namespace gpio { 9 | 10 | class GPIOBinaryOutput : public output::BinaryOutput, public Component { 11 | public: 12 | void set_pin(GPIOPin *pin) { pin_ = pin; } 13 | 14 | void setup() override { 15 | this->turn_off(); 16 | this->pin_->setup(); 17 | this->turn_off(); 18 | } 19 | void dump_config() override; 20 | float get_setup_priority() const override { return setup_priority::HARDWARE; } 21 | 22 | protected: 23 | void write_state(bool state) override { this->pin_->digital_write(state); } 24 | 25 | GPIOPin *pin_; 26 | }; 27 | 28 | } // namespace gpio 29 | } // namespace esphome 30 | -------------------------------------------------------------------------------- /components/gpio/one_wire/__init__.py: -------------------------------------------------------------------------------- 1 | from esphome import pins 2 | import esphome.codegen as cg 3 | from esphome.components.one_wire import OneWireBus 4 | import esphome.config_validation as cv 5 | from esphome.const import CONF_ID, CONF_PIN 6 | 7 | from .. import gpio_ns 8 | 9 | CODEOWNERS = ["@ssieb"] 10 | 11 | GPIOOneWireBus = gpio_ns.class_("GPIOOneWireBus", OneWireBus, cg.Component) 12 | 13 | CONFIG_SCHEMA = cv.Schema( 14 | { 15 | cv.GenerateID(): cv.declare_id(GPIOOneWireBus), 16 | cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, 17 | } 18 | ).extend(cv.COMPONENT_SCHEMA) 19 | 20 | 21 | async def to_code(config): 22 | var = cg.new_Pvariable(config[CONF_ID]) 23 | await cg.register_component(var, config) 24 | 25 | pin = await cg.gpio_pin_expression(config[CONF_PIN]) 26 | cg.add(var.set_pin(pin)) 27 | -------------------------------------------------------------------------------- /components/light/esp_color_correction.cpp: -------------------------------------------------------------------------------- 1 | #include "esp_color_correction.h" 2 | #include "light_color_values.h" 3 | #include "esphome/core/log.h" 4 | 5 | namespace esphome::light { 6 | 7 | void ESPColorCorrection::calculate_gamma_table(float gamma) { 8 | for (uint16_t i = 0; i < 256; i++) { 9 | // corrected = val ^ gamma 10 | auto corrected = to_uint8_scale(gamma_correct(i / 255.0f, gamma)); 11 | this->gamma_table_[i] = corrected; 12 | } 13 | if (gamma == 0.0f) { 14 | for (uint16_t i = 0; i < 256; i++) 15 | this->gamma_reverse_table_[i] = i; 16 | return; 17 | } 18 | for (uint16_t i = 0; i < 256; i++) { 19 | // val = corrected ^ (1/gamma) 20 | auto uncorrected = to_uint8_scale(powf(i / 255.0f, 1.0f / gamma)); 21 | this->gamma_reverse_table_[i] = uncorrected; 22 | } 23 | } 24 | 25 | } // namespace esphome::light 26 | -------------------------------------------------------------------------------- /components/safe_mode/button/safe_mode_button.cpp: -------------------------------------------------------------------------------- 1 | #include "safe_mode_button.h" 2 | #include "esphome/core/hal.h" 3 | #include "esphome/core/log.h" 4 | #include "esphome/core/application.h" 5 | 6 | namespace esphome { 7 | namespace safe_mode { 8 | 9 | static const char *const TAG = "safe_mode.button"; 10 | 11 | void SafeModeButton::set_safe_mode(SafeModeComponent *safe_mode_component) { 12 | this->safe_mode_component_ = safe_mode_component; 13 | } 14 | 15 | void SafeModeButton::press_action() { 16 | ESP_LOGI(TAG, "Restarting in safe mode"); 17 | this->safe_mode_component_->set_safe_mode_pending(true); 18 | 19 | // Let MQTT settle a bit 20 | delay(100); // NOLINT 21 | App.safe_reboot(); 22 | } 23 | 24 | void SafeModeButton::dump_config() { LOG_BUTTON("", "Safe Mode Button", this); } 25 | 26 | } // namespace safe_mode 27 | } // namespace esphome 28 | -------------------------------------------------------------------------------- /components/gpio/output/__init__.py: -------------------------------------------------------------------------------- 1 | from esphome import pins 2 | import esphome.codegen as cg 3 | from esphome.components import output 4 | import esphome.config_validation as cv 5 | from esphome.const import CONF_ID, CONF_PIN 6 | 7 | from .. import gpio_ns 8 | 9 | GPIOBinaryOutput = gpio_ns.class_("GPIOBinaryOutput", output.BinaryOutput, cg.Component) 10 | 11 | CONFIG_SCHEMA = output.BINARY_OUTPUT_SCHEMA.extend( 12 | { 13 | cv.Required(CONF_ID): cv.declare_id(GPIOBinaryOutput), 14 | cv.Required(CONF_PIN): pins.gpio_output_pin_schema, 15 | } 16 | ).extend(cv.COMPONENT_SCHEMA) 17 | 18 | 19 | async def to_code(config): 20 | var = cg.new_Pvariable(config[CONF_ID]) 21 | await output.register_output(var, config) 22 | await cg.register_component(var, config) 23 | 24 | pin = await cg.gpio_pin_expression(config[CONF_PIN]) 25 | cg.add(var.set_pin(pin)) 26 | -------------------------------------------------------------------------------- /esphome-webserver/packages/captive-portal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@esphome-webserver/captive-portal", 3 | "version": "2.0.0", 4 | "main": "main.ts", 5 | "license": "MIT", 6 | "scripts": { 7 | "start": "npm run dev", 8 | "dev": "vite", 9 | "build": "vite build && npm run deploy", 10 | "serve": "vite preview", 11 | "deploy": "bash -c '../../scripts/make_header.sh ../../_static/captive_portal captive_index.h captive_portal'" 12 | }, 13 | "dependencies": { 14 | "rollup-plugin-generate-html-template": "^1.7.0", 15 | "rollup-plugin-gzip": "^2.5.0", 16 | "rollup-plugin-minify-html-template-literals": "^1.2.0", 17 | "vite-plugin-html": "^2.1.1" 18 | }, 19 | "devDependencies": { 20 | "@rollup/plugin-node-resolve": "^13.0.6", 21 | "@types/node": "^15.12.1", 22 | "typescript": "^4.1.3", 23 | "vite": "^2.6.14", 24 | "vite-plugin-singlefile": "^0.5.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /components/switch/binary_sensor/__init__.py: -------------------------------------------------------------------------------- 1 | import esphome.codegen as cg 2 | from esphome.components import binary_sensor 3 | import esphome.config_validation as cv 4 | from esphome.const import CONF_SOURCE_ID 5 | 6 | from .. import Switch, switch_ns 7 | 8 | CODEOWNERS = ["@ssieb"] 9 | 10 | SwitchBinarySensor = switch_ns.class_( 11 | "SwitchBinarySensor", binary_sensor.BinarySensor, cg.Component 12 | ) 13 | 14 | 15 | CONFIG_SCHEMA = ( 16 | binary_sensor.binary_sensor_schema(SwitchBinarySensor) 17 | .extend( 18 | { 19 | cv.Required(CONF_SOURCE_ID): cv.use_id(Switch), 20 | } 21 | ) 22 | .extend(cv.COMPONENT_SCHEMA) 23 | ) 24 | 25 | 26 | async def to_code(config): 27 | var = await binary_sensor.new_binary_sensor(config) 28 | await cg.register_component(var, config) 29 | 30 | source = await cg.get_variable(config[CONF_SOURCE_ID]) 31 | cg.add(var.set_source(source)) 32 | -------------------------------------------------------------------------------- /components/template/output/template_output.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/automation.h" 4 | #include "esphome/components/output/binary_output.h" 5 | #include "esphome/components/output/float_output.h" 6 | 7 | namespace esphome::template_ { 8 | 9 | class TemplateBinaryOutput final : public output::BinaryOutput { 10 | public: 11 | Trigger *get_trigger() const { return trigger_; } 12 | 13 | protected: 14 | void write_state(bool state) override { this->trigger_->trigger(state); } 15 | 16 | Trigger *trigger_ = new Trigger(); 17 | }; 18 | 19 | class TemplateFloatOutput final : public output::FloatOutput { 20 | public: 21 | Trigger *get_trigger() const { return trigger_; } 22 | 23 | protected: 24 | void write_state(float state) override { this->trigger_->trigger(state); } 25 | 26 | Trigger *trigger_ = new Trigger(); 27 | }; 28 | 29 | } // namespace esphome::template_ 30 | -------------------------------------------------------------------------------- /components/light/light_effect.cpp: -------------------------------------------------------------------------------- 1 | #include "light_effect.h" 2 | #include "light_state.h" 3 | 4 | namespace esphome::light { 5 | 6 | uint32_t LightEffect::get_index() const { 7 | if (this->state_ == nullptr) { 8 | return 0; 9 | } 10 | return this->get_index_in_parent_(); 11 | } 12 | 13 | bool LightEffect::is_active() const { 14 | if (this->state_ == nullptr) { 15 | return false; 16 | } 17 | return this->get_index() != 0 && this->state_->get_current_effect_index() == this->get_index(); 18 | } 19 | 20 | uint32_t LightEffect::get_index_in_parent_() const { 21 | if (this->state_ == nullptr) { 22 | return 0; 23 | } 24 | 25 | const auto &effects = this->state_->get_effects(); 26 | for (size_t i = 0; i < effects.size(); i++) { 27 | if (effects[i] == this) { 28 | return i + 1; // Effects are 1-indexed in the API 29 | } 30 | } 31 | return 0; // Not found 32 | } 33 | 34 | } // namespace esphome::light 35 | -------------------------------------------------------------------------------- /components/monochromatic/monochromatic_light_output.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/component.h" 4 | #include "esphome/components/output/float_output.h" 5 | #include "esphome/components/light/light_output.h" 6 | 7 | namespace esphome { 8 | namespace monochromatic { 9 | 10 | class MonochromaticLightOutput : public light::LightOutput { 11 | public: 12 | void set_output(output::FloatOutput *output) { output_ = output; } 13 | light::LightTraits get_traits() override { 14 | auto traits = light::LightTraits(); 15 | traits.set_supported_color_modes({light::ColorMode::BRIGHTNESS}); 16 | return traits; 17 | } 18 | void write_state(light::LightState *state) override { 19 | float bright; 20 | state->current_values_as_brightness(&bright); 21 | this->output_->set_level(bright); 22 | } 23 | 24 | protected: 25 | output::FloatOutput *output_; 26 | }; 27 | 28 | } // namespace monochromatic 29 | } // namespace esphome 30 | -------------------------------------------------------------------------------- /components/safe_mode/switch/safe_mode_switch.cpp: -------------------------------------------------------------------------------- 1 | #include "safe_mode_switch.h" 2 | #include "esphome/core/application.h" 3 | #include "esphome/core/hal.h" 4 | #include "esphome/core/log.h" 5 | 6 | namespace esphome { 7 | namespace safe_mode { 8 | 9 | static const char *const TAG = "safe_mode.switch"; 10 | 11 | void SafeModeSwitch::set_safe_mode(SafeModeComponent *safe_mode_component) { 12 | this->safe_mode_component_ = safe_mode_component; 13 | } 14 | 15 | void SafeModeSwitch::write_state(bool state) { 16 | // Acknowledge 17 | this->publish_state(false); 18 | 19 | if (state) { 20 | ESP_LOGI(TAG, "Restarting in safe mode"); 21 | this->safe_mode_component_->set_safe_mode_pending(true); 22 | 23 | // Let MQTT settle a bit 24 | delay(100); // NOLINT 25 | App.safe_reboot(); 26 | } 27 | } 28 | 29 | void SafeModeSwitch::dump_config() { LOG_SWITCH("", "Safe Mode Switch", this); } 30 | 31 | } // namespace safe_mode 32 | } // namespace esphome 33 | -------------------------------------------------------------------------------- /esphome-webserver/packages/captive-portal/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import gzipPlugin from "rollup-plugin-gzip"; 3 | import { viteSingleFile } from "vite-plugin-singlefile"; 4 | 5 | import minifyHTML from "rollup-plugin-minify-html-template-literals"; 6 | import { minifyHtml as ViteMinifyHtml } from "vite-plugin-html"; 7 | 8 | export default defineConfig({ 9 | clearScreen: false, 10 | plugins: [ 11 | viteSingleFile(), 12 | { ...minifyHTML(), enforce: "pre", apply: "build" }, 13 | ViteMinifyHtml(), 14 | { 15 | ...gzipPlugin({ filter: /\.(html)$/ }), 16 | enforce: "post", 17 | apply: "build", 18 | }, 19 | ], 20 | css: { 21 | postcss: {}, 22 | }, 23 | build: { 24 | brotliSize: false, 25 | cssCodeSplit: false, 26 | outDir: "../../_static/captive_portal", 27 | assetsInlineLimit: 100000000, 28 | polyfillModulePreload: false, 29 | }, 30 | server: { 31 | open: "/", // auto open browser 32 | }, 33 | }); 34 | -------------------------------------------------------------------------------- /components/light/esp_hsv_color.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/color.h" 4 | #include "esphome/core/helpers.h" 5 | 6 | namespace esphome::light { 7 | 8 | struct ESPHSVColor { 9 | union { 10 | struct { 11 | union { 12 | uint8_t hue; 13 | uint8_t h; 14 | }; 15 | union { 16 | uint8_t saturation; 17 | uint8_t s; 18 | }; 19 | union { 20 | uint8_t value; 21 | uint8_t v; 22 | }; 23 | }; 24 | uint8_t raw[3]; 25 | }; 26 | inline ESPHSVColor() ESPHOME_ALWAYS_INLINE : h(0), s(0), v(0) { // NOLINT 27 | } 28 | inline ESPHSVColor(uint8_t hue, uint8_t saturation, uint8_t value) ESPHOME_ALWAYS_INLINE : hue(hue), 29 | saturation(saturation), 30 | value(value) {} 31 | Color to_rgb() const; 32 | }; 33 | 34 | } // namespace esphome::light 35 | -------------------------------------------------------------------------------- /components/safe_mode/switch/__init__.py: -------------------------------------------------------------------------------- 1 | import esphome.codegen as cg 2 | from esphome.components import switch 3 | import esphome.config_validation as cv 4 | from esphome.const import CONF_SAFE_MODE, ENTITY_CATEGORY_CONFIG, ICON_RESTART_ALERT 5 | 6 | from .. import SafeModeComponent, safe_mode_ns 7 | 8 | DEPENDENCIES = ["safe_mode"] 9 | 10 | SafeModeSwitch = safe_mode_ns.class_("SafeModeSwitch", switch.Switch, cg.Component) 11 | 12 | CONFIG_SCHEMA = ( 13 | switch.switch_schema( 14 | SafeModeSwitch, 15 | block_inverted=True, 16 | entity_category=ENTITY_CATEGORY_CONFIG, 17 | icon=ICON_RESTART_ALERT, 18 | ) 19 | .extend({cv.GenerateID(CONF_SAFE_MODE): cv.use_id(SafeModeComponent)}) 20 | .extend(cv.COMPONENT_SCHEMA) 21 | ) 22 | 23 | 24 | async def to_code(config): 25 | var = await switch.new_switch(config) 26 | await cg.register_component(var, config) 27 | 28 | safe_mode_component = await cg.get_variable(config[CONF_SAFE_MODE]) 29 | cg.add(var.set_safe_mode(safe_mode_component)) 30 | -------------------------------------------------------------------------------- /components/ddp/ddp_addressable_light_effect.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef USE_ARDUINO 4 | 5 | #include "esphome/core/component.h" 6 | #include "esphome/components/light/addressable_light_effect.h" 7 | #include "ddp_light_effect_base.h" 8 | 9 | namespace esphome { 10 | namespace ddp { 11 | 12 | class DDPAddressableLightEffect : public DDPLightEffectBase, public light::AddressableLightEffect { 13 | public: 14 | DDPAddressableLightEffect(const char *name); 15 | 16 | const char *get_name() override; 17 | 18 | void start() override; 19 | void stop() override; 20 | 21 | void apply(light::AddressableLight &it, const Color ¤t_color) override; 22 | 23 | protected: 24 | uint16_t process_(const uint8_t *payload, uint16_t size, uint16_t used) override; 25 | 26 | float scan_packet_and_return_multiplier_(const uint8_t *payload, uint16_t start, uint16_t end); 27 | float multiplier_from_max_val_(uint8_t max_val); 28 | void set_max_brightness_(); 29 | 30 | }; 31 | 32 | } // namespace ddp 33 | } // namespace esphome 34 | 35 | #endif // USE_ARDUINO 36 | -------------------------------------------------------------------------------- /esphome-webserver/packages/v2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@esphome-webserver/v2", 3 | "version": "2.0.0", 4 | "scripts": { 5 | "start": "npm run dev", 6 | "dev": "vite", 7 | "xbuild": "vite build --emptyOutDir", 8 | "build": "vite build --emptyOutDir && npm run deploy", 9 | "serve": "vite preview", 10 | "deploy": "bash -c '../../scripts/make_header.sh ../../_static/v2 server_index_v2.h web_server 2'" 11 | }, 12 | "dependencies": { 13 | "http-proxy-middleware": "^2.0.1", 14 | "lit": "^2.0.2" 15 | }, 16 | "devDependencies": { 17 | "rollup-plugin-copy": "^3.4.0", 18 | "rollup-plugin-gzip": "^2.5.0", 19 | "rollup-plugin-minify-html-template-literals": "^1.2.0", 20 | "@rollup/plugin-node-resolve": "^13.0.6", 21 | "@rollup/plugin-replace": "^3.0.0", 22 | "@types/node": "^15.12.1", 23 | "rollup-plugin-strip-banner": "^2.0.0", 24 | "typescript": "^4.1.3", 25 | "vite": "^2.3.6", 26 | "vite-plugin-html": "^2.1.1", 27 | "vite-plugin-package-version": "^1.0.2", 28 | "vite-plugin-singlefile": "^0.5.1" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /components/template/fan/template_fan.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/component.h" 4 | #include "esphome/components/fan/fan.h" 5 | 6 | namespace esphome::template_ { 7 | 8 | class TemplateFan final : public Component, public fan::Fan { 9 | public: 10 | TemplateFan() {} 11 | void setup() override; 12 | void dump_config() override; 13 | void set_has_direction(bool has_direction) { this->has_direction_ = has_direction; } 14 | void set_has_oscillating(bool has_oscillating) { this->has_oscillating_ = has_oscillating; } 15 | void set_speed_count(int count) { this->speed_count_ = count; } 16 | void set_preset_modes(std::initializer_list presets) { this->preset_modes_ = presets; } 17 | fan::FanTraits get_traits() override { return this->traits_; } 18 | 19 | protected: 20 | void control(const fan::FanCall &call) override; 21 | 22 | bool has_oscillating_{false}; 23 | bool has_direction_{false}; 24 | int speed_count_{0}; 25 | fan::FanTraits traits_; 26 | std::vector preset_modes_{}; 27 | }; 28 | 29 | } // namespace esphome::template_ 30 | -------------------------------------------------------------------------------- /esphome-webserver/packages/captive-portal/README.md: -------------------------------------------------------------------------------- 1 | # captive-portal 2 | Source code to build esphome captive portal. Output is `captive_index.h` file to be included by the Captive Portal Component https://esphome.io/components/captive_portal.html 3 | 4 | ### Features 5 | 6 | - All assets (css, svg and js) are inlined and served from index.html 7 | - index.html is gzipped, and stored in flash compressed saving ~1K of flash memory from previous version 8 | - ssid scan result is returned via `/config.json` api request 9 | 10 | 11 | development 12 | =========== 13 | 14 | ``` 15 | git clone https://github.com/esphome/esphome-webserver.git 16 | cd captive-portal 17 | pnpm install 18 | ``` 19 | 20 | `npm run start` 21 | Starts a dev server on http://localhost:3000 22 | 23 | build 24 | ===== 25 | `npm run build` 26 | The build files are copied to `dist` folder. `captive_index.h` is built to be deployed to https://github.com/esphome/esphome/tree/dev/esphome/components/captive_portal 27 | 28 | 29 | serve 30 | ===== 31 | `npm run server` 32 | Starts a production test server on http://localhost:5001 33 | -------------------------------------------------------------------------------- /components/gpio/switch/gpio_switch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/component.h" 4 | #include "esphome/core/hal.h" 5 | #include "esphome/core/helpers.h" 6 | #include "esphome/components/switch/switch.h" 7 | 8 | namespace esphome { 9 | namespace gpio { 10 | 11 | class GPIOSwitch : public switch_::Switch, public Component { 12 | public: 13 | void set_pin(GPIOPin *pin) { pin_ = pin; } 14 | 15 | // ========== INTERNAL METHODS ========== 16 | // (In most use cases you won't need these) 17 | float get_setup_priority() const override; 18 | 19 | void setup() override; 20 | void dump_config() override; 21 | void set_interlock(const std::initializer_list &interlock); 22 | void set_interlock_wait_time(uint32_t interlock_wait_time) { interlock_wait_time_ = interlock_wait_time; } 23 | 24 | bool is_setup(); 25 | 26 | protected: 27 | void write_state(bool state) override; 28 | 29 | GPIOPin *pin_; 30 | FixedVector interlock_; 31 | uint32_t interlock_wait_time_{0}; 32 | 33 | bool is_setup_{false}; 34 | }; 35 | 36 | } // namespace gpio 37 | } // namespace esphome 38 | -------------------------------------------------------------------------------- /esphome-webserver/packages/v2/src/css/button.ts: -------------------------------------------------------------------------------- 1 | import { css } from "lit"; 2 | 3 | export default css` 4 | button, 5 | .btn { 6 | cursor: pointer; 7 | border-radius: 4px; 8 | background-color: inherit; 9 | background-image: linear-gradient( 10 | 0deg, 11 | rgba(127, 127, 127, 0.5) 0%, 12 | rgba(127, 127, 127, 0.5) 100% 13 | ); 14 | color: inherit; 15 | border: 1px solid rgba(127, 127, 127, 0.5); 16 | padding: 2px; 17 | } 18 | 19 | button:active, 20 | .btn:active { 21 | background-image: linear-gradient( 22 | 0deg, 23 | rgba(127, 127, 127, 0.8) 0%, 24 | rgba(127, 127, 127, 0.2) 100% 25 | ); 26 | transition-duration: 1s; 27 | } 28 | 29 | button:hover, 30 | .btn:hover { 31 | background-image: linear-gradient( 32 | 0deg, 33 | rgba(127, 127, 127, 0.2) 0%, 34 | rgba(127, 127, 127, 0.8) 100% 35 | ); 36 | transition-duration: 1s; 37 | } 38 | 39 | .rnd { 40 | border-radius: 1rem; 41 | height: 2rem; 42 | width: 2rem; 43 | font-weight: 500; 44 | font-size: 1.2rem; 45 | } 46 | `; 47 | -------------------------------------------------------------------------------- /components/safe_mode/button/__init__.py: -------------------------------------------------------------------------------- 1 | import esphome.codegen as cg 2 | from esphome.components import button 3 | import esphome.config_validation as cv 4 | from esphome.const import ( 5 | CONF_SAFE_MODE, 6 | DEVICE_CLASS_RESTART, 7 | ENTITY_CATEGORY_CONFIG, 8 | ICON_RESTART_ALERT, 9 | ) 10 | 11 | from .. import SafeModeComponent, safe_mode_ns 12 | 13 | DEPENDENCIES = ["safe_mode"] 14 | 15 | SafeModeButton = safe_mode_ns.class_("SafeModeButton", button.Button, cg.Component) 16 | 17 | CONFIG_SCHEMA = ( 18 | button.button_schema( 19 | SafeModeButton, 20 | device_class=DEVICE_CLASS_RESTART, 21 | entity_category=ENTITY_CATEGORY_CONFIG, 22 | icon=ICON_RESTART_ALERT, 23 | ) 24 | .extend({cv.GenerateID(CONF_SAFE_MODE): cv.use_id(SafeModeComponent)}) 25 | .extend(cv.COMPONENT_SCHEMA) 26 | ) 27 | 28 | 29 | async def to_code(config): 30 | var = await button.new_button(config) 31 | await cg.register_component(var, config) 32 | 33 | safe_mode_component = await cg.get_variable(config[CONF_SAFE_MODE]) 34 | cg.add(var.set_safe_mode(safe_mode_component)) 35 | -------------------------------------------------------------------------------- /esphome-webserver/scripts/make_header.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cat <./$1/$2 3 | #pragma once 4 | // Generated from https://github.com/esphome/esphome-webserver 5 | 6 | EOT 7 | 8 | if [ -n "$4" ]; then 9 | echo "#ifdef USE_WEBSERVER_LOCAL" >>./$1/$2 10 | echo "#if USE_WEBSERVER_VERSION == $4" >>./$1/$2 11 | echo "" >>./$1/$2 12 | fi 13 | 14 | cat <>./$1/$2 15 | #include "esphome/core/hal.h" 16 | 17 | namespace esphome { 18 | namespace $3 { 19 | 20 | EOT 21 | echo "const uint8_t INDEX_GZ[] PROGMEM = {" >>./$1/$2 22 | xxd -cols 19 -i $1/index.html.gz | sed -e '2,$!d' -e 's/^/ /' -e '$d' | sed -e '$d' | sed -e '$s/$/};/' >>./$1/$2 23 | cat <>./$1/$2 24 | 25 | } // namespace $3 26 | } // namespace esphome 27 | EOT 28 | 29 | if [ -n "$4" ]; then 30 | echo "" >>./$1/$2 31 | echo "#endif" >>./$1/$2 32 | echo "#endif" >>./$1/$2 33 | 34 | if [ "$4" = "3" ]; then 35 | cp ../../_static/v2/server_index_v2.h ../../../components/web_server/ 36 | fi 37 | fi 38 | 39 | 40 | if [ "$3" = "captive_portal" ]; then 41 | cp ../../_static/captive_portal/captive_index.h ../../../components/captive_portal/ 42 | fi -------------------------------------------------------------------------------- /components/esp8266/helpers.cpp: -------------------------------------------------------------------------------- 1 | #include "esphome/core/helpers.h" 2 | 3 | #ifdef USE_ESP8266 4 | 5 | #include 6 | #include 7 | // for xt_rsil()/xt_wsr_ps() 8 | #include 9 | 10 | namespace esphome { 11 | 12 | uint32_t random_uint32() { return os_random(); } 13 | bool random_bytes(uint8_t *data, size_t len) { return os_get_random(data, len) == 0; } 14 | 15 | // ESP8266 doesn't have mutexes, but that shouldn't be an issue as it's single-core and non-preemptive OS. 16 | Mutex::Mutex() {} 17 | Mutex::~Mutex() {} 18 | void Mutex::lock() {} 19 | bool Mutex::try_lock() { return true; } 20 | void Mutex::unlock() {} 21 | 22 | IRAM_ATTR InterruptLock::InterruptLock() { state_ = xt_rsil(15); } 23 | IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(state_); } 24 | 25 | // ESP8266 doesn't support lwIP core locking, so this is a no-op 26 | LwIPLock::LwIPLock() {} 27 | LwIPLock::~LwIPLock() {} 28 | 29 | void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter) 30 | wifi_get_macaddr(STATION_IF, mac); 31 | } 32 | 33 | } // namespace esphome 34 | 35 | #endif // USE_ESP8266 36 | -------------------------------------------------------------------------------- /components/light/light_output.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/component.h" 4 | #include "light_traits.h" 5 | #include "light_state.h" 6 | #include "light_transformer.h" 7 | 8 | namespace esphome::light { 9 | 10 | /// Interface to write LightStates to hardware. 11 | class LightOutput { 12 | public: 13 | /// Return the LightTraits of this LightOutput. 14 | virtual LightTraits get_traits() = 0; 15 | 16 | /// Return the default transformer used for transitions. 17 | virtual std::unique_ptr create_default_transition(); 18 | 19 | virtual void setup_state(LightState *state) {} 20 | 21 | /// Called on every update of the current values of the associated LightState, 22 | /// can optionally be used to do processing of this change. 23 | virtual void update_state(LightState *state) {} 24 | 25 | /// Called from loop() every time the light state has changed, and should 26 | /// should write the new state to hardware. Every call to write_state() is 27 | /// preceded by (at least) one call to update_state(). 28 | virtual void write_state(LightState *state) = 0; 29 | }; 30 | 31 | } // namespace esphome::light 32 | -------------------------------------------------------------------------------- /esphome-webserver/packages/v3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@esphome-webserver/v3", 3 | "version": "3.0.0", 4 | "scripts": { 5 | "start": "npm run dev", 6 | "dev": "vite", 7 | "xbuild": "vite build --emptyOutDir", 8 | "build": "vite build --emptyOutDir && npm run deploy", 9 | "serve": "vite preview", 10 | "deploy": "bash -c '../../scripts/make_header.sh ../../_static/v3 server_index_v3.h web_server 3'" 11 | }, 12 | "dependencies": { 13 | "chart.js": "^4.4.1", 14 | "http-proxy-middleware": "^2.0.1", 15 | "iconify-icon": "^1.0.8", 16 | "lit": "^2.0.2" 17 | }, 18 | "devDependencies": { 19 | "rollup-plugin-copy": "^3.4.0", 20 | "rollup-plugin-gzip": "^2.5.0", 21 | "rollup-plugin-minify-html-template-literals": "^1.2.0", 22 | "@rollup/plugin-node-resolve": "^13.0.6", 23 | "@rollup/plugin-replace": "^3.0.0", 24 | "@types/node": "^15.12.1", 25 | "rollup-plugin-strip-banner": "^2.0.0", 26 | "typescript": "^4.1.3", 27 | "vite": "^2.3.6", 28 | "vite-plugin-html": "^2.1.1", 29 | "vite-plugin-package-version": "^1.0.2", 30 | "vite-plugin-singlefile": "^0.5.1" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /components/ddp/ddp_light_effect_base.cpp: -------------------------------------------------------------------------------- 1 | #ifdef USE_ARDUINO 2 | 3 | #include "ddp.h" 4 | #include "ddp_light_effect_base.h" 5 | 6 | namespace esphome { 7 | namespace ddp { 8 | 9 | DDPLightEffectBase::DDPLightEffectBase() {} 10 | 11 | void DDPLightEffectBase::start() { 12 | if (this->ddp_) { 13 | this->ddp_->add_effect(this); 14 | } 15 | } 16 | 17 | void DDPLightEffectBase::stop() { 18 | if (this->ddp_) { 19 | this->ddp_->remove_effect(this); 20 | } 21 | } 22 | 23 | // returns true if this effect is timed out 24 | // next_packet_will_be_first_ variable keeps it from timing out multiple times 25 | bool DDPLightEffectBase::timeout_check() { 26 | 27 | // don't timeout if timeout is disabled 28 | if ( this->timeout_ == 0) { return false; } 29 | 30 | // don't timeout if no ddp stream was ever started 31 | if ( this->next_packet_will_be_first_ ) { return false; } 32 | 33 | // don't timeout if timeout hasn't been reached 34 | if ( (millis() - this->last_ddp_time_ms_) <= this->timeout_ ) { return false; } 35 | 36 | return true; 37 | 38 | } 39 | 40 | } // namespace ddp 41 | } // namespace esphome 42 | 43 | #endif // USE_ARDUINO 44 | -------------------------------------------------------------------------------- /esphome-webserver/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 wilberforce 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /components/rgb/rgb_light_output.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/component.h" 4 | #include "esphome/components/output/float_output.h" 5 | #include "esphome/components/light/light_output.h" 6 | 7 | namespace esphome { 8 | namespace rgb { 9 | 10 | class RGBLightOutput : public light::LightOutput { 11 | public: 12 | void set_red(output::FloatOutput *red) { red_ = red; } 13 | void set_green(output::FloatOutput *green) { green_ = green; } 14 | void set_blue(output::FloatOutput *blue) { blue_ = blue; } 15 | 16 | light::LightTraits get_traits() override { 17 | auto traits = light::LightTraits(); 18 | traits.set_supported_color_modes({light::ColorMode::RGB}); 19 | return traits; 20 | } 21 | void write_state(light::LightState *state) override { 22 | float red, green, blue; 23 | state->current_values_as_rgb(&red, &green, &blue, false); 24 | this->red_->set_level(red); 25 | this->green_->set_level(green); 26 | this->blue_->set_level(blue); 27 | } 28 | 29 | protected: 30 | output::FloatOutput *red_; 31 | output::FloatOutput *green_; 32 | output::FloatOutput *blue_; 33 | }; 34 | 35 | } // namespace rgb 36 | } // namespace esphome 37 | -------------------------------------------------------------------------------- /components/template/lock/template_lock.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/component.h" 4 | #include "esphome/core/automation.h" 5 | #include "esphome/core/template_lambda.h" 6 | #include "esphome/components/lock/lock.h" 7 | 8 | namespace esphome::template_ { 9 | 10 | class TemplateLock final : public lock::Lock, public Component { 11 | public: 12 | TemplateLock(); 13 | 14 | void setup() override; 15 | void dump_config() override; 16 | 17 | template void set_state_lambda(F &&f) { this->f_.set(std::forward(f)); } 18 | Trigger<> *get_lock_trigger() const; 19 | Trigger<> *get_unlock_trigger() const; 20 | Trigger<> *get_open_trigger() const; 21 | void set_optimistic(bool optimistic); 22 | void loop() override; 23 | 24 | float get_setup_priority() const override; 25 | 26 | protected: 27 | void control(const lock::LockCall &call) override; 28 | void open_latch() override; 29 | 30 | TemplateLambda f_; 31 | bool optimistic_{false}; 32 | Trigger<> *lock_trigger_; 33 | Trigger<> *unlock_trigger_; 34 | Trigger<> *open_trigger_; 35 | Trigger<> *prev_trigger_{nullptr}; 36 | }; 37 | 38 | } // namespace esphome::template_ 39 | -------------------------------------------------------------------------------- /components/ddp/ddp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef USE_ARDUINO 4 | 5 | #include "esphome/core/component.h" 6 | 7 | #ifdef USE_ESP32 8 | #include 9 | #endif 10 | 11 | #ifdef USE_ESP8266 12 | #include 13 | #include 14 | #endif 15 | 16 | #ifdef USE_LIBRETINY 17 | #include 18 | #include 19 | #endif 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | namespace esphome { 27 | namespace ddp { 28 | 29 | class DDPLightEffectBase; 30 | 31 | class DDPComponent : public esphome::Component { 32 | public: 33 | DDPComponent(); 34 | ~DDPComponent(); 35 | 36 | void setup() override; 37 | void loop() override; 38 | float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } 39 | 40 | void add_effect(DDPLightEffectBase *light_effect); 41 | void remove_effect(DDPLightEffectBase *light_effect); 42 | 43 | protected: 44 | std::unique_ptr udp_; 45 | std::set light_effects_; 46 | 47 | bool process_(const uint8_t *payload, uint16_t size); 48 | }; 49 | 50 | } // namespace ddp 51 | } // namespace esphome 52 | 53 | #endif // USE_ARDUINO 54 | -------------------------------------------------------------------------------- /components/gpio/one_wire/gpio_one_wire.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/component.h" 4 | #include "esphome/core/hal.h" 5 | #include "esphome/components/one_wire/one_wire.h" 6 | 7 | namespace esphome { 8 | namespace gpio { 9 | 10 | class GPIOOneWireBus : public one_wire::OneWireBus, public Component { 11 | public: 12 | void setup() override; 13 | void dump_config() override; 14 | float get_setup_priority() const override { return setup_priority::BUS; } 15 | 16 | void set_pin(InternalGPIOPin *pin) { 17 | this->t_pin_ = pin; 18 | this->pin_ = pin->to_isr(); 19 | } 20 | 21 | void write8(uint8_t val) override; 22 | void write64(uint64_t val) override; 23 | uint8_t read8() override; 24 | uint64_t read64() override; 25 | 26 | protected: 27 | InternalGPIOPin *t_pin_; 28 | ISRInternalGPIOPin pin_; 29 | uint8_t last_discrepancy_{0}; 30 | bool last_device_flag_{false}; 31 | uint64_t address_; 32 | 33 | int reset_int() override; 34 | void reset_search() override; 35 | uint64_t search_int() override; 36 | void write_bit_(bool bit); 37 | bool read_bit_(); 38 | bool read_bit_(uint32_t *t); 39 | }; 40 | 41 | } // namespace gpio 42 | } // namespace esphome 43 | -------------------------------------------------------------------------------- /components/esp8266/gpio.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef USE_ESP8266 4 | 5 | #include "esphome/core/hal.h" 6 | #include 7 | 8 | namespace esphome::esp8266 { 9 | 10 | class ESP8266GPIOPin : public InternalGPIOPin { 11 | public: 12 | void set_pin(uint8_t pin) { pin_ = pin; } 13 | void set_inverted(bool inverted) { inverted_ = inverted; } 14 | void set_flags(gpio::Flags flags) { flags_ = flags; } 15 | 16 | void setup() override { pin_mode(flags_); } 17 | void pin_mode(gpio::Flags flags) override; 18 | bool digital_read() override; 19 | void digital_write(bool value) override; 20 | std::string dump_summary() const override; 21 | void detach_interrupt() const override; 22 | ISRInternalGPIOPin to_isr() const override; 23 | uint8_t get_pin() const override { return pin_; } 24 | gpio::Flags get_flags() const override { return flags_; } 25 | bool is_inverted() const override { return inverted_; } 26 | 27 | protected: 28 | void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; 29 | 30 | uint8_t pin_; 31 | bool inverted_{}; 32 | gpio::Flags flags_{}; 33 | }; 34 | 35 | } // namespace esphome::esp8266 36 | 37 | #endif // USE_ESP8266 38 | -------------------------------------------------------------------------------- /components/template/switch/template_switch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/component.h" 4 | #include "esphome/core/automation.h" 5 | #include "esphome/core/template_lambda.h" 6 | #include "esphome/components/switch/switch.h" 7 | 8 | namespace esphome { 9 | namespace template_ { 10 | 11 | class TemplateSwitch final : public switch_::Switch, public Component { 12 | public: 13 | TemplateSwitch(); 14 | 15 | void setup() override; 16 | void dump_config() override; 17 | 18 | template void set_state_lambda(F &&f) { this->f_.set(std::forward(f)); } 19 | Trigger<> *get_turn_on_trigger() const; 20 | Trigger<> *get_turn_off_trigger() const; 21 | void set_optimistic(bool optimistic); 22 | void set_assumed_state(bool assumed_state); 23 | void loop() override; 24 | 25 | float get_setup_priority() const override; 26 | 27 | protected: 28 | bool assumed_state() override; 29 | 30 | void write_state(bool state) override; 31 | 32 | TemplateLambda f_; 33 | bool optimistic_{false}; 34 | bool assumed_state_{false}; 35 | Trigger<> *turn_on_trigger_; 36 | Trigger<> *turn_off_trigger_; 37 | Trigger<> *prev_trigger_{nullptr}; 38 | }; 39 | 40 | } // namespace template_ 41 | } // namespace esphome 42 | -------------------------------------------------------------------------------- /components/template/fan/template_fan.cpp: -------------------------------------------------------------------------------- 1 | #include "template_fan.h" 2 | #include "esphome/core/log.h" 3 | 4 | namespace esphome::template_ { 5 | 6 | static const char *const TAG = "template.fan"; 7 | 8 | void TemplateFan::setup() { 9 | auto restore = this->restore_state_(); 10 | if (restore.has_value()) { 11 | restore->apply(*this); 12 | } 13 | 14 | // Construct traits 15 | this->traits_ = 16 | fan::FanTraits(this->has_oscillating_, this->speed_count_ > 0, this->has_direction_, this->speed_count_); 17 | this->traits_.set_supported_preset_modes(this->preset_modes_); 18 | } 19 | 20 | void TemplateFan::dump_config() { LOG_FAN("", "Template Fan", this); } 21 | 22 | void TemplateFan::control(const fan::FanCall &call) { 23 | if (call.get_state().has_value()) 24 | this->state = *call.get_state(); 25 | if (call.get_speed().has_value() && (this->speed_count_ > 0)) 26 | this->speed = *call.get_speed(); 27 | if (call.get_oscillating().has_value() && this->has_oscillating_) 28 | this->oscillating = *call.get_oscillating(); 29 | if (call.get_direction().has_value() && this->has_direction_) 30 | this->direction = *call.get_direction(); 31 | this->set_preset_mode_(call.get_preset_mode()); 32 | 33 | this->publish_state(); 34 | } 35 | 36 | } // namespace esphome::template_ 37 | -------------------------------------------------------------------------------- /esphome-webserver/packages/v1/src/webserver-v1.min.js: -------------------------------------------------------------------------------- 1 | const source=new EventSource("/events");source.addEventListener("log",function(t){const e=document.getElementById("log");let n=[["","e"],["","w"],["","i"],["","c"],["","d"],["","v"]],o="";for(const e of n)t.data.startsWith(e[0])&&(o=e[1]);""==o&&(e.innerHTML+=t.data+"\n"),e.innerHTML+=''+t.data.substr(7,t.data.length-11)+"\n"}),actions=[["switch",["toggle"]],["light",["toggle"]],["fan",["toggle"]],["cover",["open","close"]],["button",["press"]],["lock",["lock","unlock","open"]]],multi_actions=[["select","option"],["number","value"]],source.addEventListener("state",function(t){const e=JSON.parse(t.data);document.getElementById(e.id).children[1].innerText=e.state});const states=document.getElementById("states");let row,i=0;for(;row=states.rows[i];i++)if(row.children[2].children.length){for(const t of actions)if(row.classList.contains(t[0])){let e=row.id.substr(t[0].length+1);for(let n=0;nf_.has_value()) 10 | return; 11 | std::string value = this->initial_value_; 12 | if (!this->pref_) { 13 | ESP_LOGD(TAG, "State from initial: %s", value.c_str()); 14 | } else { 15 | uint32_t key = this->get_preference_hash(); 16 | key += this->traits.get_min_length() << 2; 17 | key += this->traits.get_max_length() << 4; 18 | key += fnv1_hash(this->traits.get_pattern_c_str()) << 6; 19 | this->pref_->setup(key, value); 20 | } 21 | if (!value.empty()) 22 | this->publish_state(value); 23 | } 24 | 25 | void TemplateText::update() { 26 | if (!this->f_.has_value()) 27 | return; 28 | 29 | auto val = this->f_(); 30 | if (val.has_value()) { 31 | this->publish_state(*val); 32 | } 33 | } 34 | 35 | void TemplateText::control(const std::string &value) { 36 | this->set_trigger_->trigger(value); 37 | 38 | if (this->optimistic_) 39 | this->publish_state(value); 40 | 41 | if (this->pref_) { 42 | if (!this->pref_->save(value)) { 43 | ESP_LOGW(TAG, "Text value too long to save"); 44 | } 45 | } 46 | } 47 | void TemplateText::dump_config() { 48 | LOG_TEXT("", "Template Text Input", this); 49 | ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_)); 50 | LOG_UPDATE_INTERVAL(this); 51 | } 52 | 53 | } // namespace esphome::template_ 54 | -------------------------------------------------------------------------------- /components/template/datetime/template_date.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/defines.h" 4 | 5 | #ifdef USE_DATETIME_DATE 6 | 7 | #include "esphome/components/datetime/date_entity.h" 8 | #include "esphome/core/automation.h" 9 | #include "esphome/core/component.h" 10 | #include "esphome/core/preferences.h" 11 | #include "esphome/core/time.h" 12 | #include "esphome/core/template_lambda.h" 13 | 14 | namespace esphome::template_ { 15 | 16 | class TemplateDate final : public datetime::DateEntity, public PollingComponent { 17 | public: 18 | template void set_template(F &&f) { this->f_.set(std::forward(f)); } 19 | 20 | void setup() override; 21 | void update() override; 22 | void dump_config() override; 23 | float get_setup_priority() const override { return setup_priority::HARDWARE; } 24 | 25 | Trigger *get_set_trigger() const { return this->set_trigger_; } 26 | void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } 27 | 28 | void set_initial_value(ESPTime initial_value) { this->initial_value_ = initial_value; } 29 | void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; } 30 | 31 | protected: 32 | void control(const datetime::DateCall &call) override; 33 | 34 | bool optimistic_{false}; 35 | ESPTime initial_value_{}; 36 | bool restore_value_{false}; 37 | Trigger *set_trigger_ = new Trigger(); 38 | TemplateLambda f_; 39 | 40 | ESPPreferenceObject pref_; 41 | }; 42 | 43 | } // namespace esphome::template_ 44 | 45 | #endif // USE_DATETIME_DATE 46 | -------------------------------------------------------------------------------- /components/template/datetime/template_time.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/defines.h" 4 | 5 | #ifdef USE_DATETIME_TIME 6 | 7 | #include "esphome/components/datetime/time_entity.h" 8 | #include "esphome/core/automation.h" 9 | #include "esphome/core/component.h" 10 | #include "esphome/core/preferences.h" 11 | #include "esphome/core/time.h" 12 | #include "esphome/core/template_lambda.h" 13 | 14 | namespace esphome::template_ { 15 | 16 | class TemplateTime final : public datetime::TimeEntity, public PollingComponent { 17 | public: 18 | template void set_template(F &&f) { this->f_.set(std::forward(f)); } 19 | 20 | void setup() override; 21 | void update() override; 22 | void dump_config() override; 23 | float get_setup_priority() const override { return setup_priority::HARDWARE; } 24 | 25 | Trigger *get_set_trigger() const { return this->set_trigger_; } 26 | void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } 27 | 28 | void set_initial_value(ESPTime initial_value) { this->initial_value_ = initial_value; } 29 | void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; } 30 | 31 | protected: 32 | void control(const datetime::TimeCall &call) override; 33 | 34 | bool optimistic_{false}; 35 | ESPTime initial_value_{}; 36 | bool restore_value_{false}; 37 | Trigger *set_trigger_ = new Trigger(); 38 | TemplateLambda f_; 39 | 40 | ESPPreferenceObject pref_; 41 | }; 42 | 43 | } // namespace esphome::template_ 44 | 45 | #endif // USE_DATETIME_TIME 46 | -------------------------------------------------------------------------------- /components/template/datetime/template_datetime.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/defines.h" 4 | 5 | #ifdef USE_DATETIME_DATETIME 6 | 7 | #include "esphome/components/datetime/datetime_entity.h" 8 | #include "esphome/core/automation.h" 9 | #include "esphome/core/component.h" 10 | #include "esphome/core/preferences.h" 11 | #include "esphome/core/time.h" 12 | #include "esphome/core/template_lambda.h" 13 | 14 | namespace esphome::template_ { 15 | 16 | class TemplateDateTime final : public datetime::DateTimeEntity, public PollingComponent { 17 | public: 18 | template void set_template(F &&f) { this->f_.set(std::forward(f)); } 19 | 20 | void setup() override; 21 | void update() override; 22 | void dump_config() override; 23 | float get_setup_priority() const override { return setup_priority::HARDWARE; } 24 | 25 | Trigger *get_set_trigger() const { return this->set_trigger_; } 26 | void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } 27 | 28 | void set_initial_value(ESPTime initial_value) { this->initial_value_ = initial_value; } 29 | void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; } 30 | 31 | protected: 32 | void control(const datetime::DateTimeCall &call) override; 33 | 34 | bool optimistic_{false}; 35 | ESPTime initial_value_{}; 36 | bool restore_value_{false}; 37 | Trigger *set_trigger_ = new Trigger(); 38 | TemplateLambda f_; 39 | 40 | ESPPreferenceObject pref_; 41 | }; 42 | 43 | } // namespace esphome::template_ 44 | 45 | #endif // USE_DATETIME_DATETIME 46 | -------------------------------------------------------------------------------- /components/ddp/ddp_light_effect_base.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef USE_ARDUINO 4 | 5 | #include "esphome/core/component.h" 6 | #include "esphome/components/light/light_effect.h" 7 | #include "esphome/components/light/light_output.h" 8 | 9 | namespace esphome { 10 | namespace ddp { 11 | 12 | class DDPComponent; 13 | 14 | enum DDPScalingMode { DDP_NO_SCALING = 0, 15 | DDP_SCALE_PIXEL = 1, 16 | DDP_SCALE_STRIP = 2, 17 | DDP_SCALE_PACKET = 3, 18 | DDP_SCALE_MULTIPLY = 4 }; 19 | 20 | class DDPLightEffectBase { 21 | public: 22 | DDPLightEffectBase(); 23 | 24 | virtual const char *get_name() = 0; 25 | 26 | virtual void start(); 27 | virtual void stop(); 28 | bool timeout_check(); 29 | 30 | void set_ddp(DDPComponent *ddp) { this->ddp_ = ddp; } 31 | void set_timeout(uint32_t timeout) {this->timeout_ = timeout;} 32 | void set_disable_gamma(bool disable_gamma) { this->disable_gamma_ = disable_gamma;} 33 | void set_scaling_mode(DDPScalingMode scaling_mode) { this->scaling_mode_ = scaling_mode;} 34 | 35 | protected: 36 | DDPComponent *ddp_{nullptr}; 37 | 38 | uint32_t timeout_{10000}; 39 | uint32_t last_ddp_time_ms_{0}; 40 | 41 | bool disable_gamma_{true}; 42 | float gamma_backup_{0.0}; 43 | bool next_packet_will_be_first_{true}; 44 | 45 | DDPScalingMode scaling_mode_{DDP_NO_SCALING}; 46 | 47 | virtual uint16_t process_(const uint8_t *payload, uint16_t size, uint16_t used) = 0; 48 | 49 | friend class DDPComponent; 50 | }; 51 | 52 | } // namespace ddp 53 | } // namespace esphome 54 | 55 | #endif // USE_ARDUINO 56 | -------------------------------------------------------------------------------- /components/light/light_traits.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "color_mode.h" 4 | #include "esphome/core/helpers.h" 5 | 6 | namespace esphome { 7 | 8 | #ifdef USE_API 9 | namespace api { 10 | class APIConnection; 11 | } // namespace api 12 | #endif 13 | 14 | namespace light { 15 | 16 | /// This class is used to represent the capabilities of a light. 17 | class LightTraits { 18 | public: 19 | LightTraits() = default; 20 | 21 | // Return by value to avoid dangling reference when get_traits() returns a temporary 22 | ColorModeMask get_supported_color_modes() const { return this->supported_color_modes_; } 23 | void set_supported_color_modes(ColorModeMask supported_color_modes) { 24 | this->supported_color_modes_ = supported_color_modes; 25 | } 26 | void set_supported_color_modes(std::initializer_list modes) { 27 | this->supported_color_modes_ = ColorModeMask(modes); 28 | } 29 | 30 | bool supports_color_mode(ColorMode color_mode) const { return this->supported_color_modes_.count(color_mode) > 0; } 31 | bool supports_color_capability(ColorCapability color_capability) const { 32 | return has_capability(this->supported_color_modes_, color_capability); 33 | } 34 | 35 | float get_min_mireds() const { return this->min_mireds_; } 36 | void set_min_mireds(float min_mireds) { this->min_mireds_ = min_mireds; } 37 | float get_max_mireds() const { return this->max_mireds_; } 38 | void set_max_mireds(float max_mireds) { this->max_mireds_ = max_mireds; } 39 | 40 | protected: 41 | float min_mireds_{0}; 42 | float max_mireds_{0}; 43 | ColorModeMask supported_color_modes_{}; 44 | }; 45 | 46 | } // namespace light 47 | } // namespace esphome 48 | -------------------------------------------------------------------------------- /components/template/sensor/__init__.py: -------------------------------------------------------------------------------- 1 | from esphome import automation 2 | import esphome.codegen as cg 3 | from esphome.components import sensor 4 | import esphome.config_validation as cv 5 | from esphome.const import CONF_ID, CONF_LAMBDA, CONF_STATE 6 | 7 | from .. import template_ns 8 | 9 | TemplateSensor = template_ns.class_( 10 | "TemplateSensor", sensor.Sensor, cg.PollingComponent 11 | ) 12 | 13 | CONFIG_SCHEMA = ( 14 | sensor.sensor_schema( 15 | TemplateSensor, 16 | accuracy_decimals=1, 17 | ) 18 | .extend( 19 | { 20 | cv.Optional(CONF_LAMBDA): cv.returning_lambda, 21 | } 22 | ) 23 | .extend(cv.polling_component_schema("60s")) 24 | ) 25 | 26 | 27 | async def to_code(config): 28 | var = await sensor.new_sensor(config) 29 | await cg.register_component(var, config) 30 | 31 | if CONF_LAMBDA in config: 32 | template_ = await cg.process_lambda( 33 | config[CONF_LAMBDA], [], return_type=cg.optional.template(float) 34 | ) 35 | cg.add(var.set_template(template_)) 36 | 37 | 38 | @automation.register_action( 39 | "sensor.template.publish", 40 | sensor.SensorPublishAction, 41 | cv.Schema( 42 | { 43 | cv.Required(CONF_ID): cv.use_id(sensor.Sensor), 44 | cv.Required(CONF_STATE): cv.templatable(cv.float_), 45 | } 46 | ), 47 | ) 48 | async def sensor_template_publish_to_code(config, action_id, template_arg, args): 49 | paren = await cg.get_variable(config[CONF_ID]) 50 | var = cg.new_Pvariable(action_id, template_arg, paren) 51 | template_ = await cg.templatable(config[CONF_STATE], args, float) 52 | cg.add(var.set_state(template_)) 53 | return var 54 | -------------------------------------------------------------------------------- /components/template/number/template_number.cpp: -------------------------------------------------------------------------------- 1 | #include "template_number.h" 2 | #include "esphome/core/log.h" 3 | 4 | namespace esphome::template_ { 5 | 6 | static const char *const TAG = "template.number"; 7 | 8 | void TemplateNumber::setup() { 9 | if (this->f_.has_value()) 10 | return; 11 | 12 | float value; 13 | if (!this->restore_value_) { 14 | value = this->initial_value_; 15 | } else { 16 | 17 | if ( this->has_global_forced_addr ) { id(global_forced_addr) = this->forced_addr; } 18 | if ( this->has_forced_hash ) { 19 | this->pref_ = global_preferences->make_preference(this->forced_hash); 20 | } else { 21 | this->pref_ = global_preferences->make_preference(this->get_preference_hash()); 22 | } 23 | 24 | if (!this->pref_.load(&value)) { 25 | if (!std::isnan(this->initial_value_)) { 26 | value = this->initial_value_; 27 | } else { 28 | value = this->traits.get_min_value(); 29 | } 30 | } 31 | } 32 | this->publish_state(value); 33 | } 34 | 35 | void TemplateNumber::update() { 36 | if (!this->f_.has_value()) 37 | return; 38 | 39 | auto val = this->f_(); 40 | if (val.has_value()) { 41 | this->publish_state(*val); 42 | } 43 | } 44 | 45 | void TemplateNumber::control(float value) { 46 | this->set_trigger_->trigger(value); 47 | 48 | if (this->optimistic_) 49 | this->publish_state(value); 50 | 51 | if (this->restore_value_) 52 | this->pref_.save(&value); 53 | } 54 | void TemplateNumber::dump_config() { 55 | LOG_NUMBER("", "Template Number", this); 56 | ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_)); 57 | LOG_UPDATE_INTERVAL(this); 58 | } 59 | 60 | } // namespace esphome::template_ 61 | -------------------------------------------------------------------------------- /components/template/output/__init__.py: -------------------------------------------------------------------------------- 1 | from esphome import automation 2 | import esphome.codegen as cg 3 | from esphome.components import output 4 | import esphome.config_validation as cv 5 | from esphome.const import CONF_BINARY, CONF_ID, CONF_TYPE 6 | 7 | from .. import template_ns 8 | 9 | TemplateBinaryOutput = template_ns.class_("TemplateBinaryOutput", output.BinaryOutput) 10 | TemplateFloatOutput = template_ns.class_("TemplateFloatOutput", output.FloatOutput) 11 | 12 | CONF_FLOAT = "float" 13 | CONF_WRITE_ACTION = "write_action" 14 | 15 | CONFIG_SCHEMA = cv.typed_schema( 16 | { 17 | CONF_BINARY: output.BINARY_OUTPUT_SCHEMA.extend( 18 | { 19 | cv.GenerateID(): cv.declare_id(TemplateBinaryOutput), 20 | cv.Required(CONF_WRITE_ACTION): automation.validate_automation( 21 | single=True 22 | ), 23 | } 24 | ), 25 | CONF_FLOAT: output.FLOAT_OUTPUT_SCHEMA.extend( 26 | { 27 | cv.GenerateID(): cv.declare_id(TemplateFloatOutput), 28 | cv.Required(CONF_WRITE_ACTION): automation.validate_automation( 29 | single=True 30 | ), 31 | } 32 | ), 33 | }, 34 | lower=True, 35 | ) 36 | 37 | 38 | async def to_code(config): 39 | var = cg.new_Pvariable(config[CONF_ID]) 40 | if config[CONF_TYPE] == CONF_BINARY: 41 | await automation.build_automation( 42 | var.get_trigger(), [(bool, "state")], config[CONF_WRITE_ACTION] 43 | ) 44 | else: 45 | await automation.build_automation( 46 | var.get_trigger(), [(float, "state")], config[CONF_WRITE_ACTION] 47 | ) 48 | await output.register_output(var, config) 49 | -------------------------------------------------------------------------------- /components/light/light_effect.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/component.h" 4 | 5 | namespace esphome::light { 6 | 7 | class LightState; 8 | 9 | class LightEffect { 10 | public: 11 | explicit LightEffect(const char *name) : name_(name) {} 12 | 13 | /// Initialize this LightEffect. Will be called once after creation. 14 | virtual void start() {} 15 | 16 | virtual void start_internal() { this->start(); } 17 | 18 | /// Called when this effect is about to be removed 19 | virtual void stop() {} 20 | 21 | /// Apply this effect. Use the provided state for starting transitions, ... 22 | virtual void apply() = 0; 23 | 24 | /** 25 | * Returns the name of this effect. 26 | * The returned pointer is valid for the lifetime of the program and must not be freed. 27 | */ 28 | const char *get_name() const { return this->name_; } 29 | 30 | /// Internal method called by the LightState when this light effect is registered in it. 31 | virtual void init() {} 32 | 33 | void init_internal(LightState *state) { 34 | this->state_ = state; 35 | this->init(); 36 | } 37 | 38 | /// Get the index of this effect in the parent light's effect list. 39 | /// Returns 0 if not found or not initialized. 40 | uint32_t get_index() const; 41 | 42 | /// Check if this effect is currently active. 43 | bool is_active() const; 44 | 45 | /// Get a reference to the parent light state. 46 | /// Returns nullptr if not initialized. 47 | LightState *get_light_state() const { return this->state_; } 48 | 49 | protected: 50 | LightState *state_{nullptr}; 51 | const char *name_; 52 | 53 | /// Internal method to find this effect's index in the parent light's effect list. 54 | uint32_t get_index_in_parent_() const; 55 | }; 56 | 57 | } // namespace esphome::light 58 | -------------------------------------------------------------------------------- /components/template/text_sensor/__init__.py: -------------------------------------------------------------------------------- 1 | from esphome import automation 2 | import esphome.codegen as cg 3 | from esphome.components import text_sensor 4 | from esphome.components.text_sensor import TextSensorPublishAction 5 | import esphome.config_validation as cv 6 | from esphome.const import CONF_ID, CONF_LAMBDA, CONF_STATE 7 | 8 | from .. import template_ns 9 | 10 | TemplateTextSensor = template_ns.class_( 11 | "TemplateTextSensor", text_sensor.TextSensor, cg.PollingComponent 12 | ) 13 | 14 | CONFIG_SCHEMA = ( 15 | text_sensor.text_sensor_schema() 16 | .extend( 17 | { 18 | cv.GenerateID(): cv.declare_id(TemplateTextSensor), 19 | cv.Optional(CONF_LAMBDA): cv.returning_lambda, 20 | } 21 | ) 22 | .extend(cv.polling_component_schema("60s")) 23 | ) 24 | 25 | 26 | async def to_code(config): 27 | var = await text_sensor.new_text_sensor(config) 28 | await cg.register_component(var, config) 29 | 30 | if CONF_LAMBDA in config: 31 | template_ = await cg.process_lambda( 32 | config[CONF_LAMBDA], [], return_type=cg.optional.template(cg.std_string) 33 | ) 34 | cg.add(var.set_template(template_)) 35 | 36 | 37 | @automation.register_action( 38 | "text_sensor.template.publish", 39 | TextSensorPublishAction, 40 | cv.Schema( 41 | { 42 | cv.Required(CONF_ID): cv.use_id(text_sensor.TextSensor), 43 | cv.Required(CONF_STATE): cv.templatable(cv.string_strict), 44 | } 45 | ), 46 | ) 47 | async def text_sensor_template_publish_to_code(config, action_id, template_arg, args): 48 | paren = await cg.get_variable(config[CONF_ID]) 49 | var = cg.new_Pvariable(action_id, template_arg, paren) 50 | template_ = await cg.templatable(config[CONF_STATE], args, cg.std_string) 51 | cg.add(var.set_state(template_)) 52 | return var 53 | -------------------------------------------------------------------------------- /components/monochromatic/light.py: -------------------------------------------------------------------------------- 1 | import esphome.codegen as cg 2 | from esphome.components import light, output 3 | import esphome.config_validation as cv 4 | from esphome.const import CONF_OUTPUT, CONF_OUTPUT_ID 5 | 6 | monochromatic_ns = cg.esphome_ns.namespace("monochromatic") 7 | MonochromaticLightOutput = monochromatic_ns.class_( 8 | "MonochromaticLightOutput", light.LightOutput 9 | ) 10 | 11 | CONFIG_SCHEMA = light.BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend( 12 | { 13 | cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(MonochromaticLightOutput), 14 | cv.Required(CONF_OUTPUT): cv.use_id(output.FloatOutput), 15 | } 16 | ) 17 | 18 | def final_validate(config): 19 | if ("esp8266" in fv.full_config.get()): 20 | esp8266_config = fv.full_config.get()["esp8266"] 21 | if ( ("start_free" in esp8266_config) and ("forced_addr" in config)): 22 | if ( (esp8266_config["start_free"] <= config["forced_addr"] + 11) ): 23 | start_free_num = esp8266_config["start_free"] 24 | forced_addr_num = config["forced_addr"] 25 | raise cv.Invalid( 26 | f"Forced address ({forced_addr_num}) conflicts with esp8266: start_free ({start_free_num})" 27 | ) 28 | else: 29 | if ("forced_addr" in config): 30 | raise cv.Invalid( 31 | "Forced_addr is only compatible with esp8266 platform" 32 | ) 33 | 34 | if "forced_addr" in config and "global_addr" not in config: 35 | raise cv.Invalid( 36 | "Forced_addr requires global_addr" 37 | ) 38 | 39 | FINAL_VALIDATE_SCHEMA = final_validate 40 | 41 | 42 | async def to_code(config): 43 | var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) 44 | await light.register_light(var, config) 45 | 46 | out = await cg.get_variable(config[CONF_OUTPUT]) 47 | cg.add(var.set_output(out)) 48 | -------------------------------------------------------------------------------- /esphome-webserver/packages/v3/src/css/app.ts: -------------------------------------------------------------------------------- 1 | import { css } from "lit"; 2 | 3 | export default css` 4 | .flex-grid-half { 5 | display: grid; 6 | grid-template-columns: 600px 2fr; 7 | } 8 | .flex-grid-half.expanded_entity, 9 | .flex-grid-half.expanded_logs { 10 | grid-template-columns: 1fr; 11 | } 12 | .flex-grid-half .col { 13 | margin: 8px; 14 | } 15 | .flex-grid-half .col:nth-child(2) { 16 | overflow: hidden; 17 | } 18 | .flex-grid-half.expanded_logs .col:nth-child(1) { 19 | display: none; 20 | } 21 | .flex-grid-half.expanded_entity .col:nth-child(2) { 22 | display: none; 23 | } 24 | 25 | @media (max-width: 1024px) { 26 | .flex-grid, 27 | .flex-grid-half { 28 | display: block; 29 | } 30 | .flex-grid-half .col { 31 | width: 100% !important; 32 | margin: 0 0 10px 0 !important; 33 | display: block !important; 34 | } 35 | } 36 | 37 | * { 38 | box-sizing: border-box; 39 | } 40 | .flex-grid { 41 | margin: 0 0 20px 0; 42 | } 43 | h1 { 44 | text-align: center; 45 | width: 100%; 46 | line-height: 1.1em; 47 | margin-block: 0.25em; 48 | } 49 | header div { 50 | text-align: center; 51 | width: 100%; 52 | } 53 | header #logo, 54 | header iconify-icon { 55 | float: right; 56 | font-size: 2.5rem; 57 | color: rgba(127, 127, 127, 0.5); 58 | } 59 | header #logo { 60 | float: left; 61 | color: rgba(127, 127, 127, 0.5); 62 | } 63 | .connected { 64 | color: rgba(0, 157, 16, 0.75); 65 | } 66 | esp-logo { 67 | float: left; 68 | line-height: 1em; 69 | font-size: initial; 70 | } 71 | form { 72 | display: flex; 73 | justify-content: space-between; 74 | background-color: rgba(127, 127, 127, 0.05); 75 | border-radius: 12px; 76 | border-width: 1px; 77 | border-style: solid; 78 | border-color: rgba(127, 127, 127, 0.12); 79 | } 80 | form .btn { 81 | margin-right: 0px; 82 | } 83 | `; 84 | -------------------------------------------------------------------------------- /components/template/number/template_number.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/components/number/number.h" 4 | #include "esphome/core/automation.h" 5 | #include "esphome/core/component.h" 6 | #include "esphome/core/preferences.h" 7 | #include "esphome/core/template_lambda.h" 8 | 9 | #include "esphome/components/globals/globals_component.h" 10 | 11 | namespace esphome::template_ { 12 | 13 | class TemplateNumber final : public number::Number, public PollingComponent { 14 | public: 15 | template void set_template(F &&f) { this->f_.set(std::forward(f)); } 16 | 17 | void setup() override; 18 | void update() override; 19 | void dump_config() override; 20 | float get_setup_priority() const override { return setup_priority::HARDWARE; } 21 | 22 | Trigger *get_set_trigger() const { return set_trigger_; } 23 | void set_optimistic(bool optimistic) { optimistic_ = optimistic; } 24 | void set_initial_value(float initial_value) { initial_value_ = initial_value; } 25 | void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; } 26 | 27 | bool has_forced_hash = false; 28 | uint32_t forced_hash = 0; 29 | void set_forced_hash(uint32_t hash_value) { 30 | forced_hash = hash_value; 31 | has_forced_hash = true; 32 | } 33 | 34 | uint32_t forced_addr = 12345; 35 | void set_forced_addr(uint32_t addr_value) { 36 | forced_addr = addr_value; 37 | } 38 | 39 | bool has_global_forced_addr = false; 40 | globals::GlobalsComponent *global_forced_addr; 41 | void set_global_addr(globals::GlobalsComponent *ga_in) { 42 | has_global_forced_addr = true; 43 | global_forced_addr = ga_in; 44 | } 45 | 46 | protected: 47 | void control(float value) override; 48 | bool optimistic_{false}; 49 | float initial_value_{NAN}; 50 | bool restore_value_{false}; 51 | Trigger *set_trigger_ = new Trigger(); 52 | TemplateLambda f_; 53 | 54 | ESPPreferenceObject pref_; 55 | }; 56 | 57 | } // namespace esphome::template_ 58 | -------------------------------------------------------------------------------- /components/template/valve/template_valve.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/component.h" 4 | #include "esphome/core/automation.h" 5 | #include "esphome/core/template_lambda.h" 6 | #include "esphome/components/valve/valve.h" 7 | 8 | namespace esphome::template_ { 9 | 10 | enum TemplateValveRestoreMode { 11 | VALVE_NO_RESTORE, 12 | VALVE_RESTORE, 13 | VALVE_RESTORE_AND_CALL, 14 | }; 15 | 16 | class TemplateValve final : public valve::Valve, public Component { 17 | public: 18 | TemplateValve(); 19 | 20 | template void set_state_lambda(F &&f) { this->state_f_.set(std::forward(f)); } 21 | Trigger<> *get_open_trigger() const; 22 | Trigger<> *get_close_trigger() const; 23 | Trigger<> *get_stop_trigger() const; 24 | Trigger<> *get_toggle_trigger() const; 25 | Trigger *get_position_trigger() const; 26 | void set_optimistic(bool optimistic); 27 | void set_assumed_state(bool assumed_state); 28 | void set_has_stop(bool has_stop); 29 | void set_has_position(bool has_position); 30 | void set_has_toggle(bool has_toggle); 31 | void set_restore_mode(TemplateValveRestoreMode restore_mode) { restore_mode_ = restore_mode; } 32 | 33 | void setup() override; 34 | void loop() override; 35 | void dump_config() override; 36 | 37 | float get_setup_priority() const override; 38 | 39 | protected: 40 | void control(const valve::ValveCall &call) override; 41 | valve::ValveTraits get_traits() override; 42 | void stop_prev_trigger_(); 43 | 44 | TemplateValveRestoreMode restore_mode_{VALVE_NO_RESTORE}; 45 | TemplateLambda state_f_; 46 | bool assumed_state_{false}; 47 | bool optimistic_{false}; 48 | Trigger<> *open_trigger_; 49 | Trigger<> *close_trigger_; 50 | bool has_stop_{false}; 51 | bool has_toggle_{false}; 52 | Trigger<> *stop_trigger_; 53 | Trigger<> *toggle_trigger_; 54 | Trigger<> *prev_command_trigger_{nullptr}; 55 | Trigger *position_trigger_; 56 | bool has_position_{false}; 57 | }; 58 | 59 | } // namespace esphome::template_ 60 | -------------------------------------------------------------------------------- /components/safe_mode/safe_mode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/component.h" 4 | #include "esphome/core/defines.h" 5 | #include "esphome/core/helpers.h" 6 | #include "esphome/core/preferences.h" 7 | 8 | namespace esphome { 9 | namespace safe_mode { 10 | 11 | /// SafeModeComponent provides a safe way to recover from repeated boot failures 12 | class SafeModeComponent : public Component { 13 | public: 14 | bool should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time, uint32_t boot_is_good_after); 15 | 16 | /// Set to true if the next startup will enter safe mode 17 | void set_safe_mode_pending(const bool &pending); 18 | bool get_safe_mode_pending(); 19 | 20 | void dump_config() override; 21 | float get_setup_priority() const override; 22 | void loop() override; 23 | 24 | void clean_rtc(); 25 | 26 | void on_safe_shutdown() override; 27 | 28 | void add_on_safe_mode_callback(std::function &&callback) { 29 | this->safe_mode_callback_.add(std::move(callback)); 30 | } 31 | 32 | protected: 33 | void write_rtc_(uint32_t val); 34 | uint32_t read_rtc_(); 35 | 36 | // Group all 4-byte aligned members together to avoid padding 37 | uint32_t safe_mode_boot_is_good_after_{60000}; ///< The amount of time after which the boot is considered successful 38 | uint32_t safe_mode_enable_time_{60000}; ///< The time safe mode should remain active for 39 | uint32_t safe_mode_rtc_value_{0}; 40 | uint32_t safe_mode_start_time_{0}; ///< stores when safe mode was enabled 41 | // Group 1-byte members together to minimize padding 42 | bool boot_successful_{false}; ///< set to true after boot is considered successful 43 | uint8_t safe_mode_num_attempts_{0}; 44 | // Larger objects at the end 45 | ESPPreferenceObject rtc_; 46 | CallbackManager safe_mode_callback_{}; 47 | 48 | static const uint32_t ENTER_SAFE_MODE_MAGIC = 49 | 0x5afe5afe; ///< a magic number to indicate that safe mode should be entered on next boot 50 | }; 51 | 52 | } // namespace safe_mode 53 | } // namespace esphome 54 | -------------------------------------------------------------------------------- /components/template/lock/template_lock.cpp: -------------------------------------------------------------------------------- 1 | #include "template_lock.h" 2 | #include "esphome/core/log.h" 3 | 4 | namespace esphome::template_ { 5 | 6 | using namespace esphome::lock; 7 | 8 | static const char *const TAG = "template.lock"; 9 | 10 | TemplateLock::TemplateLock() 11 | : lock_trigger_(new Trigger<>()), unlock_trigger_(new Trigger<>()), open_trigger_(new Trigger<>()) {} 12 | 13 | void TemplateLock::setup() { 14 | if (!this->f_.has_value()) 15 | this->disable_loop(); 16 | } 17 | 18 | void TemplateLock::loop() { 19 | auto val = this->f_(); 20 | if (val.has_value()) { 21 | this->publish_state(*val); 22 | } 23 | } 24 | void TemplateLock::control(const lock::LockCall &call) { 25 | if (this->prev_trigger_ != nullptr) { 26 | this->prev_trigger_->stop_action(); 27 | } 28 | 29 | auto state = *call.get_state(); 30 | if (state == LOCK_STATE_LOCKED) { 31 | this->prev_trigger_ = this->lock_trigger_; 32 | this->lock_trigger_->trigger(); 33 | } else if (state == LOCK_STATE_UNLOCKED) { 34 | this->prev_trigger_ = this->unlock_trigger_; 35 | this->unlock_trigger_->trigger(); 36 | } 37 | 38 | if (this->optimistic_) 39 | this->publish_state(state); 40 | } 41 | void TemplateLock::open_latch() { 42 | if (this->prev_trigger_ != nullptr) { 43 | this->prev_trigger_->stop_action(); 44 | } 45 | this->prev_trigger_ = this->open_trigger_; 46 | this->open_trigger_->trigger(); 47 | } 48 | void TemplateLock::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } 49 | float TemplateLock::get_setup_priority() const { return setup_priority::HARDWARE; } 50 | Trigger<> *TemplateLock::get_lock_trigger() const { return this->lock_trigger_; } 51 | Trigger<> *TemplateLock::get_unlock_trigger() const { return this->unlock_trigger_; } 52 | Trigger<> *TemplateLock::get_open_trigger() const { return this->open_trigger_; } 53 | void TemplateLock::dump_config() { 54 | LOG_LOCK("", "Template Lock", this); 55 | ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_)); 56 | } 57 | 58 | } // namespace esphome::template_ 59 | -------------------------------------------------------------------------------- /components/light/esp_hsv_color.cpp: -------------------------------------------------------------------------------- 1 | #include "esp_hsv_color.h" 2 | 3 | namespace esphome::light { 4 | 5 | Color ESPHSVColor::to_rgb() const { 6 | // based on FastLED's hsv rainbow to rgb 7 | const uint8_t hue = this->hue; 8 | const uint8_t sat = this->saturation; 9 | const uint8_t val = this->value; 10 | // upper 3 hue bits are for branch selection, lower 5 are for values 11 | const uint8_t offset8 = (hue & 0x1F) << 3; // 0..248 12 | // third of the offset, 255/3 = 85 (actually only up to 82; 164) 13 | const uint8_t third = esp_scale8(offset8, 85); 14 | const uint8_t two_thirds = esp_scale8(offset8, 170); 15 | Color rgb(255, 255, 255, 0); 16 | switch (hue >> 5) { 17 | case 0b000: 18 | rgb.r = 255 - third; 19 | rgb.g = third; 20 | rgb.b = 0; 21 | break; 22 | case 0b001: 23 | rgb.r = 171; 24 | rgb.g = 85 + third; 25 | rgb.b = 0; 26 | break; 27 | case 0b010: 28 | rgb.r = 171 - two_thirds; 29 | rgb.g = 170 + third; 30 | rgb.b = 0; 31 | break; 32 | case 0b011: 33 | rgb.r = 0; 34 | rgb.g = 255 - third; 35 | rgb.b = third; 36 | break; 37 | case 0b100: 38 | rgb.r = 0; 39 | rgb.g = 171 - two_thirds; 40 | rgb.b = 85 + two_thirds; 41 | break; 42 | case 0b101: 43 | rgb.r = third; 44 | rgb.g = 0; 45 | rgb.b = 255 - third; 46 | break; 47 | case 0b110: 48 | rgb.r = 85 + third; 49 | rgb.g = 0; 50 | rgb.b = 171 - third; 51 | break; 52 | case 0b111: 53 | rgb.r = 170 + third; 54 | rgb.g = 0; 55 | rgb.b = 85 - third; 56 | break; 57 | default: 58 | break; 59 | } 60 | // low saturation -> add uniform color to orig. hue 61 | // high saturation -> use hue directly 62 | // scales with square of saturation 63 | // (r,g,b) = (r,g,b) * sat + (1 - sat)^2 64 | rgb *= sat; 65 | const uint8_t desat = 255 - sat; 66 | rgb += esp_scale8(desat, desat); 67 | // (r,g,b) = (r,g,b) * val 68 | rgb *= val; 69 | return rgb; 70 | } 71 | 72 | } // namespace esphome::light 73 | -------------------------------------------------------------------------------- /components/template/select/template_select.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/components/select/select.h" 4 | #include "esphome/core/automation.h" 5 | #include "esphome/core/component.h" 6 | #include "esphome/core/preferences.h" 7 | #include "esphome/core/template_lambda.h" 8 | 9 | #include "esphome/components/globals/globals_component.h" 10 | 11 | namespace esphome::template_ { 12 | 13 | class TemplateSelect final : public select::Select, public PollingComponent { 14 | public: 15 | template void set_template(F &&f) { this->f_.set(std::forward(f)); } 16 | 17 | void setup() override; 18 | void update() override; 19 | void dump_config() override; 20 | float get_setup_priority() const override { return setup_priority::HARDWARE; } 21 | 22 | Trigger *get_set_trigger() const { return this->set_trigger_; } 23 | void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } 24 | void set_initial_option_index(size_t initial_option_index) { this->initial_option_index_ = initial_option_index; } 25 | void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; } 26 | 27 | bool has_forced_hash = false; 28 | uint32_t forced_hash = 0; 29 | void set_forced_hash(uint32_t hash_value) { 30 | forced_hash = hash_value; 31 | has_forced_hash = true; 32 | } 33 | 34 | uint32_t forced_addr = 12345; 35 | void set_forced_addr(uint32_t addr_value) { 36 | forced_addr = addr_value; 37 | } 38 | 39 | bool has_global_forced_addr = false; 40 | globals::GlobalsComponent *global_forced_addr; 41 | void set_global_addr(globals::GlobalsComponent *ga_in) { 42 | has_global_forced_addr = true; 43 | global_forced_addr = ga_in; 44 | } 45 | 46 | protected: 47 | void control(size_t index) override; 48 | bool optimistic_ = false; 49 | size_t initial_option_index_{0}; 50 | bool restore_value_ = false; 51 | Trigger *set_trigger_ = new Trigger(); 52 | TemplateLambda f_; 53 | 54 | ESPPreferenceObject pref_; 55 | }; 56 | 57 | } // namespace esphome::template_ 58 | -------------------------------------------------------------------------------- /components/rgb/light.py: -------------------------------------------------------------------------------- 1 | import esphome.codegen as cg 2 | from esphome.components import light, output 3 | import esphome.config_validation as cv 4 | import esphome.final_validate as fv 5 | from esphome.const import CONF_BLUE, CONF_GREEN, CONF_OUTPUT_ID, CONF_RED 6 | 7 | rgb_ns = cg.esphome_ns.namespace("rgb") 8 | RGBLightOutput = rgb_ns.class_("RGBLightOutput", light.LightOutput) 9 | 10 | CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend( 11 | { 12 | cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(RGBLightOutput), 13 | cv.Required(CONF_RED): cv.use_id(output.FloatOutput), 14 | cv.Required(CONF_GREEN): cv.use_id(output.FloatOutput), 15 | cv.Required(CONF_BLUE): cv.use_id(output.FloatOutput), 16 | } 17 | ) 18 | 19 | 20 | def final_validate(config): 21 | if ("esp8266" in fv.full_config.get()): 22 | esp8266_config = fv.full_config.get()["esp8266"] 23 | if ( ("start_free" in esp8266_config) and ("forced_addr" in config)): 24 | if ( (esp8266_config["start_free"] <= config["forced_addr"] + 11) ): 25 | start_free_num = esp8266_config["start_free"] 26 | forced_addr_num = config["forced_addr"] 27 | raise cv.Invalid( 28 | f"Forced address ({forced_addr_num}) conflicts with esp8266: start_free ({start_free_num})" 29 | ) 30 | else: 31 | if ("forced_addr" in config): 32 | raise cv.Invalid( 33 | "Forced_addr is only compatible with esp8266 platform" 34 | ) 35 | 36 | if "forced_addr" in config and "global_addr" not in config: 37 | raise cv.Invalid( 38 | "Forced_addr requires global_addr" 39 | ) 40 | 41 | FINAL_VALIDATE_SCHEMA = final_validate 42 | 43 | 44 | async def to_code(config): 45 | var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) 46 | await light.register_light(var, config) 47 | 48 | red = await cg.get_variable(config[CONF_RED]) 49 | cg.add(var.set_red(red)) 50 | green = await cg.get_variable(config[CONF_GREEN]) 51 | cg.add(var.set_green(green)) 52 | blue = await cg.get_variable(config[CONF_BLUE]) 53 | cg.add(var.set_blue(blue)) 54 | -------------------------------------------------------------------------------- /esphome-webserver/packages/v3/src/css/esp-entity-table.ts: -------------------------------------------------------------------------------- 1 | import { css } from "lit"; 2 | 3 | export default css` 4 | :host { 5 | position: relative; 6 | } 7 | select { 8 | background-color: inherit; 9 | color: inherit; 10 | width: 100%; 11 | border-radius: 4px; 12 | } 13 | option { 14 | color: currentColor; 15 | background-color: var(--primary-color, currentColor); 16 | } 17 | input[type="range"], 18 | input[type="text"] { 19 | width: calc(100% - 3rem); 20 | height: 0.75rem; 21 | } 22 | .range { 23 | text-align: center; 24 | } 25 | .entity-row { 26 | display: flex; 27 | align-items: center; 28 | flex-direction: row; 29 | transition: all 0.3s ease-out 0s; 30 | min-height: 40px; 31 | position: relative; 32 | } 33 | .entity-row.expanded { 34 | min-height: 240px; 35 | } 36 | .entity-row:nth-child(2n) { 37 | background-color: rgba(90, 90, 90, 0.1); 38 | } 39 | .entity-row iconify-icon { 40 | vertical-align: middle; 41 | } 42 | .entity-row > :nth-child(1) { 43 | flex: 0 0 40px; 44 | color: #44739e; 45 | line-height: 40px; 46 | text-align: center; 47 | } 48 | .entity-row > :nth-child(2) { 49 | flex: 1 1 40%; 50 | margin-left: 16px; 51 | margin-right: 8px; 52 | text-wrap: nowrap; 53 | overflow: hidden; 54 | text-overflow: ellipsis; 55 | min-width: 150px; 56 | } 57 | .entity-row > :nth-child(3) { 58 | flex: 1 1 50%; 59 | margin-right: 8px; 60 | margin-left: 20px; 61 | text-align: right; 62 | display: flex; 63 | justify-content: space-between; 64 | } 65 | .entity-row > :nth-child(3) > :only-child { 66 | margin-left: auto; 67 | } 68 | .binary_sensor_off { 69 | color: rgba(127, 127, 127, 0.7); 70 | } 71 | .singlebutton-row button { 72 | margin: auto; 73 | display: flex; 74 | } 75 | 76 | input[type="color"]::-webkit-color-swatch-wrapper { 77 | padding: 0 !important; 78 | } 79 | 80 | .climate { 81 | width: 100%; 82 | display: grid; 83 | text-align: center; 84 | grid-template-columns: repeat(3, 1fr); 85 | gap: 6px; 86 | padding: 10px; 87 | align-items: center; 88 | } 89 | `; 90 | -------------------------------------------------------------------------------- /components/total_daily_energy/total_daily_energy.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/component.h" 4 | #include "esphome/core/preferences.h" 5 | #include "esphome/core/hal.h" 6 | #include "esphome/components/sensor/sensor.h" 7 | #include "esphome/components/time/real_time_clock.h" 8 | 9 | #include "esphome/components/globals/globals_component.h" 10 | 11 | namespace esphome { 12 | namespace total_daily_energy { 13 | 14 | enum TotalDailyEnergyMethod { 15 | TOTAL_DAILY_ENERGY_METHOD_TRAPEZOID = 0, 16 | TOTAL_DAILY_ENERGY_METHOD_LEFT, 17 | TOTAL_DAILY_ENERGY_METHOD_RIGHT, 18 | }; 19 | 20 | class TotalDailyEnergy : public sensor::Sensor, public Component { 21 | public: 22 | void set_restore(bool restore) { restore_ = restore; } 23 | void set_time(time::RealTimeClock *time) { time_ = time; } 24 | void set_parent(Sensor *parent) { parent_ = parent; } 25 | void set_method(TotalDailyEnergyMethod method) { method_ = method; } 26 | void setup() override; 27 | void dump_config() override; 28 | void loop() override; 29 | 30 | void publish_state_and_save(float state); 31 | 32 | 33 | bool has_forced_hash = false; 34 | uint32_t forced_hash = 0; 35 | void set_forced_hash(uint32_t hash_value) { 36 | forced_hash = hash_value; 37 | has_forced_hash = true; 38 | } 39 | 40 | uint32_t forced_addr = 12345; 41 | void set_forced_addr(uint32_t addr_value) { 42 | forced_addr = addr_value; 43 | } 44 | 45 | bool has_global_forced_addr = false; 46 | globals::GlobalsComponent *global_forced_addr; 47 | void set_global_addr(globals::GlobalsComponent *ga_in) { 48 | has_global_forced_addr = true; 49 | global_forced_addr = ga_in; 50 | } 51 | 52 | void zero_total_energy(); 53 | bool manual_control = false; 54 | bool manual_zero = false; 55 | 56 | protected: 57 | void process_new_state_(float state); 58 | 59 | ESPPreferenceObject pref_; 60 | time::RealTimeClock *time_; 61 | Sensor *parent_; 62 | TotalDailyEnergyMethod method_; 63 | uint16_t last_day_of_year_{}; 64 | uint32_t last_update_{0}; 65 | bool restore_; 66 | float total_energy_{0.0f}; 67 | float last_power_state_{0.0f}; 68 | }; 69 | 70 | } // namespace total_daily_energy 71 | } // namespace esphome 72 | -------------------------------------------------------------------------------- /esphome-webserver/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Build results 45 | _static/ 46 | 47 | # TypeScript v1 declaration files 48 | typings/ 49 | 50 | # TypeScript cache 51 | *.tsbuildinfo 52 | 53 | # Optional npm cache directory 54 | .npm 55 | 56 | # Optional eslint cache 57 | .eslintcache 58 | 59 | # Microbundle cache 60 | .rpt2_cache/ 61 | .rts2_cache_cjs/ 62 | .rts2_cache_es/ 63 | .rts2_cache_umd/ 64 | 65 | # Optional REPL history 66 | .node_repl_history 67 | 68 | # Output of 'npm pack' 69 | *.tgz 70 | 71 | # Yarn Integrity file 72 | .yarn-integrity 73 | 74 | # dotenv environment variables file 75 | .env 76 | .env.test 77 | 78 | # parcel-bundler cache (https://parceljs.org/) 79 | .cache 80 | 81 | # Next.js build output 82 | .next 83 | 84 | # Nuxt.js build / generate output 85 | .nuxt 86 | dist 87 | 88 | # Gatsby files 89 | .cache/ 90 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 91 | # https://nextjs.org/blog/next-9-1#public-directory-support 92 | # public 93 | 94 | # vuepress build output 95 | .vuepress/dist 96 | 97 | # Serverless directories 98 | .serverless/ 99 | 100 | # FuseBox cache 101 | .fusebox/ 102 | 103 | # DynamoDB Local files 104 | .dynamodb/ 105 | 106 | # TernJS port file 107 | .tern-port 108 | 109 | -------------------------------------------------------------------------------- /esphome-webserver/packages/v3/src/esp-entity-chart.ts: -------------------------------------------------------------------------------- 1 | import { html, css, LitElement, TemplateResult, nothing } from "lit"; 2 | import { customElement, state, property } from "lit/decorators.js"; 3 | import { 4 | Chart, 5 | Colors, 6 | LineController, 7 | CategoryScale, 8 | LinearScale, 9 | PointElement, 10 | LineElement, 11 | } from "chart.js"; 12 | 13 | Chart.register( 14 | Colors, 15 | LineController, 16 | CategoryScale, 17 | LinearScale, 18 | PointElement, 19 | LineElement 20 | ); 21 | 22 | @customElement("esp-entity-chart") 23 | export class ChartElement extends LitElement { 24 | @property({ type: Array }) chartdata = []; 25 | private chartSubComponent: Chart; 26 | 27 | constructor() { 28 | super(); 29 | } 30 | 31 | updated(changedProperties: Map) { 32 | super.updated(changedProperties); 33 | if (changedProperties.has("chartdata")) { 34 | this.chartSubComponent.data.datasets[0].data = this.chartdata; 35 | this.chartSubComponent.data.labels = this.chartdata; 36 | this.chartSubComponent?.update(); 37 | } 38 | } 39 | 40 | firstUpdated() { 41 | const ctx = this.renderRoot.querySelector("canvas").getContext("2d"); 42 | this.chartSubComponent = new Chart(ctx, { 43 | type: "line", 44 | data: { 45 | labels: this.chartdata, 46 | datasets: [ 47 | { 48 | data: this.chartdata, 49 | borderWidth: 1, 50 | tension: 0.3, 51 | }, 52 | ], 53 | }, 54 | options: { 55 | plugins: { legend: { display: false } }, 56 | scales: { x: { display: false }, y: { position: "right" } }, 57 | responsive: true, 58 | maintainAspectRatio: false, 59 | }, 60 | }); 61 | } 62 | 63 | static get styles() { 64 | return css` 65 | :host { 66 | position: absolute; 67 | left: 24px; 68 | height: 42px !important; 69 | width: calc(100% - 42px); 70 | z-index: -100; 71 | opacity: 0.1; 72 | } 73 | :host-context(.expanded) { 74 | height: 240px !important; 75 | opacity: 0.5; 76 | } 77 | `; 78 | } 79 | 80 | render() { 81 | return html``; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /components/template/switch/template_switch.cpp: -------------------------------------------------------------------------------- 1 | #include "template_switch.h" 2 | #include "esphome/core/log.h" 3 | 4 | namespace esphome { 5 | namespace template_ { 6 | 7 | static const char *const TAG = "template.switch"; 8 | 9 | TemplateSwitch::TemplateSwitch() : turn_on_trigger_(new Trigger<>()), turn_off_trigger_(new Trigger<>()) {} 10 | 11 | void TemplateSwitch::loop() { 12 | auto s = this->f_(); 13 | if (s.has_value()) { 14 | this->publish_state(*s); 15 | } 16 | } 17 | void TemplateSwitch::write_state(bool state) { 18 | if (this->prev_trigger_ != nullptr) { 19 | this->prev_trigger_->stop_action(); 20 | } 21 | 22 | if (state) { 23 | this->prev_trigger_ = this->turn_on_trigger_; 24 | this->turn_on_trigger_->trigger(); 25 | } else { 26 | this->prev_trigger_ = this->turn_off_trigger_; 27 | this->turn_off_trigger_->trigger(); 28 | } 29 | 30 | if (this->optimistic_) 31 | this->publish_state(state); 32 | } 33 | void TemplateSwitch::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } 34 | bool TemplateSwitch::assumed_state() { return this->assumed_state_; } 35 | float TemplateSwitch::get_setup_priority() const { return setup_priority::HARDWARE - 2.0f; } 36 | Trigger<> *TemplateSwitch::get_turn_on_trigger() const { return this->turn_on_trigger_; } 37 | Trigger<> *TemplateSwitch::get_turn_off_trigger() const { return this->turn_off_trigger_; } 38 | void TemplateSwitch::setup() { 39 | if (!this->f_.has_value()) 40 | this->disable_loop(); 41 | 42 | optional initial_state = this->get_initial_state_with_restore_mode(); 43 | 44 | if (initial_state.has_value()) { 45 | ESP_LOGD(TAG, " Restored state %s", ONOFF(initial_state.value())); 46 | // if it has a value, restore_mode is not "DISABLED", therefore act on the switch: 47 | if (initial_state.value()) { 48 | this->turn_on(); 49 | } else { 50 | this->turn_off(); 51 | } 52 | } 53 | } 54 | void TemplateSwitch::dump_config() { 55 | LOG_SWITCH("", "Template Switch", this); 56 | ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_)); 57 | } 58 | void TemplateSwitch::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; } 59 | 60 | } // namespace template_ 61 | } // namespace esphome 62 | -------------------------------------------------------------------------------- /components/light/light_transformer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/hal.h" 4 | #include "esphome/core/helpers.h" 5 | #include "light_color_values.h" 6 | 7 | namespace esphome::light { 8 | 9 | /// Base class for all light color transformers, such as transitions or flashes. 10 | class LightTransformer { 11 | public: 12 | virtual ~LightTransformer() = default; 13 | 14 | void setup(const LightColorValues &start_values, const LightColorValues &target_values, uint32_t length) { 15 | this->start_time_ = millis(); 16 | this->length_ = length; 17 | this->start_values_ = start_values; 18 | this->target_values_ = target_values; 19 | this->start(); 20 | } 21 | 22 | /// Indicates whether this transformation is finished. 23 | virtual bool is_finished() { return this->get_progress_() >= 1.0f; } 24 | 25 | /// This will be called before the transition is started. 26 | virtual void start() {} 27 | 28 | /// This will be called while the transformer is active to apply the transition to the light. Can either write to the 29 | /// light directly, or return LightColorValues that will be applied. 30 | virtual optional apply() = 0; 31 | 32 | /// This will be called after transition is finished. 33 | virtual void stop() {} 34 | 35 | const LightColorValues &get_start_values() const { return this->start_values_; } 36 | 37 | const LightColorValues &get_target_values() const { return this->target_values_; } 38 | 39 | protected: 40 | // This looks crazy, but it reduces to 6x^5 - 15x^4 + 10x^3 which is just a smooth sigmoid-like 41 | // transition from 0 to 1 on x = [0, 1] 42 | static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); } 43 | 44 | /// The progress of this transition, on a scale of 0 to 1. 45 | float get_progress_() { 46 | uint32_t now = esphome::millis(); 47 | if (now < this->start_time_) 48 | return 0.0f; 49 | if (now >= this->start_time_ + this->length_) 50 | return 1.0f; 51 | 52 | return clamp((now - this->start_time_) / float(this->length_), 0.0f, 1.0f); 53 | } 54 | 55 | uint32_t start_time_; 56 | uint32_t length_; 57 | LightColorValues start_values_; 58 | LightColorValues target_values_; 59 | }; 60 | 61 | } // namespace esphome::light 62 | -------------------------------------------------------------------------------- /components/template/cover/template_cover.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/component.h" 4 | #include "esphome/core/automation.h" 5 | #include "esphome/core/template_lambda.h" 6 | #include "esphome/components/cover/cover.h" 7 | 8 | namespace esphome::template_ { 9 | 10 | enum TemplateCoverRestoreMode { 11 | COVER_NO_RESTORE, 12 | COVER_RESTORE, 13 | COVER_RESTORE_AND_CALL, 14 | }; 15 | 16 | class TemplateCover final : public cover::Cover, public Component { 17 | public: 18 | TemplateCover(); 19 | 20 | template void set_state_lambda(F &&f) { this->state_f_.set(std::forward(f)); } 21 | template void set_tilt_lambda(F &&f) { this->tilt_f_.set(std::forward(f)); } 22 | Trigger<> *get_open_trigger() const; 23 | Trigger<> *get_close_trigger() const; 24 | Trigger<> *get_stop_trigger() const; 25 | Trigger<> *get_toggle_trigger() const; 26 | Trigger *get_position_trigger() const; 27 | Trigger *get_tilt_trigger() const; 28 | void set_optimistic(bool optimistic); 29 | void set_assumed_state(bool assumed_state); 30 | void set_has_stop(bool has_stop); 31 | void set_has_position(bool has_position); 32 | void set_has_tilt(bool has_tilt); 33 | void set_has_toggle(bool has_toggle); 34 | void set_restore_mode(TemplateCoverRestoreMode restore_mode) { restore_mode_ = restore_mode; } 35 | 36 | void setup() override; 37 | void loop() override; 38 | void dump_config() override; 39 | 40 | float get_setup_priority() const override; 41 | 42 | protected: 43 | void control(const cover::CoverCall &call) override; 44 | cover::CoverTraits get_traits() override; 45 | void stop_prev_trigger_(); 46 | 47 | TemplateCoverRestoreMode restore_mode_{COVER_RESTORE}; 48 | TemplateLambda state_f_; 49 | TemplateLambda tilt_f_; 50 | bool assumed_state_{false}; 51 | bool optimistic_{false}; 52 | Trigger<> *open_trigger_; 53 | Trigger<> *close_trigger_; 54 | bool has_stop_{false}; 55 | bool has_toggle_{false}; 56 | Trigger<> *stop_trigger_; 57 | Trigger<> *toggle_trigger_; 58 | Trigger<> *prev_command_trigger_{nullptr}; 59 | Trigger *position_trigger_; 60 | bool has_position_{false}; 61 | Trigger *tilt_trigger_; 62 | bool has_tilt_{false}; 63 | }; 64 | 65 | } // namespace esphome::template_ 66 | -------------------------------------------------------------------------------- /components/gpio/binary_sensor/gpio_binary_sensor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/component.h" 4 | #include "esphome/core/hal.h" 5 | #include "esphome/core/helpers.h" 6 | #include "esphome/components/binary_sensor/binary_sensor.h" 7 | 8 | namespace esphome { 9 | namespace gpio { 10 | 11 | // Store class for ISR data (no vtables, ISR-safe) 12 | class GPIOBinarySensorStore { 13 | public: 14 | void setup(InternalGPIOPin *pin, gpio::InterruptType type, Component *component); 15 | 16 | static void gpio_intr(GPIOBinarySensorStore *arg); 17 | 18 | bool get_state() const { 19 | // No lock needed: state_ is atomically updated by ISR 20 | // Volatile ensures we read the latest value 21 | return this->state_; 22 | } 23 | 24 | bool is_changed() const { 25 | // Simple read of volatile bool - no clearing here 26 | return this->changed_; 27 | } 28 | 29 | void clear_changed() { 30 | // Separate method to clear the flag 31 | this->changed_ = false; 32 | } 33 | 34 | protected: 35 | ISRInternalGPIOPin isr_pin_; 36 | volatile bool state_{false}; 37 | volatile bool last_state_{false}; 38 | volatile bool changed_{false}; 39 | Component *component_{nullptr}; // Pointer to the component for enable_loop_soon_any_context() 40 | }; 41 | 42 | class GPIOBinarySensor : public binary_sensor::BinarySensor, public Component { 43 | public: 44 | // No destructor needed: ESPHome components are created at boot and live forever. 45 | // Interrupts are only detached on reboot when memory is cleared anyway. 46 | 47 | void set_pin(GPIOPin *pin) { pin_ = pin; } 48 | void set_use_interrupt(bool use_interrupt) { use_interrupt_ = use_interrupt; } 49 | void set_interrupt_type(gpio::InterruptType type) { interrupt_type_ = type; } 50 | // ========== INTERNAL METHODS ========== 51 | // (In most use cases you won't need these) 52 | /// Setup pin 53 | void setup() override; 54 | void dump_config() override; 55 | /// Hardware priority 56 | float get_setup_priority() const override; 57 | /// Check sensor 58 | void loop() override; 59 | 60 | protected: 61 | GPIOPin *pin_; 62 | bool use_interrupt_{true}; 63 | gpio::InterruptType interrupt_type_{gpio::INTERRUPT_ANY_EDGE}; 64 | GPIOBinarySensorStore store_; 65 | }; 66 | 67 | } // namespace gpio 68 | } // namespace esphome 69 | -------------------------------------------------------------------------------- /components/template/select/template_select.cpp: -------------------------------------------------------------------------------- 1 | #include "template_select.h" 2 | #include "esphome/core/log.h" 3 | 4 | namespace esphome::template_ { 5 | 6 | static const char *const TAG = "template.select"; 7 | 8 | void TemplateSelect::setup() { 9 | if (this->f_.has_value()) 10 | return; 11 | 12 | size_t index = this->initial_option_index_; 13 | if (this->restore_value_) { 14 | 15 | if ( this->has_global_forced_addr ) { id(global_forced_addr) = this->forced_addr; } 16 | if ( this->has_forced_hash ) { 17 | this->pref_ = global_preferences->make_preference(this->forced_hash); 18 | } else { 19 | this->pref_ = global_preferences->make_preference(this->get_preference_hash()); 20 | } 21 | size_t restored_index; 22 | if (this->pref_.load(&restored_index) && this->has_index(restored_index)) { 23 | index = restored_index; 24 | ESP_LOGD(TAG, "State from restore: %s", this->option_at(index)); 25 | } else { 26 | ESP_LOGD(TAG, "State from initial (could not load or invalid stored index): %s", this->option_at(index)); 27 | } 28 | } else { 29 | ESP_LOGD(TAG, "State from initial: %s", this->option_at(index)); 30 | } 31 | 32 | this->publish_state(index); 33 | } 34 | 35 | void TemplateSelect::update() { 36 | if (!this->f_.has_value()) 37 | return; 38 | 39 | auto val = this->f_(); 40 | if (val.has_value()) { 41 | if (!this->has_option(*val)) { 42 | ESP_LOGE(TAG, "Lambda returned an invalid option: %s", (*val).c_str()); 43 | return; 44 | } 45 | this->publish_state(*val); 46 | } 47 | } 48 | 49 | void TemplateSelect::control(size_t index) { 50 | this->set_trigger_->trigger(std::string(this->option_at(index))); 51 | 52 | if (this->optimistic_) 53 | this->publish_state(index); 54 | 55 | if (this->restore_value_) 56 | this->pref_.save(&index); 57 | } 58 | 59 | void TemplateSelect::dump_config() { 60 | LOG_SELECT("", "Template Select", this); 61 | LOG_UPDATE_INTERVAL(this); 62 | if (this->f_.has_value()) 63 | return; 64 | ESP_LOGCONFIG(TAG, 65 | " Optimistic: %s\n" 66 | " Initial Option: %s\n" 67 | " Restore Value: %s", 68 | YESNO(this->optimistic_), this->option_at(this->initial_option_index_), YESNO(this->restore_value_)); 69 | } 70 | 71 | } // namespace esphome::template_ 72 | -------------------------------------------------------------------------------- /components/gpio/switch/gpio_switch.cpp: -------------------------------------------------------------------------------- 1 | #include "gpio_switch.h" 2 | #include "esphome/core/log.h" 3 | 4 | namespace esphome { 5 | namespace gpio { 6 | 7 | static const char *const TAG = "switch.gpio"; 8 | 9 | float GPIOSwitch::get_setup_priority() const { return setup_priority::HARDWARE; } 10 | void GPIOSwitch::setup() { 11 | bool initial_state = this->get_initial_state_with_restore_mode().value_or(false); 12 | 13 | // write state before setup 14 | if (initial_state) { 15 | this->turn_on(); 16 | } else { 17 | this->turn_off(); 18 | } 19 | this->pin_->setup(); 20 | // write after setup again for other IOs 21 | if (initial_state) { 22 | this->turn_on(); 23 | } else { 24 | this->turn_off(); 25 | } 26 | 27 | this->is_setup_ = true; 28 | 29 | } 30 | void GPIOSwitch::dump_config() { 31 | LOG_SWITCH("", "GPIO Switch", this); 32 | LOG_PIN(" Pin: ", this->pin_); 33 | if (!this->interlock_.empty()) { 34 | ESP_LOGCONFIG(TAG, " Interlocks:"); 35 | for (auto *lock : this->interlock_) { 36 | if (lock == this) 37 | continue; 38 | ESP_LOGCONFIG(TAG, " %s", lock->get_name().c_str()); 39 | } 40 | } 41 | } 42 | void GPIOSwitch::write_state(bool state) { 43 | if (state != this->inverted_) { 44 | // Turning ON, check interlocking 45 | 46 | bool found = false; 47 | for (auto *lock : this->interlock_) { 48 | if (lock == this) 49 | continue; 50 | 51 | if (lock->state) { 52 | lock->turn_off(); 53 | found = true; 54 | } 55 | } 56 | if (found && this->interlock_wait_time_ != 0) { 57 | this->set_timeout("interlock", this->interlock_wait_time_, [this, state] { 58 | // Don't write directly, call the function again 59 | // (some other switch may have changed state while we were waiting) 60 | this->write_state(state); 61 | }); 62 | return; 63 | } 64 | } else if (this->interlock_wait_time_ != 0) { 65 | // If we are switched off during the interlock wait time, cancel any pending 66 | // re-activations 67 | this->cancel_timeout("interlock"); 68 | } 69 | 70 | this->pin_->digital_write(state); 71 | this->publish_state(state); 72 | } 73 | void GPIOSwitch::set_interlock(const std::initializer_list &interlock) { this->interlock_ = interlock; } 74 | 75 | bool GPIOSwitch::is_setup() { return this->is_setup_; } 76 | 77 | } // namespace gpio 78 | } // namespace esphome 79 | -------------------------------------------------------------------------------- /components/captive_portal/captive_portal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "esphome/core/defines.h" 3 | #ifdef USE_CAPTIVE_PORTAL 4 | #include 5 | #ifdef USE_ARDUINO 6 | #include 7 | #endif 8 | #ifdef USE_ESP_IDF 9 | #include "dns_server_esp32_idf.h" 10 | #endif 11 | #include "esphome/core/component.h" 12 | #include "esphome/core/helpers.h" 13 | #include "esphome/core/preferences.h" 14 | #include "esphome/components/web_server_base/web_server_base.h" 15 | 16 | namespace esphome { 17 | 18 | namespace captive_portal { 19 | 20 | class CaptivePortal : public AsyncWebHandler, public Component { 21 | public: 22 | CaptivePortal(web_server_base::WebServerBase *base); 23 | void setup() override; 24 | void dump_config() override; 25 | void loop() override { 26 | #ifdef USE_ARDUINO 27 | if (this->dns_server_ != nullptr) { 28 | this->dns_server_->processNextRequest(); 29 | } 30 | #endif 31 | #ifdef USE_ESP_IDF 32 | if (this->dns_server_ != nullptr) { 33 | this->dns_server_->process_next_request(); 34 | } 35 | #endif 36 | } 37 | float get_setup_priority() const override; 38 | void start(); 39 | bool is_active() const { return this->active_; } 40 | void end() { 41 | this->active_ = false; 42 | this->disable_loop(); // Stop processing DNS requests 43 | this->base_->deinit(); 44 | if (this->dns_server_ != nullptr) { 45 | this->dns_server_->stop(); 46 | this->dns_server_ = nullptr; 47 | } 48 | } 49 | 50 | bool canHandle(AsyncWebServerRequest *request) const override { 51 | // Handle all GET requests when captive portal is active 52 | // This allows us to respond with the portal page for any URL, 53 | // triggering OS captive portal detection 54 | return this->active_ && request->method() == HTTP_GET; 55 | } 56 | 57 | void handle_config(AsyncWebServerRequest *request); 58 | 59 | void handle_wifisave(AsyncWebServerRequest *request); 60 | 61 | void handleRequest(AsyncWebServerRequest *req) override; 62 | 63 | protected: 64 | web_server_base::WebServerBase *base_; 65 | bool initialized_{false}; 66 | bool active_{false}; 67 | #if defined(USE_ARDUINO) || defined(USE_ESP_IDF) 68 | std::unique_ptr dns_server_{nullptr}; 69 | #endif 70 | }; 71 | 72 | extern CaptivePortal *global_captive_portal; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) 73 | 74 | } // namespace captive_portal 75 | } // namespace esphome 76 | #endif 77 | -------------------------------------------------------------------------------- /esphome-webserver/packages/captive-portal/src/main.ts: -------------------------------------------------------------------------------- 1 | if (document.location.search === "?save") document.getElementsByTagName("aside")[0].style.display = "block"; 2 | function wifi(dBm: number) { 3 | let quality: number = Math.max(Math.min(2 * (dBm + 100), 100), 0) / 100.0; 4 | return svg(` 5 | `) 6 | } 7 | function svg(el:String) { 8 | return html([`${el}`]) 9 | } 10 | function lock(show: boolean) { 11 | return show 12 | ? svg(``) 16 | : "" 17 | } 18 | function html(h: String[]) { 19 | return h.join(""); 20 | } 21 | fetch("/config.json").then(function (response) { 22 | response.json().then(function (config) { 23 | document.title = config.name; 24 | // document.body.getElementsByTagName("h1")[0].innerText = "WiFi Networks: " + config.name 25 | let result = config.aps.slice(1).map(function (ap) { 26 | return `
28 | 29 | ${wifi(ap.rssi)} 30 | ${ap.ssid} 31 | 32 | ${lock(ap.lock)} 33 |
` 34 | }) 35 | document.querySelector("#net").innerHTML = html(result) 36 | document.querySelector("link[rel~='icon']").href = `data:image/svg+xml,${wifi(-65)}`; 37 | 38 | // fill in kauf-added params 39 | document.querySelector("#t_host").innerHTML = config.name 40 | document.querySelector("#t_mac").innerHTML = config.mac_addr 41 | document.querySelector("#t_hard").innerHTML = config.hard_ssid 42 | document.querySelector("#t_soft").innerHTML = config.soft_ssid 43 | document.querySelector("#t_free").innerHTML = config.free_sp + " bytes" 44 | document.querySelector("#t_esphv").innerHTML = config.esph_v 45 | document.querySelector("#t_projn").innerHTML = config.proj_n 46 | document.querySelector("#t_projv").innerHTML = config.proj_v 47 | 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /components/gpio/switch/__init__.py: -------------------------------------------------------------------------------- 1 | from esphome import pins 2 | import esphome.codegen as cg 3 | from esphome.components import switch 4 | import esphome.config_validation as cv 5 | import esphome.final_validate as fv 6 | from esphome.const import CONF_INTERLOCK, CONF_PIN 7 | 8 | from .. import gpio_ns 9 | 10 | GPIOSwitch = gpio_ns.class_("GPIOSwitch", switch.Switch, cg.Component) 11 | 12 | CONF_INTERLOCK_WAIT_TIME = "interlock_wait_time" 13 | CONFIG_SCHEMA = ( 14 | switch.switch_schema(GPIOSwitch) 15 | .extend( 16 | { 17 | cv.Required(CONF_PIN): pins.gpio_output_pin_schema, 18 | cv.Optional(CONF_INTERLOCK): cv.ensure_list(cv.use_id(switch.Switch)), 19 | cv.Optional( 20 | CONF_INTERLOCK_WAIT_TIME, default="0ms" 21 | ): cv.positive_time_period_milliseconds, 22 | } 23 | ) 24 | .extend(cv.COMPONENT_SCHEMA) 25 | ) 26 | 27 | def final_validate(config): 28 | if ("esp8266" in fv.full_config.get()): 29 | esp8266_config = fv.full_config.get()["esp8266"] 30 | if ( ("start_free" in esp8266_config) and ("forced_addr" in config)): 31 | if ( (esp8266_config["start_free"] <= config["forced_addr"] + 1) ): 32 | start_free_num = esp8266_config["start_free"] 33 | forced_addr_num = config["forced_addr"] 34 | raise cv.Invalid( 35 | f"Forced address ({forced_addr_num}) conflicts with esp8266: start_free ({start_free_num})" 36 | ) 37 | else: 38 | if ("forced_addr" in config): 39 | raise cv.Invalid( 40 | "Forced_addr is only compatible with esp8266 platform" 41 | ) 42 | 43 | if "forced_addr" in config and "global_addr" not in config: 44 | raise cv.Invalid( 45 | "Forced_addr requires global_addr" 46 | ) 47 | 48 | FINAL_VALIDATE_SCHEMA = final_validate 49 | 50 | async def to_code(config): 51 | var = await switch.new_switch(config) 52 | await cg.register_component(var, config) 53 | 54 | pin = await cg.gpio_pin_expression(config[CONF_PIN]) 55 | cg.add(var.set_pin(pin)) 56 | 57 | if CONF_INTERLOCK in config: 58 | interlock = [] 59 | for it in config[CONF_INTERLOCK]: 60 | lock = await cg.get_variable(it) 61 | interlock.append(lock) 62 | cg.add(var.set_interlock(interlock)) 63 | cg.add(var.set_interlock_wait_time(config[CONF_INTERLOCK_WAIT_TIME])) 64 | -------------------------------------------------------------------------------- /components/light/esp_range_view.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esp_color_view.h" 4 | #include "esp_hsv_color.h" 5 | 6 | namespace esphome::light { 7 | 8 | int32_t interpret_index(int32_t index, int32_t size); 9 | 10 | class AddressableLight; 11 | class ESPRangeIterator; 12 | 13 | /** 14 | * A half-open range of LEDs, inclusive of the begin index and exclusive of the end index, using zero-based numbering. 15 | */ 16 | class ESPRangeView : public ESPColorSettable { 17 | public: 18 | ESPRangeView(AddressableLight *parent, int32_t begin, int32_t end) 19 | : parent_(parent), begin_(begin), end_(end < begin ? begin : end) {} 20 | ESPRangeView(const ESPRangeView &) = default; 21 | 22 | int32_t size() const { return this->end_ - this->begin_; } 23 | ESPColorView operator[](int32_t index) const; 24 | ESPRangeIterator begin(); 25 | ESPRangeIterator end(); 26 | 27 | void set(const Color &color) override; 28 | void set(const ESPHSVColor &color) { this->set(color.to_rgb()); } 29 | void set_red(uint8_t red) override; 30 | void set_green(uint8_t green) override; 31 | void set_blue(uint8_t blue) override; 32 | void set_white(uint8_t white) override; 33 | void set_effect_data(uint8_t effect_data) override; 34 | 35 | void fade_to_white(uint8_t amnt) override; 36 | void fade_to_black(uint8_t amnt) override; 37 | void lighten(uint8_t delta) override; 38 | void darken(uint8_t delta) override; 39 | 40 | ESPRangeView &operator=(const Color &rhs) { 41 | this->set(rhs); 42 | return *this; 43 | } 44 | ESPRangeView &operator=(const ESPColorView &rhs) { 45 | this->set(rhs.get()); 46 | return *this; 47 | } 48 | ESPRangeView &operator=(const ESPHSVColor &rhs) { 49 | this->set_hsv(rhs); 50 | return *this; 51 | } 52 | ESPRangeView &operator=(const ESPRangeView &rhs); 53 | 54 | protected: 55 | friend ESPRangeIterator; 56 | 57 | AddressableLight *parent_; 58 | int32_t begin_; 59 | int32_t end_; 60 | }; 61 | 62 | class ESPRangeIterator { 63 | public: 64 | ESPRangeIterator(const ESPRangeView &range, int32_t i) : range_(range), i_(i) {} 65 | ESPRangeIterator(const ESPRangeIterator &) = default; 66 | ESPRangeIterator operator++() { 67 | this->i_++; 68 | return *this; 69 | } 70 | bool operator!=(const ESPRangeIterator &other) const { return this->i_ != other.i_; } 71 | ESPColorView operator*() const; 72 | 73 | protected: 74 | ESPRangeView range_; 75 | int32_t i_; 76 | }; 77 | 78 | } // namespace esphome::light 79 | -------------------------------------------------------------------------------- /esphome-webserver/packages/v1/src/webserver-v1.js: -------------------------------------------------------------------------------- 1 | const source = new EventSource("/events"); 2 | 3 | source.addEventListener('log', function (e) { 4 | const log = document.getElementById("log"); 5 | let log_prefs = [ 6 | ["\u001b[1;31m", 'e'], 7 | ["\u001b[0;33m", 'w'], 8 | ["\u001b[0;32m", 'i'], 9 | ["\u001b[0;35m", 'c'], 10 | ["\u001b[0;36m", 'd'], 11 | ["\u001b[0;37m", 'v'], 12 | ]; 13 | 14 | let klass = ''; 15 | for (const log_pref of log_prefs){ 16 | if (e.data.startsWith(log_pref[0])) { 17 | klass = log_pref[1]; 18 | } 19 | } 20 | if (klass == ''){ 21 | log.innerHTML += e.data + '\n'; 22 | } 23 | log.innerHTML += '' + e.data.substr(7, e.data.length - 11) + "\n"; 24 | }); 25 | 26 | actions = [ 27 | ["switch", ["toggle"]], 28 | ["light", ["toggle"]], 29 | ["fan", ["toggle"]], 30 | ["cover", ["open", "close"]], 31 | ["button", ["press"]], 32 | ["lock", ["lock", "unlock", "open"]], 33 | ]; 34 | multi_actions = [ 35 | ["select", "option"], 36 | ["number", "value"], 37 | ]; 38 | 39 | source.addEventListener('state', function (e) { 40 | const data = JSON.parse(e.data); 41 | document.getElementById(data.id).children[1].innerText = data.state; 42 | }); 43 | 44 | const states = document.getElementById("states"); 45 | let i = 0, row; 46 | for (; row = states.rows[i]; i++) { 47 | if (!row.children[2].children.length) { 48 | continue; 49 | } 50 | 51 | for (const domain of actions){ 52 | if (row.classList.contains(domain[0])) { 53 | let id = row.id.substr(domain[0].length+1); 54 | for (let j=0;jsize()) + this->begin_; 14 | return (*this->parent_)[index]; 15 | } 16 | ESPRangeIterator ESPRangeView::begin() { return {*this, this->begin_}; } 17 | ESPRangeIterator ESPRangeView::end() { return {*this, this->end_}; } 18 | 19 | void ESPRangeView::set(const Color &color) { 20 | for (int32_t i = this->begin_; i < this->end_; i++) { 21 | (*this->parent_)[i] = color; 22 | } 23 | } 24 | 25 | void ESPRangeView::set_red(uint8_t red) { 26 | for (auto c : *this) 27 | c.set_red(red); 28 | } 29 | void ESPRangeView::set_green(uint8_t green) { 30 | for (auto c : *this) 31 | c.set_green(green); 32 | } 33 | void ESPRangeView::set_blue(uint8_t blue) { 34 | for (auto c : *this) 35 | c.set_blue(blue); 36 | } 37 | void ESPRangeView::set_white(uint8_t white) { 38 | for (auto c : *this) 39 | c.set_white(white); 40 | } 41 | void ESPRangeView::set_effect_data(uint8_t effect_data) { 42 | for (auto c : *this) 43 | c.set_effect_data(effect_data); 44 | } 45 | 46 | void ESPRangeView::fade_to_white(uint8_t amnt) { 47 | for (auto c : *this) 48 | c.fade_to_white(amnt); 49 | } 50 | void ESPRangeView::fade_to_black(uint8_t amnt) { 51 | for (auto c : *this) 52 | c.fade_to_black(amnt); 53 | } 54 | void ESPRangeView::lighten(uint8_t delta) { 55 | for (auto c : *this) 56 | c.lighten(delta); 57 | } 58 | void ESPRangeView::darken(uint8_t delta) { 59 | for (auto c : *this) 60 | c.darken(delta); 61 | } 62 | ESPRangeView &ESPRangeView::operator=(const ESPRangeView &rhs) { // NOLINT 63 | // If size doesn't match, error (todo warning) 64 | if (rhs.size() != this->size()) 65 | return *this; 66 | 67 | if (this->parent_ != rhs.parent_) { 68 | for (int32_t i = 0; i < this->size(); i++) 69 | (*this)[i].set(rhs[i].get()); 70 | return *this; 71 | } 72 | 73 | // If both equal, already done 74 | if (rhs.begin_ == this->begin_) 75 | return *this; 76 | 77 | if (rhs.begin_ > this->begin_) { 78 | // Copy from left 79 | for (int32_t i = 0; i < this->size(); i++) { 80 | (*this)[i].set(rhs[i].get()); 81 | } 82 | } else { 83 | // Copy from right 84 | for (int32_t i = this->size() - 1; i >= 0; i--) { 85 | (*this)[i].set(rhs[i].get()); 86 | } 87 | } 88 | 89 | return *this; 90 | } 91 | 92 | ESPColorView ESPRangeIterator::operator*() const { return this->range_.parent_->get(this->i_); } 93 | 94 | } // namespace esphome::light 95 | -------------------------------------------------------------------------------- /components/ddp/__init__.py: -------------------------------------------------------------------------------- 1 | import esphome.codegen as cg 2 | import esphome.config_validation as cv 3 | from esphome.components.light.types import AddressableLightEffect, LightEffect 4 | from esphome.components.light.effects import ( 5 | register_addressable_effect, 6 | register_rgb_effect, 7 | ) 8 | from esphome.const import CONF_ID, CONF_NAME 9 | 10 | DEPENDENCIES = ["network"] 11 | 12 | ddp_ns = cg.esphome_ns.namespace("ddp") 13 | DDPLightEffect = ddp_ns.class_("DDPLightEffect", LightEffect) 14 | DDPAddressableLightEffect = ddp_ns.class_( 15 | "DDPAddressableLightEffect", AddressableLightEffect 16 | ) 17 | DDPComponent = ddp_ns.class_("DDPComponent", cg.Component) 18 | 19 | DDP_SCALING = { 20 | "PIXEL": ddp_ns.DDP_SCALE_PIXEL, 21 | "STRIP": ddp_ns.DDP_SCALE_STRIP, 22 | "PACKET": ddp_ns.DDP_SCALE_PACKET, 23 | "MULTIPLY": ddp_ns.DDP_SCALE_MULTIPLY, 24 | "NONE": ddp_ns.DDP_NO_SCALING} 25 | 26 | CONF_DDP_ID = "ddp_id" 27 | CONF_DDP_TIMEOUT = "timeout" 28 | CONF_DDP_DIS_GAMMA = "disable_gamma" 29 | CONF_DDP_SCALING = "brightness_scaling" 30 | 31 | CONFIG_SCHEMA = cv.All( 32 | cv.Schema( 33 | { 34 | cv.GenerateID(): cv.declare_id(DDPComponent), 35 | } 36 | ), 37 | cv.only_with_arduino, 38 | ) 39 | 40 | 41 | async def to_code(config): 42 | var = cg.new_Pvariable(config[CONF_ID]) 43 | await cg.register_component(var, config) 44 | 45 | 46 | @register_rgb_effect( 47 | "ddp", 48 | DDPLightEffect, 49 | "DDP", 50 | { 51 | cv.GenerateID(CONF_DDP_ID): cv.use_id(DDPComponent), 52 | cv.Optional(CONF_DDP_TIMEOUT): cv.positive_time_period_milliseconds, 53 | cv.Optional(CONF_DDP_DIS_GAMMA): cv.boolean, 54 | cv.Optional(CONF_DDP_SCALING): cv.one_of(*DDP_SCALING, upper=True), 55 | }, 56 | ) 57 | @register_addressable_effect( 58 | "addressable_ddp", 59 | DDPAddressableLightEffect, 60 | "Addressable DDP", 61 | { 62 | cv.GenerateID(CONF_DDP_ID): cv.use_id(DDPComponent), 63 | cv.Optional(CONF_DDP_TIMEOUT): cv.positive_time_period_milliseconds, 64 | cv.Optional(CONF_DDP_DIS_GAMMA): cv.boolean, 65 | cv.Optional(CONF_DDP_SCALING): cv.one_of(*DDP_SCALING, upper=True), 66 | }, 67 | ) 68 | async def ddp_light_effect_to_code(config, effect_id): 69 | parent = await cg.get_variable(config[CONF_DDP_ID]) 70 | 71 | effect = cg.new_Pvariable(effect_id, config[CONF_NAME]) 72 | 73 | cg.add(effect.set_ddp(parent)) 74 | 75 | if CONF_DDP_TIMEOUT in config: 76 | cg.add(effect.set_timeout(config[CONF_DDP_TIMEOUT])) 77 | 78 | if CONF_DDP_DIS_GAMMA in config: 79 | cg.add(effect.set_disable_gamma(config[CONF_DDP_DIS_GAMMA])) 80 | 81 | if CONF_DDP_SCALING in config: 82 | cg.add(effect.set_scaling_mode(DDP_SCALING[config[CONF_DDP_SCALING]])) 83 | 84 | return effect 85 | -------------------------------------------------------------------------------- /components/esp8266/core.cpp: -------------------------------------------------------------------------------- 1 | #ifdef USE_ESP8266 2 | 3 | #include "core.h" 4 | #include "esphome/core/defines.h" 5 | #include "esphome/core/hal.h" 6 | #include "esphome/core/helpers.h" 7 | #include "preferences.h" 8 | #include 9 | #include 10 | 11 | namespace esphome { 12 | 13 | void IRAM_ATTR HOT yield() { ::yield(); } 14 | uint32_t IRAM_ATTR HOT millis() { return ::millis(); } 15 | void IRAM_ATTR HOT delay(uint32_t ms) { ::delay(ms); } 16 | uint32_t IRAM_ATTR HOT micros() { return ::micros(); } 17 | void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); } 18 | void arch_restart() { 19 | ESP.restart(); // NOLINT(readability-static-accessed-through-instance) 20 | // restart() doesn't always end execution 21 | while (true) { // NOLINT(clang-diagnostic-unreachable-code) 22 | yield(); 23 | } 24 | } 25 | void arch_init() {} 26 | void IRAM_ATTR HOT arch_feed_wdt() { 27 | ESP.wdtFeed(); // NOLINT(readability-static-accessed-through-instance) 28 | } 29 | 30 | uint8_t progmem_read_byte(const uint8_t *addr) { 31 | return pgm_read_byte(addr); // NOLINT 32 | } 33 | uint32_t IRAM_ATTR HOT arch_get_cpu_cycle_count() { 34 | return ESP.getCycleCount(); // NOLINT(readability-static-accessed-through-instance) 35 | } 36 | uint32_t arch_get_cpu_freq_hz() { return F_CPU; } 37 | 38 | void force_link_symbols() { 39 | // Tasmota uses magic bytes in the binary to check if an OTA firmware is compatible 40 | // with their settings - ESPHome uses a different settings system (that can also survive 41 | // erases). So set magic bytes indicating all tasmota versions are supported. 42 | // This only adds 12 bytes of binary size, which is an acceptable price to pay for easier support 43 | // for Tasmota. 44 | // https://github.com/arendst/Tasmota/blob/b05301b1497942167a015a6113b7f424e42942cd/tasmota/settings.ino#L346-L380 45 | // https://github.com/arendst/Tasmota/blob/b05301b1497942167a015a6113b7f424e42942cd/tasmota/i18n.h#L652-L654 46 | const static uint32_t TASMOTA_MAGIC_BYTES[] PROGMEM = {0x5AA55AA5, 0xFFFFFFFF, 0xA55AA55A}; 47 | // Force link symbol by using a volatile integer (GCC attribute used does not work because of LTO) 48 | volatile int x = 0; 49 | x = TASMOTA_MAGIC_BYTES[x]; 50 | } 51 | 52 | extern "C" void resetPins() { // NOLINT 53 | // Added in framework 2.7.0 54 | // usually this sets up all pins to be in INPUT mode 55 | // however, not strictly needed as we set up the pins properly 56 | // ourselves and this causes pins to toggle during reboot. 57 | force_link_symbols(); 58 | 59 | #ifdef USE_ESP8266_EARLY_PIN_INIT 60 | for (int i = 0; i < 16; i++) { 61 | uint8_t mode = progmem_read_byte(&ESPHOME_ESP8266_GPIO_INITIAL_MODE[i]); 62 | uint8_t level = progmem_read_byte(&ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[i]); 63 | if (mode != 255) 64 | pinMode(i, mode); // NOLINT 65 | if (level != 255) 66 | digitalWrite(i, level); // NOLINT 67 | } 68 | #endif 69 | } 70 | 71 | } // namespace esphome 72 | 73 | #endif // USE_ESP8266 74 | -------------------------------------------------------------------------------- /components/template/datetime/template_date.cpp: -------------------------------------------------------------------------------- 1 | #include "template_date.h" 2 | 3 | #ifdef USE_DATETIME_DATE 4 | 5 | #include "esphome/core/log.h" 6 | 7 | namespace esphome::template_ { 8 | 9 | static const char *const TAG = "template.date"; 10 | 11 | void TemplateDate::setup() { 12 | if (this->f_.has_value()) 13 | return; 14 | 15 | ESPTime state{}; 16 | 17 | if (!this->restore_value_) { 18 | state = this->initial_value_; 19 | } else { 20 | datetime::DateEntityRestoreState temp; 21 | this->pref_ = 22 | global_preferences->make_preference(194434030U ^ this->get_preference_hash()); 23 | if (this->pref_.load(&temp)) { 24 | temp.apply(this); 25 | return; 26 | } else { 27 | // set to inital value if loading from pref failed 28 | state = this->initial_value_; 29 | } 30 | } 31 | 32 | this->year_ = state.year; 33 | this->month_ = state.month; 34 | this->day_ = state.day_of_month; 35 | this->publish_state(); 36 | } 37 | 38 | void TemplateDate::update() { 39 | if (!this->f_.has_value()) 40 | return; 41 | 42 | auto val = this->f_(); 43 | if (val.has_value()) { 44 | this->year_ = val->year; 45 | this->month_ = val->month; 46 | this->day_ = val->day_of_month; 47 | this->publish_state(); 48 | } 49 | } 50 | 51 | void TemplateDate::control(const datetime::DateCall &call) { 52 | bool has_year = call.get_year().has_value(); 53 | bool has_month = call.get_month().has_value(); 54 | bool has_day = call.get_day().has_value(); 55 | 56 | ESPTime value = {}; 57 | if (has_year) 58 | value.year = *call.get_year(); 59 | 60 | if (has_month) 61 | value.month = *call.get_month(); 62 | 63 | if (has_day) 64 | value.day_of_month = *call.get_day(); 65 | 66 | this->set_trigger_->trigger(value); 67 | 68 | if (this->optimistic_) { 69 | if (has_year) 70 | this->year_ = *call.get_year(); 71 | if (has_month) 72 | this->month_ = *call.get_month(); 73 | if (has_day) 74 | this->day_ = *call.get_day(); 75 | this->publish_state(); 76 | } 77 | 78 | if (this->restore_value_) { 79 | datetime::DateEntityRestoreState temp = {}; 80 | if (has_year) { 81 | temp.year = *call.get_year(); 82 | } else { 83 | temp.year = this->year_; 84 | } 85 | if (has_month) { 86 | temp.month = *call.get_month(); 87 | } else { 88 | temp.month = this->month_; 89 | } 90 | if (has_day) { 91 | temp.day = *call.get_day(); 92 | } else { 93 | temp.day = this->day_; 94 | } 95 | 96 | this->pref_.save(&temp); 97 | } 98 | } 99 | 100 | void TemplateDate::dump_config() { 101 | LOG_DATETIME_DATE("", "Template Date", this); 102 | ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_)); 103 | LOG_UPDATE_INTERVAL(this); 104 | } 105 | 106 | } // namespace esphome::template_ 107 | 108 | #endif // USE_DATETIME_DATE 109 | -------------------------------------------------------------------------------- /components/total_daily_energy/total_daily_energy.cpp: -------------------------------------------------------------------------------- 1 | #include "total_daily_energy.h" 2 | #include "esphome/core/log.h" 3 | 4 | namespace esphome { 5 | namespace total_daily_energy { 6 | 7 | static const char *const TAG = "total_daily_energy"; 8 | 9 | void TotalDailyEnergy::setup() { 10 | float initial_value = 0; 11 | 12 | if (this->restore_) { 13 | 14 | if ( this->has_global_forced_addr ) { id(global_forced_addr) = this->forced_addr; } 15 | if ( this->has_forced_hash ) { 16 | this->pref_ = global_preferences->make_preference(this->forced_hash); 17 | } else { 18 | this->pref_ = global_preferences->make_preference(this->get_preference_hash()); 19 | } 20 | 21 | this->pref_.load(&initial_value); 22 | } 23 | this->publish_state_and_save(initial_value); 24 | 25 | this->last_update_ = millis(); 26 | 27 | this->parent_->add_on_state_callback([this](float state) { this->process_new_state_(state); }); 28 | } 29 | 30 | void TotalDailyEnergy::dump_config() { LOG_SENSOR("", "Total Daily Energy", this); } 31 | 32 | void TotalDailyEnergy::loop() { 33 | 34 | if (this->manual_zero) { 35 | this->manual_zero = false; 36 | this->total_energy_ = 0; 37 | this->publish_state_and_save(0); 38 | } 39 | 40 | if (this->manual_control) 41 | return; 42 | 43 | auto t = this->time_->now(); 44 | if (!t.is_valid()) 45 | return; 46 | 47 | if (this->last_day_of_year_ == 0) { 48 | this->last_day_of_year_ = t.day_of_year; 49 | return; 50 | } 51 | 52 | if (t.day_of_year != this->last_day_of_year_) { 53 | this->last_day_of_year_ = t.day_of_year; 54 | this->total_energy_ = 0; 55 | this->publish_state_and_save(0); 56 | } 57 | } 58 | 59 | void TotalDailyEnergy::publish_state_and_save(float state) { 60 | this->total_energy_ = state; 61 | this->publish_state(state); 62 | if (this->restore_) { 63 | this->pref_.save(&state); 64 | } 65 | } 66 | 67 | void TotalDailyEnergy::process_new_state_(float state) { 68 | if (std::isnan(state)) 69 | return; 70 | const uint32_t now = millis(); 71 | const float old_state = this->last_power_state_; 72 | const float new_state = state; 73 | float delta_hours = (now - this->last_update_) / 1000.0f / 60.0f / 60.0f; 74 | float delta_energy = 0.0f; 75 | switch (this->method_) { 76 | case TOTAL_DAILY_ENERGY_METHOD_TRAPEZOID: 77 | delta_energy = delta_hours * (old_state + new_state) / 2.0; 78 | break; 79 | case TOTAL_DAILY_ENERGY_METHOD_LEFT: 80 | delta_energy = delta_hours * old_state; 81 | break; 82 | case TOTAL_DAILY_ENERGY_METHOD_RIGHT: 83 | delta_energy = delta_hours * new_state; 84 | break; 85 | } 86 | this->last_power_state_ = new_state; 87 | this->last_update_ = now; 88 | this->publish_state_and_save(this->total_energy_ + delta_energy); 89 | } 90 | 91 | void TotalDailyEnergy::zero_total_energy() { 92 | this->manual_zero = true; 93 | this->manual_control = true; 94 | } 95 | 96 | } // namespace total_daily_energy 97 | } // namespace esphome 98 | -------------------------------------------------------------------------------- /components/template/datetime/template_time.cpp: -------------------------------------------------------------------------------- 1 | #include "template_time.h" 2 | 3 | #ifdef USE_DATETIME_TIME 4 | 5 | #include "esphome/core/log.h" 6 | 7 | namespace esphome::template_ { 8 | 9 | static const char *const TAG = "template.time"; 10 | 11 | void TemplateTime::setup() { 12 | if (this->f_.has_value()) 13 | return; 14 | 15 | ESPTime state{}; 16 | 17 | if (!this->restore_value_) { 18 | state = this->initial_value_; 19 | } else { 20 | datetime::TimeEntityRestoreState temp; 21 | this->pref_ = 22 | global_preferences->make_preference(194434060U ^ this->get_preference_hash()); 23 | if (this->pref_.load(&temp)) { 24 | temp.apply(this); 25 | return; 26 | } else { 27 | // set to inital value if loading from pref failed 28 | state = this->initial_value_; 29 | } 30 | } 31 | 32 | this->hour_ = state.hour; 33 | this->minute_ = state.minute; 34 | this->second_ = state.second; 35 | this->publish_state(); 36 | } 37 | 38 | void TemplateTime::update() { 39 | if (!this->f_.has_value()) 40 | return; 41 | 42 | auto val = this->f_(); 43 | if (val.has_value()) { 44 | this->hour_ = val->hour; 45 | this->minute_ = val->minute; 46 | this->second_ = val->second; 47 | this->publish_state(); 48 | } 49 | } 50 | 51 | void TemplateTime::control(const datetime::TimeCall &call) { 52 | bool has_hour = call.get_hour().has_value(); 53 | bool has_minute = call.get_minute().has_value(); 54 | bool has_second = call.get_second().has_value(); 55 | 56 | ESPTime value = {}; 57 | if (has_hour) 58 | value.hour = *call.get_hour(); 59 | 60 | if (has_minute) 61 | value.minute = *call.get_minute(); 62 | 63 | if (has_second) 64 | value.second = *call.get_second(); 65 | 66 | this->set_trigger_->trigger(value); 67 | 68 | if (this->optimistic_) { 69 | if (has_hour) 70 | this->hour_ = *call.get_hour(); 71 | if (has_minute) 72 | this->minute_ = *call.get_minute(); 73 | if (has_second) 74 | this->second_ = *call.get_second(); 75 | this->publish_state(); 76 | } 77 | 78 | if (this->restore_value_) { 79 | datetime::TimeEntityRestoreState temp = {}; 80 | if (has_hour) { 81 | temp.hour = *call.get_hour(); 82 | } else { 83 | temp.hour = this->hour_; 84 | } 85 | if (has_minute) { 86 | temp.minute = *call.get_minute(); 87 | } else { 88 | temp.minute = this->minute_; 89 | } 90 | if (has_second) { 91 | temp.second = *call.get_second(); 92 | } else { 93 | temp.second = this->second_; 94 | } 95 | 96 | this->pref_.save(&temp); 97 | } 98 | } 99 | 100 | void TemplateTime::dump_config() { 101 | LOG_DATETIME_TIME("", "Template Time", this); 102 | ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_)); 103 | LOG_UPDATE_INTERVAL(this); 104 | } 105 | 106 | } // namespace esphome::template_ 107 | 108 | #endif // USE_DATETIME_TIME 109 | -------------------------------------------------------------------------------- /components/web_server/list_entities.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/defines.h" 4 | #ifdef USE_WEBSERVER 5 | #include "esphome/core/component.h" 6 | #include "esphome/core/component_iterator.h" 7 | namespace esphome { 8 | #ifdef USE_ESP32 9 | namespace web_server_idf { 10 | class AsyncEventSource; 11 | } 12 | #endif 13 | namespace web_server { 14 | 15 | #if !defined(USE_ESP32) && defined(USE_ARDUINO) 16 | class DeferredUpdateEventSource; 17 | #endif 18 | class WebServer; 19 | 20 | class ListEntitiesIterator : public ComponentIterator { 21 | public: 22 | #ifdef USE_ESP32 23 | ListEntitiesIterator(const WebServer *ws, esphome::web_server_idf::AsyncEventSource *es); 24 | #elif defined(USE_ARDUINO) 25 | ListEntitiesIterator(const WebServer *ws, DeferredUpdateEventSource *es); 26 | #endif 27 | virtual ~ListEntitiesIterator(); 28 | #ifdef USE_BINARY_SENSOR 29 | bool on_binary_sensor(binary_sensor::BinarySensor *obj) override; 30 | #endif 31 | #ifdef USE_COVER 32 | bool on_cover(cover::Cover *obj) override; 33 | #endif 34 | #ifdef USE_FAN 35 | bool on_fan(fan::Fan *obj) override; 36 | #endif 37 | #ifdef USE_LIGHT 38 | bool on_light(light::LightState *obj) override; 39 | #endif 40 | #ifdef USE_SENSOR 41 | bool on_sensor(sensor::Sensor *obj) override; 42 | #endif 43 | #ifdef USE_SWITCH 44 | bool on_switch(switch_::Switch *obj) override; 45 | #endif 46 | #ifdef USE_BUTTON 47 | bool on_button(button::Button *obj) override; 48 | #endif 49 | #ifdef USE_TEXT_SENSOR 50 | bool on_text_sensor(text_sensor::TextSensor *obj) override; 51 | #endif 52 | #ifdef USE_CLIMATE 53 | bool on_climate(climate::Climate *obj) override; 54 | #endif 55 | #ifdef USE_NUMBER 56 | bool on_number(number::Number *obj) override; 57 | #endif 58 | #ifdef USE_DATETIME_DATE 59 | bool on_date(datetime::DateEntity *obj) override; 60 | #endif 61 | #ifdef USE_DATETIME_TIME 62 | bool on_time(datetime::TimeEntity *obj) override; 63 | #endif 64 | #ifdef USE_DATETIME_DATETIME 65 | bool on_datetime(datetime::DateTimeEntity *obj) override; 66 | #endif 67 | #ifdef USE_TEXT 68 | bool on_text(text::Text *obj) override; 69 | #endif 70 | #ifdef USE_SELECT 71 | bool on_select(select::Select *obj) override; 72 | #endif 73 | #ifdef USE_LOCK 74 | bool on_lock(lock::Lock *obj) override; 75 | #endif 76 | #ifdef USE_VALVE 77 | bool on_valve(valve::Valve *obj) override; 78 | #endif 79 | #ifdef USE_ALARM_CONTROL_PANEL 80 | bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *obj) override; 81 | #endif 82 | #ifdef USE_EVENT 83 | bool on_event(event::Event *obj) override; 84 | #endif 85 | #ifdef USE_UPDATE 86 | bool on_update(update::UpdateEntity *obj) override; 87 | #endif 88 | bool completed() { return this->state_ == IteratorState::NONE; } 89 | 90 | protected: 91 | const WebServer *web_server_; 92 | #ifdef USE_ESP32 93 | esphome::web_server_idf::AsyncEventSource *events_; 94 | #elif USE_ARDUINO 95 | DeferredUpdateEventSource *events_; 96 | #endif 97 | }; 98 | 99 | } // namespace web_server 100 | } // namespace esphome 101 | #endif 102 | -------------------------------------------------------------------------------- /esphome-webserver/packages/v3/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import gzipPlugin from "rollup-plugin-gzip"; 3 | import minifyHTML from "rollup-plugin-minify-html-template-literals"; 4 | import { brotliCompressSync } from "zlib"; 5 | import { nodeResolve } from "@rollup/plugin-node-resolve"; 6 | import loadVersion from "vite-plugin-package-version"; 7 | import { viteSingleFile } from "vite-plugin-singlefile"; 8 | import { minifyHtml as ViteMinifyHtml } from "vite-plugin-html"; 9 | import stripBanner from "rollup-plugin-strip-banner"; 10 | import replace from "@rollup/plugin-replace"; 11 | 12 | const proxy_target = process.env.PROXY_TARGET || "http://nodemcu.local"; 13 | 14 | export default defineConfig({ 15 | clearScreen: false, 16 | plugins: [ 17 | { 18 | ...nodeResolve({ exportConditions: ["development"] }), 19 | enforce: "pre", 20 | apply: "start", 21 | }, 22 | stripBanner(), 23 | loadVersion(), 24 | { ...minifyHTML(), enforce: "pre", apply: "build" }, 25 | // 26 | { 27 | ...ViteMinifyHtml({ removeComments: true }), 28 | enforce: "post", 29 | apply: "build", 30 | }, 31 | replace({ 32 | "@license": "license", 33 | "Value passed to 'css' function must be a 'css' function result:": 34 | "use css function", 35 | "Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.": 36 | "Use unsafeCSS", 37 | delimiters: ["", ""], 38 | preventAssignment: true, 39 | }), 40 | viteSingleFile(), 41 | { 42 | ...gzipPlugin({ 43 | filter: /\.(js|css|html|svg)$/, 44 | additionalFiles: [], 45 | customCompression: (content) => 46 | brotliCompressSync(Buffer.from(content)), 47 | fileName: ".br", 48 | }), 49 | enforce: "post", 50 | apply: "build", 51 | }, 52 | { 53 | ...gzipPlugin({ filter: /\.(js|css|html|svg)$/ }), 54 | enforce: "post", 55 | apply: "build", 56 | }, 57 | ], 58 | build: { 59 | brotliSize: false, 60 | // cssCodeSplit: true, 61 | outDir: "../../_static/v3", 62 | polyfillModulePreload: false, 63 | rollupOptions: { 64 | output: { 65 | manualChunks: (chunk) => { 66 | return "vendor"; 67 | }, // create one js bundle, 68 | chunkFileNames: "[name].js", 69 | assetFileNames: "www[extname]", 70 | entryFileNames: "www.js", 71 | }, 72 | }, 73 | }, 74 | server: { 75 | open: "/", // auto open browser in dev mode 76 | host: true, // dev on local and network 77 | port: 5001, 78 | strictPort: true, 79 | proxy: { 80 | "/light": proxy_target, 81 | "/select": proxy_target, 82 | "/cover": proxy_target, 83 | "/switch": proxy_target, 84 | "/button": proxy_target, 85 | "/fan": proxy_target, 86 | "/lock": proxy_target, 87 | "/number": proxy_target, 88 | "/climate": proxy_target, 89 | "/events": proxy_target, 90 | "/text": proxy_target, 91 | "/date": proxy_target, 92 | "/time": proxy_target, 93 | }, 94 | }, 95 | }); 96 | -------------------------------------------------------------------------------- /esphome-webserver/packages/captive-portal/src/stylesheet.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: inherit; 3 | } 4 | 5 | div, 6 | input { 7 | padding: 5px; 8 | font-size: 1em; 9 | } 10 | 11 | input { 12 | width: 95%; 13 | } 14 | 15 | body { 16 | text-align: center; 17 | font-family: sans-serif; 18 | } 19 | 20 | button { 21 | border: 0; 22 | border-radius: 0.3rem; 23 | background-color: #1fa3ec; 24 | color: #fff; 25 | line-height: 2.4rem; 26 | font-size: 1.2rem; 27 | width: 100%; 28 | padding: 0; 29 | cursor: pointer; 30 | } 31 | 32 | main { 33 | text-align: left; 34 | display: inline-block; 35 | min-width: 260px; 36 | } 37 | 38 | .network { 39 | display: flex; 40 | justify-content: space-between; 41 | align-items: center; 42 | } 43 | 44 | .network-left { 45 | display: flex; 46 | align-items: center; 47 | } 48 | 49 | .network-ssid { 50 | margin-bottom: -7px; 51 | margin-left: 10px; 52 | } 53 | 54 | aside { 55 | border: 1px solid; 56 | margin: 10px 0px; 57 | padding: 15px 10px; 58 | color: #4f8a10; 59 | background-color: #dff2bf; 60 | display: none 61 | } 62 | 63 | i { 64 | width: 24px; 65 | height: 24px; 66 | } 67 | 68 | i.lock { 69 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 17a2 2 0 0 0 2-2 2 2 0 0 0-2-2 2 2 0 0 0-2 2 2 2 0 0 0 2 2m6-9a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V10a2 2 0 0 1 2-2h1V6a5 5 0 0 1 5-5 5 5 0 0 1 5 5v2h1m-6-5a3 3 0 0 0-3 3v2h6V6a3 3 0 0 0-3-3z'/%3E%3C/svg%3E"); 70 | } 71 | 72 | i.sig1 { 73 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' %3E%3Cpath d='M12 3A18.9 18.9 0 0 0 .38 7C4.41 12.06 7.89 16.37 12 21.5L23.65 7C20.32 4.41 16.22 3 12 3m0 2c3.07 0 6.09.86 8.71 2.45l-5.1 6.36a8.43 8.43 0 0 0-7.22-.01L3.27 7.44A16.94 16.94 0 0 1 12 5z'/%3E%3C/svg%3E"); 74 | } 75 | i.sig2 { 76 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' %3E%3Cpath d='M12 3A18.9 18.9 0 0 0 .38 7C4.41 12.06 7.89 16.37 12 21.5L23.65 7C20.32 4.41 16.22 3 12 3m0 2c3.07 0 6.09.86 8.71 2.45l-3.21 3.98a11.32 11.32 0 0 0-11 0L3.27 7.44A16.94 16.94 0 0 1 12 5z'/%3E%3C/svg%3E"); 77 | } 78 | i.sig3 { 79 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' %3E%3Cpath d='M12 3A18.9 18.9 0 0 0 .38 7C4.41 12.06 7.89 16.37 12 21.5L23.65 7C20.32 4.41 16.22 3 12 3m0 2c3.07 0 6.09.86 8.71 2.45l-1.94 2.43A13.6 13.6 0 0 0 12 8C9 8 6.68 9 5.21 9.84l-1.94-2.4A16.94 16.94 0 0 1 12 5z'/%3E%3C/svg%3E"); 80 | } 81 | i.sig4 { 82 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' %3E%3Cpath d='M12 3A18.9 18.9 0 0 0 .38 7C4.41 12.06 7.89 16.37 12 21.5L23.65 7C20.32 4.41 16.22 3 12 3z'/%3E%3C/svg%3E"); 83 | } 84 | 85 | i.sig { 86 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' %3E%3Cpath d='M12 3A18.9 18.9 0 0 0 .38 7C4.41 12.06 7.89 16.37 12 21.5L23.65 7C20.32 4.41 16.22 3 12 3m0 2c3.07 0 6.09.86 8.71 2.45l-5.1 6.36a8.43 8.43 0 0 0-7.22-.01L3.27 7.44A16.94 16.94 0 0 1 12 5z'/%3E%3C/svg%3E"); 87 | } 88 | 89 | th, td { 90 | padding: 10px; 91 | } -------------------------------------------------------------------------------- /esphome-webserver/packages/v2/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import gzipPlugin from "rollup-plugin-gzip"; 3 | import minifyHTML from "rollup-plugin-minify-html-template-literals"; 4 | import { brotliCompressSync } from "zlib"; 5 | import { nodeResolve } from "@rollup/plugin-node-resolve"; 6 | import loadVersion from "vite-plugin-package-version"; 7 | import { viteSingleFile } from "vite-plugin-singlefile"; 8 | import { minifyHtml as ViteMinifyHtml } from "vite-plugin-html"; 9 | import stripBanner from "rollup-plugin-strip-banner"; 10 | import replace from "@rollup/plugin-replace"; 11 | 12 | const proxy_target = process.env.PROXY_TARGET || "http://nodemcu.local"; 13 | 14 | export default defineConfig({ 15 | clearScreen: false, 16 | plugins: [ 17 | { 18 | ...nodeResolve({ exportConditions: ["development"] }), 19 | enforce: "pre", 20 | apply: "start", 21 | }, 22 | stripBanner(), 23 | loadVersion(), 24 | { ...minifyHTML(), enforce: "pre", apply: "build" }, 25 | { 26 | ...ViteMinifyHtml({ removeComments: true }), 27 | enforce: "post", 28 | apply: "build", 29 | }, 30 | replace({ 31 | "@license": "license", 32 | "Value passed to 'css' function must be a 'css' function result:": 33 | "use css function", 34 | "Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.": 35 | "Use unsafeCSS", 36 | delimiters: ["", ""], 37 | preventAssignment: true, 38 | }), 39 | viteSingleFile(), 40 | { 41 | ...gzipPlugin({ 42 | filter: /\.(js|css|html|svg)$/, 43 | additionalFiles: [], 44 | customCompression: (content) => 45 | brotliCompressSync(Buffer.from(content)), 46 | fileName: ".br", 47 | }), 48 | enforce: "post", 49 | apply: "build", 50 | }, 51 | { 52 | ...gzipPlugin({ filter: /\.(js|css|html|svg)$/ }), 53 | enforce: "post", 54 | apply: "build", 55 | }, 56 | ], 57 | build: { 58 | brotliSize: false, 59 | // cssCodeSplit: true, 60 | outDir: "../../_static/v2", 61 | polyfillModulePreload: false, 62 | rollupOptions: { 63 | output: { 64 | manualChunks: (chunk) => { 65 | return "vendor"; 66 | }, // create one js bundle, 67 | chunkFileNames: "[name].js", 68 | assetFileNames: "www[extname]", 69 | entryFileNames: "www.js", 70 | }, 71 | }, 72 | }, 73 | server: { 74 | open: "/", // auto open browser in dev mode 75 | host: true, // dev on local and network 76 | port: 5001, 77 | strictPort: true, 78 | proxy: { 79 | "/light": proxy_target, 80 | "/select": proxy_target, 81 | "/cover": proxy_target, 82 | "/switch": proxy_target, 83 | "/button": proxy_target, 84 | "/fan": proxy_target, 85 | "/lock": proxy_target, 86 | "/number": proxy_target, 87 | "/climate": proxy_target, 88 | "/events": proxy_target, 89 | "/text": proxy_target, 90 | "/date": proxy_target, 91 | "/time": proxy_target, 92 | "/valve": proxy_target, 93 | }, 94 | }, 95 | }); 96 | -------------------------------------------------------------------------------- /components/template/text/template_text.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/components/text/text.h" 4 | #include "esphome/core/automation.h" 5 | #include "esphome/core/component.h" 6 | #include "esphome/core/preferences.h" 7 | #include "esphome/core/template_lambda.h" 8 | 9 | namespace esphome::template_ { 10 | 11 | // We keep this separate so we don't have to template and duplicate 12 | // the text input for each different size flash allocation. 13 | class TemplateTextSaverBase { 14 | public: 15 | virtual bool save(const std::string &value) { return true; } 16 | 17 | virtual void setup(uint32_t id, std::string &value) {} 18 | 19 | protected: 20 | ESPPreferenceObject pref_; 21 | std::string prev_; 22 | }; 23 | 24 | template class TextSaver : public TemplateTextSaverBase { 25 | public: 26 | bool save(const std::string &value) override { 27 | int diff = value.compare(this->prev_); 28 | if (diff != 0) { 29 | // If string is bigger than the allocation, do not save it. 30 | // We don't need to waste ram setting prev_value either. 31 | int size = value.size(); 32 | if (size <= SZ) { 33 | // Make it into a length prefixed thing 34 | unsigned char temp[SZ + 1]; 35 | memcpy(temp + 1, value.c_str(), size); 36 | // SZ should be pre checked at the schema level, it can't go past the char range. 37 | temp[0] = ((unsigned char) size); 38 | this->pref_.save(&temp); 39 | this->prev_.assign(value); 40 | return true; 41 | } 42 | } 43 | return false; 44 | } 45 | 46 | // Make the preference object. Fill the provided location with the saved data 47 | // If it is available, else leave it alone 48 | void setup(uint32_t id, std::string &value) override { 49 | this->pref_ = global_preferences->make_preference(id); 50 | 51 | char temp[SZ + 1]; 52 | bool hasdata = this->pref_.load(&temp); 53 | 54 | if (hasdata) { 55 | value.assign(temp + 1, (size_t) temp[0]); 56 | } 57 | 58 | this->prev_.assign(value); 59 | } 60 | }; 61 | 62 | class TemplateText final : public text::Text, public PollingComponent { 63 | public: 64 | template void set_template(F &&f) { this->f_.set(std::forward(f)); } 65 | 66 | void setup() override; 67 | void update() override; 68 | void dump_config() override; 69 | float get_setup_priority() const override { return setup_priority::HARDWARE; } 70 | 71 | Trigger *get_set_trigger() const { return this->set_trigger_; } 72 | void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } 73 | void set_initial_value(const std::string &initial_value) { this->initial_value_ = initial_value; } 74 | void set_value_saver(TemplateTextSaverBase *restore_value_saver) { this->pref_ = restore_value_saver; } 75 | 76 | protected: 77 | void control(const std::string &value) override; 78 | bool optimistic_ = false; 79 | std::string initial_value_; 80 | Trigger *set_trigger_ = new Trigger(); 81 | TemplateLambda f_{}; 82 | 83 | TemplateTextSaverBase *pref_ = nullptr; 84 | }; 85 | 86 | } // namespace esphome::template_ 87 | -------------------------------------------------------------------------------- /esphome-webserver/packages/captive-portal/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |

Found Wi-Fi Networks

13 | 17 | 18 | 19 | 20 |

Wi-Fi Credentials:

21 |
22 |
23 |
24 |
25 | 26 |
27 |
28 |
29 |

Show password:

30 |

31 |
32 |
33 |
34 |

Device Parameters

35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 |
Hostname
MAC Address
Hard-Coded SSID
Software-Configured SSID
Free Space for OTA
ESPHome Version
Project Name
Project Version
69 |
70 |
71 |
72 |

OTA Update

73 | **** DO NOT USE TASMOTA-MINIMAL.BIN or .BIN.GZ.
74 | **** Use tasmota.bin.gz or tasmota-lite.bin.gz.

75 | **** Do not use any WLED firmware.
76 | **** KAUF bulbs have WLED support built in.

77 |
78 | 79 | 80 |
81 |
82 | 83 | 84 | 85 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /components/switch/automation.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/component.h" 4 | #include "esphome/core/automation.h" 5 | #include "esphome/components/switch/switch.h" 6 | 7 | namespace esphome { 8 | namespace switch_ { 9 | 10 | template class TurnOnAction : public Action { 11 | public: 12 | explicit TurnOnAction(Switch *a_switch) : switch_(a_switch) {} 13 | 14 | void play(const Ts &...x) override { this->switch_->turn_on(); } 15 | 16 | protected: 17 | Switch *switch_; 18 | }; 19 | 20 | template class TurnOffAction : public Action { 21 | public: 22 | explicit TurnOffAction(Switch *a_switch) : switch_(a_switch) {} 23 | 24 | void play(const Ts &...x) override { this->switch_->turn_off(); } 25 | 26 | protected: 27 | Switch *switch_; 28 | }; 29 | 30 | template class ToggleAction : public Action { 31 | public: 32 | explicit ToggleAction(Switch *a_switch) : switch_(a_switch) {} 33 | 34 | void play(const Ts &...x) override { this->switch_->toggle(); } 35 | 36 | protected: 37 | Switch *switch_; 38 | }; 39 | 40 | template class ControlAction : public Action { 41 | public: 42 | explicit ControlAction(Switch *a_switch) : switch_(a_switch) {} 43 | 44 | TEMPLATABLE_VALUE(bool, state) 45 | 46 | void play(const Ts &...x) override { 47 | auto state = this->state_.optional_value(x...); 48 | if (state.has_value()) { 49 | this->switch_->control(*state); 50 | } 51 | } 52 | 53 | protected: 54 | Switch *switch_; 55 | }; 56 | 57 | template class SwitchCondition : public Condition { 58 | public: 59 | SwitchCondition(Switch *parent, bool state) : parent_(parent), state_(state) {} 60 | bool check(const Ts &...x) override { return this->parent_->state == this->state_; } 61 | 62 | protected: 63 | Switch *parent_; 64 | bool state_; 65 | }; 66 | 67 | class SwitchStateTrigger : public Trigger { 68 | public: 69 | SwitchStateTrigger(Switch *a_switch) { 70 | a_switch->add_on_state_callback([this](bool state) { this->trigger(state); }); 71 | } 72 | }; 73 | 74 | class SwitchTurnOnTrigger : public Trigger<> { 75 | public: 76 | SwitchTurnOnTrigger(Switch *a_switch) { 77 | a_switch->add_on_state_callback([this](bool state) { 78 | if (state) { 79 | this->trigger(); 80 | } 81 | }); 82 | } 83 | }; 84 | 85 | class SwitchTurnOffTrigger : public Trigger<> { 86 | public: 87 | SwitchTurnOffTrigger(Switch *a_switch) { 88 | a_switch->add_on_state_callback([this](bool state) { 89 | if (!state) { 90 | this->trigger(); 91 | } 92 | }); 93 | } 94 | }; 95 | 96 | template class SwitchPublishAction : public Action { 97 | public: 98 | SwitchPublishAction(Switch *a_switch) : switch_(a_switch) {} 99 | TEMPLATABLE_VALUE(bool, state) 100 | 101 | void play(const Ts &...x) override { this->switch_->publish_state(this->state_.value(x...)); } 102 | 103 | protected: 104 | Switch *switch_; 105 | }; 106 | 107 | } // namespace switch_ 108 | } // namespace esphome 109 | -------------------------------------------------------------------------------- /components/web_server/ota/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import esphome.codegen as cg 4 | from esphome.components.esp32 import add_idf_component 5 | from esphome.components.ota import BASE_OTA_SCHEMA, OTAComponent, ota_to_code 6 | from esphome.config_helpers import merge_config 7 | import esphome.config_validation as cv 8 | from esphome.const import CONF_ID, CONF_OTA, CONF_PLATFORM, CONF_WEB_SERVER 9 | from esphome.core import CORE, coroutine_with_priority 10 | from esphome.coroutine import CoroPriority 11 | import esphome.final_validate as fv 12 | from esphome.types import ConfigType 13 | 14 | _LOGGER = logging.getLogger(__name__) 15 | 16 | CODEOWNERS = ["@esphome/core"] 17 | DEPENDENCIES = ["network", "web_server_base"] 18 | 19 | web_server_ns = cg.esphome_ns.namespace("web_server") 20 | WebServerOTAComponent = web_server_ns.class_("WebServerOTAComponent", OTAComponent) 21 | 22 | 23 | def _web_server_ota_final_validate(config: ConfigType) -> None: 24 | """Merge multiple web_server OTA instances into one. 25 | 26 | Multiple web_server OTA instances register duplicate HTTP handlers for /update, 27 | causing undefined behavior. Merge them into a single instance. 28 | """ 29 | full_conf = fv.full_config.get() 30 | ota_confs = full_conf.get(CONF_OTA, []) 31 | 32 | web_server_ota_configs: list[ConfigType] = [] 33 | other_ota_configs: list[ConfigType] = [] 34 | 35 | for ota_conf in ota_confs: 36 | if ota_conf.get(CONF_PLATFORM) == CONF_WEB_SERVER: 37 | web_server_ota_configs.append(ota_conf) 38 | else: 39 | other_ota_configs.append(ota_conf) 40 | 41 | if len(web_server_ota_configs) <= 1: 42 | return 43 | 44 | # Merge all web_server OTA configs into the first one 45 | merged = web_server_ota_configs[0] 46 | for ota_conf in web_server_ota_configs[1:]: 47 | # Validate that IDs are consistent if manually specified 48 | if ( 49 | merged[CONF_ID].is_manual 50 | and ota_conf[CONF_ID].is_manual 51 | and merged[CONF_ID] != ota_conf[CONF_ID] 52 | ): 53 | raise cv.Invalid( 54 | f"Found multiple web_server OTA configurations but {CONF_ID} is inconsistent" 55 | ) 56 | merged = merge_config(merged, ota_conf) 57 | 58 | _LOGGER.warning( 59 | "Found and merged %d web_server OTA configurations into one instance", 60 | len(web_server_ota_configs), 61 | ) 62 | 63 | # Replace OTA configs with merged web_server + other OTA platforms 64 | other_ota_configs.append(merged) 65 | full_conf[CONF_OTA] = other_ota_configs 66 | fv.full_config.set(full_conf) 67 | 68 | 69 | CONFIG_SCHEMA = ( 70 | cv.Schema( 71 | { 72 | cv.GenerateID(): cv.declare_id(WebServerOTAComponent), 73 | } 74 | ) 75 | .extend(BASE_OTA_SCHEMA) 76 | .extend(cv.COMPONENT_SCHEMA) 77 | ) 78 | 79 | FINAL_VALIDATE_SCHEMA = _web_server_ota_final_validate 80 | 81 | 82 | @coroutine_with_priority(CoroPriority.WEB_SERVER_OTA) 83 | async def to_code(config): 84 | var = cg.new_Pvariable(config[CONF_ID]) 85 | await ota_to_code(var, config) 86 | await cg.register_component(var, config) 87 | cg.add_define("USE_WEBSERVER_OTA") 88 | if CORE.is_esp32: 89 | add_idf_component(name="zorxx/multipart-parser", ref="1.0.1") 90 | -------------------------------------------------------------------------------- /esphome-webserver/README.md: -------------------------------------------------------------------------------- 1 | # esphome-webserver 2 | A Lit Element web component htm webserver for esphome devices. This is a fork of sorts modified for Kaufman Home Automation products. The original can be found at https://github.com/esphome/esphome-webserver 3 | 4 | ### Features 5 | 6 | - 30 sec heartbeat showing node connection is active 7 | - Built with Lit Element web components 8 | - Completely standalone - no other external dependencies 9K compressed 9 | - Light and Dark themes 10 | - Primary theme - currently light blue - can be overridden 11 | - Embedded ESP home logo svg 12 | - Entities are discovered and display 13 | - No css fetch - index page fetches one js file 14 | 15 | dark scheme desktop: 16 | ==================== 17 | ![web_server-v2](https://user-images.githubusercontent.com/5050824/141174356-789cc160-46a1-43fc-9a86-ed5a764c35d7.png) 18 | 19 | Light scheme on mobile: 20 | ======================= 21 | ![image](https://user-images.githubusercontent.com/5050824/141175240-95b5b74e-d8c8-48bc-9d6d-053ebeaf8910.png) 22 | 23 | ### Near future: 24 | 25 | - [ ] Support for compressed files in flash for Standalone no internet nodes 26 | - [ ] Add Climate 27 | - [x] Add Select drop list 28 | - [ ] Add Number editing 29 | - [ ] Potentially use an optional card layout instead of a table 30 | 31 | ## Example entry for `config.yaml`: 32 | 33 | ```yaml 34 | web_server: 35 | port: 80 36 | css_url: "" 37 | js_url: https://esphome.io/_static/v2/www.js 38 | version: 2 39 | ``` 40 | 41 | development 42 | =========== 43 | 44 | ``` 45 | git clone https://github.com/esphome/esphome-webserver.git 46 | cd esphome-webserver 47 | npm install 48 | ``` 49 | 50 | Build and deploy all packages from the root directory: 51 | ```` 52 | npm run build 53 | ```` 54 | 55 | ### Work with specific packages 56 | Starts a dev server on http://localhost:3000 57 | ``` 58 | cd packages/v2 59 | npm run start 60 | ``` 61 | 62 | proxy 63 | ====== 64 | Events from a real device can be proxied for development by using the `PROXY_TARGET` environment variable. 65 | 66 | ``` 67 | PROXY_TARGET=http://nodemcu.local npm run build 68 | # and/or 69 | PROXY_TARGET=http://nodemcu.local npm run serve 70 | ``` 71 | 72 | Alternatively, update this line in `packages/[version]/vite.config.ts` to point to your real device. 73 | ```js 74 | const proxy_target = process.env.PROXY_TARGET || "http://nodemcu.local"; 75 | ``` 76 | 77 | The json api will POST to the real device and the events are proxied 78 | 79 | build 80 | ===== 81 | ```js 82 | cd packages/v2 83 | npm run build 84 | ``` 85 | The build files are copied to _static/v2 usually for deployment to https://oi.esphome.io/v2 or your /local/www Home Assistant folder 86 | 87 | If you customise, you can deploy to your local Home Assistant /local/www/_static/v2 and use: 88 | 89 | ```yaml 90 | web_server: 91 | port: 80 92 | version: 2 93 | js_url: http://homeassistant.local:8123/local/_static/v2/www.js 94 | ``` 95 | 96 | To use a specific version of a CDN hosted device dashboard, you can use the following override as an example: 97 | ```yaml 98 | web_server: 99 | port: 80 100 | version: 3 101 | js_url: https://oi.esphome.io/v3/www.js 102 | ``` 103 | 104 | serve 105 | ===== 106 | ```js 107 | cd packages/v2 108 | npm run serve 109 | ``` 110 | Starts a production test server on http://localhost:5001 111 | Events and the json api are proxied. 112 | -------------------------------------------------------------------------------- /components/template/lock/__init__.py: -------------------------------------------------------------------------------- 1 | from esphome import automation 2 | import esphome.codegen as cg 3 | from esphome.components import lock 4 | import esphome.config_validation as cv 5 | from esphome.const import ( 6 | CONF_ASSUMED_STATE, 7 | CONF_ID, 8 | CONF_LAMBDA, 9 | CONF_LOCK_ACTION, 10 | CONF_OPEN_ACTION, 11 | CONF_OPTIMISTIC, 12 | CONF_STATE, 13 | CONF_UNLOCK_ACTION, 14 | ) 15 | 16 | from .. import template_ns 17 | 18 | TemplateLock = template_ns.class_("TemplateLock", lock.Lock, cg.Component) 19 | 20 | TemplateLockPublishAction = template_ns.class_( 21 | "TemplateLockPublishAction", 22 | automation.Action, 23 | cg.Parented.template(TemplateLock), 24 | ) 25 | 26 | 27 | def validate(config): 28 | if not config[CONF_OPTIMISTIC] and ( 29 | CONF_LOCK_ACTION not in config or CONF_UNLOCK_ACTION not in config 30 | ): 31 | raise cv.Invalid( 32 | "Either optimistic mode must be enabled, or lock_action and unlock_action must be set, " 33 | "to handle the lock being changed." 34 | ) 35 | return config 36 | 37 | 38 | CONFIG_SCHEMA = cv.All( 39 | lock.lock_schema(TemplateLock) 40 | .extend( 41 | { 42 | cv.Optional(CONF_LAMBDA): cv.returning_lambda, 43 | cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, 44 | cv.Optional(CONF_ASSUMED_STATE, default=False): cv.boolean, 45 | cv.Optional(CONF_UNLOCK_ACTION): automation.validate_automation( 46 | single=True 47 | ), 48 | cv.Optional(CONF_LOCK_ACTION): automation.validate_automation(single=True), 49 | cv.Optional(CONF_OPEN_ACTION): automation.validate_automation(single=True), 50 | } 51 | ) 52 | .extend(cv.COMPONENT_SCHEMA), 53 | validate, 54 | ) 55 | 56 | 57 | async def to_code(config): 58 | var = await lock.new_lock(config) 59 | await cg.register_component(var, config) 60 | 61 | if CONF_LAMBDA in config: 62 | template_ = await cg.process_lambda( 63 | config[CONF_LAMBDA], [], return_type=cg.optional.template(lock.LockState) 64 | ) 65 | cg.add(var.set_state_lambda(template_)) 66 | if CONF_UNLOCK_ACTION in config: 67 | await automation.build_automation( 68 | var.get_unlock_trigger(), [], config[CONF_UNLOCK_ACTION] 69 | ) 70 | if CONF_LOCK_ACTION in config: 71 | await automation.build_automation( 72 | var.get_lock_trigger(), [], config[CONF_LOCK_ACTION] 73 | ) 74 | if CONF_OPEN_ACTION in config: 75 | await automation.build_automation( 76 | var.get_open_trigger(), [], config[CONF_OPEN_ACTION] 77 | ) 78 | cg.add(var.traits.set_supports_open(CONF_OPEN_ACTION in config)) 79 | cg.add(var.traits.set_assumed_state(config[CONF_ASSUMED_STATE])) 80 | cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) 81 | 82 | 83 | @automation.register_action( 84 | "lock.template.publish", 85 | TemplateLockPublishAction, 86 | cv.maybe_simple_value( 87 | { 88 | cv.GenerateID(): cv.use_id(TemplateLock), 89 | cv.Required(CONF_STATE): cv.templatable(lock.validate_lock_state), 90 | }, 91 | key=CONF_STATE, 92 | ), 93 | ) 94 | async def lock_template_publish_to_code(config, action_id, template_arg, args): 95 | var = cg.new_Pvariable(action_id, template_arg) 96 | await cg.register_parented(var, config[CONF_ID]) 97 | template_ = await cg.templatable(config[CONF_STATE], args, lock.LockState) 98 | cg.add(var.set_state(template_)) 99 | return var 100 | -------------------------------------------------------------------------------- /components/template/text/__init__.py: -------------------------------------------------------------------------------- 1 | from esphome import automation 2 | import esphome.codegen as cg 3 | from esphome.components import text 4 | import esphome.config_validation as cv 5 | from esphome.const import ( 6 | CONF_INITIAL_VALUE, 7 | CONF_LAMBDA, 8 | CONF_MAX_LENGTH, 9 | CONF_MIN_LENGTH, 10 | CONF_OPTIMISTIC, 11 | CONF_PATTERN, 12 | CONF_RESTORE_VALUE, 13 | CONF_SET_ACTION, 14 | ) 15 | 16 | from .. import template_ns 17 | 18 | TemplateText = template_ns.class_("TemplateText", text.Text, cg.PollingComponent) 19 | 20 | TextSaverBase = template_ns.class_("TemplateTextSaverBase") 21 | TextSaverTemplate = template_ns.class_("TextSaver", TextSaverBase) 22 | 23 | CONF_MAX_RESTORE_DATA_LENGTH = "max_restore_data_length" 24 | 25 | 26 | def validate(config): 27 | if CONF_LAMBDA in config: 28 | if config[CONF_OPTIMISTIC]: 29 | raise cv.Invalid("optimistic cannot be used with lambda") 30 | if CONF_INITIAL_VALUE in config: 31 | raise cv.Invalid("initial_value cannot be used with lambda") 32 | if config[CONF_RESTORE_VALUE]: 33 | raise cv.Invalid("restore_value cannot be used with lambda") 34 | elif CONF_INITIAL_VALUE not in config: 35 | config[CONF_INITIAL_VALUE] = "" 36 | 37 | if not config[CONF_OPTIMISTIC] and CONF_SET_ACTION not in config: 38 | raise cv.Invalid( 39 | "Either optimistic mode must be enabled, or set_action must be set, to handle the text input being set." 40 | ) 41 | 42 | with cv.prepend_path(CONF_MIN_LENGTH): 43 | if config[CONF_MIN_LENGTH] > config[CONF_MAX_LENGTH]: 44 | raise cv.Invalid("min_length must be less than or equal to max_length") 45 | return config 46 | 47 | 48 | CONFIG_SCHEMA = cv.All( 49 | text.text_schema(TemplateText) 50 | .extend( 51 | { 52 | cv.Optional(CONF_MIN_LENGTH, default=0): cv.int_range(min=0, max=255), 53 | cv.Optional(CONF_MAX_LENGTH, default=255): cv.int_range(min=0, max=255), 54 | cv.Optional(CONF_PATTERN): cv.string, 55 | cv.Optional(CONF_LAMBDA): cv.returning_lambda, 56 | cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, 57 | cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True), 58 | cv.Optional(CONF_INITIAL_VALUE): cv.string_strict, 59 | cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean, 60 | } 61 | ) 62 | .extend(cv.polling_component_schema("60s")), 63 | validate, 64 | ) 65 | 66 | 67 | async def to_code(config): 68 | var = await text.new_text( 69 | config, 70 | min_length=config[CONF_MIN_LENGTH], 71 | max_length=config[CONF_MAX_LENGTH], 72 | pattern=config.get(CONF_PATTERN), 73 | ) 74 | await cg.register_component(var, config) 75 | 76 | if CONF_LAMBDA in config: 77 | template_ = await cg.process_lambda( 78 | config[CONF_LAMBDA], [], return_type=cg.optional.template(cg.std_string) 79 | ) 80 | cg.add(var.set_template(template_)) 81 | 82 | else: 83 | cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) 84 | if initial_value_config := config.get(CONF_INITIAL_VALUE): 85 | cg.add(var.set_initial_value(initial_value_config)) 86 | if config[CONF_RESTORE_VALUE]: 87 | args = cg.TemplateArguments(config[CONF_MAX_LENGTH]) 88 | saver = TextSaverTemplate.template(args).new() 89 | cg.add(var.set_value_saver(saver)) 90 | 91 | if CONF_SET_ACTION in config: 92 | await automation.build_automation( 93 | var.get_set_trigger(), [(cg.std_string, "x")], config[CONF_SET_ACTION] 94 | ) 95 | -------------------------------------------------------------------------------- /components/gpio/binary_sensor/gpio_binary_sensor.cpp: -------------------------------------------------------------------------------- 1 | #include "gpio_binary_sensor.h" 2 | #include "esphome/core/log.h" 3 | 4 | namespace esphome { 5 | namespace gpio { 6 | 7 | static const char *const TAG = "gpio.binary_sensor"; 8 | 9 | #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG 10 | static const LogString *interrupt_type_to_string(gpio::InterruptType type) { 11 | switch (type) { 12 | case gpio::INTERRUPT_RISING_EDGE: 13 | return LOG_STR("RISING_EDGE"); 14 | case gpio::INTERRUPT_FALLING_EDGE: 15 | return LOG_STR("FALLING_EDGE"); 16 | case gpio::INTERRUPT_ANY_EDGE: 17 | return LOG_STR("ANY_EDGE"); 18 | default: 19 | return LOG_STR("UNKNOWN"); 20 | } 21 | } 22 | 23 | static const LogString *gpio_mode_to_string(bool use_interrupt) { 24 | return use_interrupt ? LOG_STR("interrupt") : LOG_STR("polling"); 25 | } 26 | #endif 27 | 28 | void IRAM_ATTR GPIOBinarySensorStore::gpio_intr(GPIOBinarySensorStore *arg) { 29 | bool new_state = arg->isr_pin_.digital_read(); 30 | if (new_state != arg->last_state_) { 31 | arg->state_ = new_state; 32 | arg->last_state_ = new_state; 33 | arg->changed_ = true; 34 | // Wake up the component from its disabled loop state 35 | if (arg->component_ != nullptr) { 36 | arg->component_->enable_loop_soon_any_context(); 37 | } 38 | } 39 | } 40 | 41 | void GPIOBinarySensorStore::setup(InternalGPIOPin *pin, gpio::InterruptType type, Component *component) { 42 | pin->setup(); 43 | this->isr_pin_ = pin->to_isr(); 44 | this->component_ = component; 45 | 46 | // Read initial state 47 | this->last_state_ = pin->digital_read(); 48 | this->state_ = this->last_state_; 49 | 50 | // Attach interrupt - from this point on, any changes will be caught by the interrupt 51 | pin->attach_interrupt(&GPIOBinarySensorStore::gpio_intr, this, type); 52 | } 53 | 54 | void GPIOBinarySensor::setup() { 55 | if (this->use_interrupt_ && !this->pin_->is_internal()) { 56 | ESP_LOGD(TAG, "GPIO is not internal, falling back to polling mode"); 57 | this->use_interrupt_ = false; 58 | } 59 | 60 | if (this->use_interrupt_) { 61 | auto *internal_pin = static_cast(this->pin_); 62 | this->store_.setup(internal_pin, this->interrupt_type_, this); 63 | this->publish_initial_state(this->store_.get_state()); 64 | } else { 65 | this->pin_->setup(); 66 | this->publish_initial_state(this->pin_->digital_read()); 67 | } 68 | } 69 | 70 | void GPIOBinarySensor::dump_config() { 71 | LOG_BINARY_SENSOR("", "GPIO Binary Sensor", this); 72 | LOG_PIN(" Pin: ", this->pin_); 73 | ESP_LOGCONFIG(TAG, " Mode: %s", LOG_STR_ARG(gpio_mode_to_string(this->use_interrupt_))); 74 | if (this->use_interrupt_) { 75 | ESP_LOGCONFIG(TAG, " Interrupt Type: %s", LOG_STR_ARG(interrupt_type_to_string(this->interrupt_type_))); 76 | } 77 | } 78 | 79 | void GPIOBinarySensor::loop() { 80 | if (this->use_interrupt_) { 81 | if (this->store_.is_changed()) { 82 | // Clear the flag immediately to minimize the window where we might miss changes 83 | this->store_.clear_changed(); 84 | // Read the state and publish it 85 | // Note: If the ISR fires between clear_changed() and get_state(), that's fine - 86 | // we'll process the new change on the next loop iteration 87 | bool state = this->store_.get_state(); 88 | this->publish_state(state); 89 | } else { 90 | // No changes, disable the loop until the next interrupt 91 | this->disable_loop(); 92 | } 93 | } else { 94 | this->publish_state(this->pin_->digital_read()); 95 | } 96 | } 97 | 98 | float GPIOBinarySensor::get_setup_priority() const { return setup_priority::HARDWARE; } 99 | 100 | } // namespace gpio 101 | } // namespace esphome 102 | -------------------------------------------------------------------------------- /components/light/types.py: -------------------------------------------------------------------------------- 1 | from esphome import automation 2 | import esphome.codegen as cg 3 | 4 | # Base 5 | light_ns = cg.esphome_ns.namespace("light") 6 | LightState = light_ns.class_("LightState", cg.EntityBase, cg.Component) 7 | AddressableLightState = light_ns.class_("AddressableLightState", LightState) 8 | LightOutput = light_ns.class_("LightOutput") 9 | AddressableLight = light_ns.class_("AddressableLight", LightOutput, cg.Component) 10 | AddressableLightRef = AddressableLight.operator("ref") 11 | 12 | Color = cg.esphome_ns.class_("Color") 13 | LightColorValues = light_ns.class_("LightColorValues") 14 | 15 | LightStateRTCState = light_ns.struct("LightStateRTCState") 16 | 17 | # Color modes 18 | ColorMode = light_ns.enum("ColorMode", is_class=True) 19 | COLOR_MODES = { 20 | "ON_OFF": ColorMode.ON_OFF, 21 | "BRIGHTNESS": ColorMode.BRIGHTNESS, 22 | "WHITE": ColorMode.WHITE, 23 | "COLOR_TEMPERATURE": ColorMode.COLOR_TEMPERATURE, 24 | "COLD_WARM_WHITE": ColorMode.COLD_WARM_WHITE, 25 | "RGB": ColorMode.RGB, 26 | "RGB_WHITE": ColorMode.RGB_WHITE, 27 | "RGB_COLOR_TEMPERATURE": ColorMode.RGB_COLOR_TEMPERATURE, 28 | "RGB_COLD_WARM_WHITE": ColorMode.RGB_COLD_WARM_WHITE, 29 | } 30 | 31 | # Limit modes 32 | LimitMode = light_ns.enum("LimitMode", is_class=True) 33 | LIMIT_MODES = { 34 | "CLAMP": LimitMode.CLAMP, 35 | "DO_NOTHING": LimitMode.DO_NOTHING, 36 | } 37 | 38 | # Actions 39 | ToggleAction = light_ns.class_("ToggleAction", automation.Action) 40 | LightControlAction = light_ns.class_("LightControlAction", automation.Action) 41 | DimRelativeAction = light_ns.class_("DimRelativeAction", automation.Action) 42 | AddressableSet = light_ns.class_("AddressableSet", automation.Action) 43 | LightIsOnCondition = light_ns.class_("LightIsOnCondition", automation.Condition) 44 | LightIsOffCondition = light_ns.class_("LightIsOffCondition", automation.Condition) 45 | 46 | # Triggers 47 | LightTurnOnTrigger = light_ns.class_( 48 | "LightTurnOnTrigger", automation.Trigger.template() 49 | ) 50 | LightTurnOffTrigger = light_ns.class_( 51 | "LightTurnOffTrigger", automation.Trigger.template() 52 | ) 53 | LightStateTrigger = light_ns.class_("LightStateTrigger", automation.Trigger.template()) 54 | 55 | # Effects 56 | LightEffect = light_ns.class_("LightEffect") 57 | PulseLightEffect = light_ns.class_("PulseLightEffect", LightEffect) 58 | RandomLightEffect = light_ns.class_("RandomLightEffect", LightEffect) 59 | LambdaLightEffect = light_ns.class_("LambdaLightEffect", LightEffect) 60 | AutomationLightEffect = light_ns.class_("AutomationLightEffect", LightEffect) 61 | StrobeLightEffect = light_ns.class_("StrobeLightEffect", LightEffect) 62 | StrobeLightEffectColor = light_ns.class_("StrobeLightEffectColor", LightEffect) 63 | FlickerLightEffect = light_ns.class_("FlickerLightEffect", LightEffect) 64 | AddressableLightEffect = light_ns.class_("AddressableLightEffect", LightEffect) 65 | AddressableLambdaLightEffect = light_ns.class_( 66 | "AddressableLambdaLightEffect", AddressableLightEffect 67 | ) 68 | AddressableRainbowLightEffect = light_ns.class_( 69 | "AddressableRainbowLightEffect", AddressableLightEffect 70 | ) 71 | AddressableColorWipeEffect = light_ns.class_( 72 | "AddressableColorWipeEffect", AddressableLightEffect 73 | ) 74 | AddressableColorWipeEffectColor = light_ns.struct("AddressableColorWipeEffectColor") 75 | AddressableScanEffect = light_ns.class_("AddressableScanEffect", AddressableLightEffect) 76 | AddressableTwinkleEffect = light_ns.class_( 77 | "AddressableTwinkleEffect", AddressableLightEffect 78 | ) 79 | AddressableRandomTwinkleEffect = light_ns.class_( 80 | "AddressableRandomTwinkleEffect", AddressableLightEffect 81 | ) 82 | AddressableFireworksEffect = light_ns.class_( 83 | "AddressableFireworksEffect", AddressableLightEffect 84 | ) 85 | AddressableFlickerEffect = light_ns.class_( 86 | "AddressableFlickerEffect", AddressableLightEffect 87 | ) 88 | -------------------------------------------------------------------------------- /components/light/esp_color_correction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/color.h" 4 | 5 | namespace esphome::light { 6 | 7 | class ESPColorCorrection { 8 | public: 9 | ESPColorCorrection() : max_brightness_(255, 255, 255, 255) {} 10 | void set_max_brightness(const Color &max_brightness) { this->max_brightness_ = max_brightness; } 11 | void set_local_brightness(uint8_t local_brightness) { this->local_brightness_ = local_brightness; } 12 | void calculate_gamma_table(float gamma); 13 | inline Color color_correct(Color color) const ESPHOME_ALWAYS_INLINE { 14 | // corrected = (uncorrected * max_brightness * local_brightness) ^ gamma 15 | return Color(this->color_correct_red(color.red), this->color_correct_green(color.green), 16 | this->color_correct_blue(color.blue), this->color_correct_white(color.white)); 17 | } 18 | inline uint8_t color_correct_red(uint8_t red) const ESPHOME_ALWAYS_INLINE { 19 | uint8_t res = esp_scale8_twice(red, this->max_brightness_.red, this->local_brightness_); 20 | return this->gamma_table_[res]; 21 | } 22 | inline uint8_t color_correct_green(uint8_t green) const ESPHOME_ALWAYS_INLINE { 23 | uint8_t res = esp_scale8_twice(green, this->max_brightness_.green, this->local_brightness_); 24 | return this->gamma_table_[res]; 25 | } 26 | inline uint8_t color_correct_blue(uint8_t blue) const ESPHOME_ALWAYS_INLINE { 27 | uint8_t res = esp_scale8_twice(blue, this->max_brightness_.blue, this->local_brightness_); 28 | return this->gamma_table_[res]; 29 | } 30 | inline uint8_t color_correct_white(uint8_t white) const ESPHOME_ALWAYS_INLINE { 31 | uint8_t res = esp_scale8_twice(white, this->max_brightness_.white, this->local_brightness_); 32 | return this->gamma_table_[res]; 33 | } 34 | inline Color color_uncorrect(Color color) const ESPHOME_ALWAYS_INLINE { 35 | // uncorrected = corrected^(1/gamma) / (max_brightness * local_brightness) 36 | return Color(this->color_uncorrect_red(color.red), this->color_uncorrect_green(color.green), 37 | this->color_uncorrect_blue(color.blue), this->color_uncorrect_white(color.white)); 38 | } 39 | inline uint8_t color_uncorrect_red(uint8_t red) const ESPHOME_ALWAYS_INLINE { 40 | if (this->max_brightness_.red == 0 || this->local_brightness_ == 0) 41 | return 0; 42 | uint16_t uncorrected = this->gamma_reverse_table_[red] * 255UL; 43 | uint16_t res = ((uncorrected / this->max_brightness_.red) * 255UL) / this->local_brightness_; 44 | return (uint8_t) std::min(res, uint16_t(255)); 45 | } 46 | inline uint8_t color_uncorrect_green(uint8_t green) const ESPHOME_ALWAYS_INLINE { 47 | if (this->max_brightness_.green == 0 || this->local_brightness_ == 0) 48 | return 0; 49 | uint16_t uncorrected = this->gamma_reverse_table_[green] * 255UL; 50 | uint16_t res = ((uncorrected / this->max_brightness_.green) * 255UL) / this->local_brightness_; 51 | return (uint8_t) std::min(res, uint16_t(255)); 52 | } 53 | inline uint8_t color_uncorrect_blue(uint8_t blue) const ESPHOME_ALWAYS_INLINE { 54 | if (this->max_brightness_.blue == 0 || this->local_brightness_ == 0) 55 | return 0; 56 | uint16_t uncorrected = this->gamma_reverse_table_[blue] * 255UL; 57 | uint16_t res = ((uncorrected / this->max_brightness_.blue) * 255UL) / this->local_brightness_; 58 | return (uint8_t) std::min(res, uint16_t(255)); 59 | } 60 | inline uint8_t color_uncorrect_white(uint8_t white) const ESPHOME_ALWAYS_INLINE { 61 | if (this->max_brightness_.white == 0 || this->local_brightness_ == 0) 62 | return 0; 63 | uint16_t uncorrected = this->gamma_reverse_table_[white] * 255UL; 64 | uint16_t res = ((uncorrected / this->max_brightness_.white) * 255UL) / this->local_brightness_; 65 | return (uint8_t) std::min(res, uint16_t(255)); 66 | } 67 | 68 | protected: 69 | uint8_t gamma_table_[256]; 70 | uint8_t gamma_reverse_table_[256]; 71 | Color max_brightness_; 72 | uint8_t local_brightness_{255}; 73 | }; 74 | 75 | } // namespace esphome::light 76 | -------------------------------------------------------------------------------- /components/captive_portal/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import esphome.codegen as cg 4 | from esphome.components import web_server_base 5 | from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID 6 | from esphome.config_helpers import filter_source_files_from_platform 7 | import esphome.config_validation as cv 8 | from esphome.const import ( 9 | CONF_AP, 10 | CONF_ID, 11 | PLATFORM_BK72XX, 12 | PLATFORM_ESP32, 13 | PLATFORM_ESP8266, 14 | PLATFORM_LN882X, 15 | PLATFORM_RTL87XX, 16 | PlatformFramework, 17 | ) 18 | from esphome.core import CORE, coroutine_with_priority 19 | from esphome.coroutine import CoroPriority 20 | import esphome.final_validate as fv 21 | from esphome.types import ConfigType 22 | 23 | _LOGGER = logging.getLogger(__name__) 24 | 25 | 26 | def AUTO_LOAD() -> list[str]: 27 | auto_load = ["web_server_base", "ota.web_server"] 28 | if CORE.using_esp_idf: 29 | auto_load.append("socket") 30 | return auto_load 31 | 32 | 33 | DEPENDENCIES = ["wifi"] 34 | CODEOWNERS = ["@esphome/core"] 35 | 36 | captive_portal_ns = cg.esphome_ns.namespace("captive_portal") 37 | CaptivePortal = captive_portal_ns.class_("CaptivePortal", cg.Component) 38 | 39 | CONFIG_SCHEMA = cv.All( 40 | cv.Schema( 41 | { 42 | cv.GenerateID(): cv.declare_id(CaptivePortal), 43 | cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( 44 | web_server_base.WebServerBase 45 | ), 46 | } 47 | ).extend(cv.COMPONENT_SCHEMA), 48 | cv.only_on( 49 | [ 50 | PLATFORM_ESP32, 51 | PLATFORM_ESP8266, 52 | PLATFORM_BK72XX, 53 | PLATFORM_LN882X, 54 | PLATFORM_RTL87XX, 55 | ] 56 | ), 57 | ) 58 | 59 | 60 | def _final_validate(config: ConfigType) -> ConfigType: 61 | full_config = fv.full_config.get() 62 | wifi_conf = full_config.get("wifi") 63 | 64 | if wifi_conf is None: 65 | # This shouldn't happen due to DEPENDENCIES = ["wifi"], but check anyway 66 | raise cv.Invalid("Captive portal requires the wifi component to be configured") 67 | 68 | if CONF_AP not in wifi_conf: 69 | _LOGGER.warning( 70 | "Captive portal is enabled but no WiFi AP is configured. " 71 | "The captive portal will not be accessible. " 72 | "Add 'ap:' to your WiFi configuration to enable the captive portal." 73 | ) 74 | 75 | # Register socket needs for DNS server and additional HTTP connections 76 | # - 1 UDP socket for DNS server 77 | # - 3 additional TCP sockets for captive portal detection probes + configuration requests 78 | # OS captive portal detection makes multiple probe requests that stay in TIME_WAIT. 79 | # Need headroom for actual user configuration requests. 80 | # LRU purging will reclaim idle sockets to prevent exhaustion from repeated attempts. 81 | from esphome.components import socket 82 | 83 | socket.consume_sockets(4, "captive_portal")(config) 84 | 85 | return config 86 | 87 | 88 | FINAL_VALIDATE_SCHEMA = _final_validate 89 | 90 | 91 | @coroutine_with_priority(CoroPriority.CAPTIVE_PORTAL) 92 | async def to_code(config): 93 | paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID]) 94 | 95 | var = cg.new_Pvariable(config[CONF_ID], paren) 96 | await cg.register_component(var, config) 97 | cg.add_define("USE_CAPTIVE_PORTAL") 98 | 99 | if CORE.using_arduino: 100 | if CORE.is_esp32: 101 | cg.add_library("ESP32 Async UDP", None) 102 | cg.add_library("DNSServer", None) 103 | cg.add_library("WiFi", None) 104 | if CORE.is_esp8266: 105 | cg.add_library("DNSServer", None) 106 | if CORE.is_libretiny: 107 | cg.add_library("DNSServer", None) 108 | 109 | 110 | # Only compile the ESP-IDF DNS server when using ESP-IDF framework 111 | FILTER_SOURCE_FILES = filter_source_files_from_platform( 112 | { 113 | "dns_server_esp32_idf.cpp": {PlatformFramework.ESP32_IDF}, 114 | } 115 | ) 116 | --------------------------------------------------------------------------------