diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 1d2a1b5323..eb3a5a945c 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -11,6 +11,7 @@ on: - ".github/workflows/**" - "requirements*.txt" - "platformio.ini" + - "script/platformio_install_deps.py" pull_request: paths: @@ -18,6 +19,7 @@ on: - ".github/workflows/**" - "requirements*.txt" - "platformio.ini" + - "script/platformio_install_deps.py" permissions: contents: read diff --git a/CODEOWNERS b/CODEOWNERS index d07d00632c..80d493dcc2 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -21,6 +21,7 @@ esphome/components/airthings_wave_mini/* @ncareau esphome/components/airthings_wave_plus/* @jeromelaban esphome/components/am43/* @buxtronix esphome/components/am43/cover/* @buxtronix +esphome/components/am43/sensor/* @buxtronix esphome/components/analog_threshold/* @ianchi esphome/components/animation/* @syndlex esphome/components/anova/* @buxtronix @@ -162,6 +163,7 @@ esphome/components/midea/* @dudanov esphome/components/midea_ir/* @dudanov esphome/components/mitsubishi/* @RubyBailey esphome/components/mlx90393/* @functionpointer +esphome/components/mlx90614/* @jesserockz esphome/components/mmc5603/* @benhoff esphome/components/modbus_controller/* @martgras esphome/components/modbus_controller/binary_sensor/* @martgras diff --git a/docker/Dockerfile b/docker/Dockerfile index 383c73565d..a59a470394 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -24,6 +24,7 @@ RUN \ python3-setuptools=52.0.0-4 \ python3-pil=8.1.2+dfsg-0.3+deb11u1 \ python3-cryptography=3.3.2-1 \ + python3-venv=3.9.2-3 \ iputils-ping=3:20210202-1 \ git=1:2.30.2-1 \ curl=7.74.0-1.3+deb11u7 \ @@ -59,7 +60,7 @@ RUN \ # First install requirements to leverage caching when requirements don't change -COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini / +COPY requirements.txt requirements_optional.txt script/platformio_install_deps.py platformio.ini / RUN \ pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ && /platformio_install_deps.py /platformio.ini diff --git a/docker/platformio_install_deps.py b/docker/platformio_install_deps.py deleted file mode 100755 index c7b11cf321..0000000000 --- a/docker/platformio_install_deps.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env python3 -# This script is used in the docker containers to preinstall -# all platformio libraries in the global storage - -import configparser -import subprocess -import sys - -config = configparser.ConfigParser(inline_comment_prefixes=(';', )) -config.read(sys.argv[1]) - -libs = [] -# Extract from every lib_deps key in all sections -for section in config.sections(): - conf = config[section] - if "lib_deps" not in conf: - continue - for lib_dep in conf["lib_deps"].splitlines(): - if not lib_dep: - # Empty line or comment - continue - if lib_dep.startswith("${"): - # Extending from another section - continue - if "@" not in lib_dep: - # No version pinned, this is an internal lib - continue - libs.append(lib_dep) - -subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs]) diff --git a/esphome/components/am43/__init__.py b/esphome/components/am43/__init__.py index e69de29bb2..f21a15ce0a 100644 --- a/esphome/components/am43/__init__.py +++ b/esphome/components/am43/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@buxtronix"] diff --git a/esphome/components/am43/cover/__init__.py b/esphome/components/am43/cover/__init__.py index 79eeb2eef3..103ac809e6 100644 --- a/esphome/components/am43/cover/__init__.py +++ b/esphome/components/am43/cover/__init__.py @@ -5,7 +5,7 @@ from esphome.const import CONF_ID, CONF_PIN CODEOWNERS = ["@buxtronix"] DEPENDENCIES = ["ble_client"] -AUTO_LOAD = ["am43", "sensor"] +AUTO_LOAD = ["am43"] CONF_INVERT_POSITION = "invert_position" @@ -27,10 +27,10 @@ CONFIG_SCHEMA = ( ) -def to_code(config): +async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) cg.add(var.set_pin(config[CONF_PIN])) cg.add(var.set_invert_position(config[CONF_INVERT_POSITION])) - yield cg.register_component(var, config) - yield cover.register_cover(var, config) - yield ble_client.register_ble_node(var, config) + await cg.register_component(var, config) + await cover.register_cover(var, config) + await ble_client.register_ble_node(var, config) diff --git a/esphome/components/am43/cover/am43_cover.cpp b/esphome/components/am43/cover/am43_cover.cpp index d0ef4a2fbb..93c77ea364 100644 --- a/esphome/components/am43/cover/am43_cover.cpp +++ b/esphome/components/am43/cover/am43_cover.cpp @@ -40,6 +40,7 @@ void Am43Component::loop() { CoverTraits Am43Component::get_traits() { auto traits = CoverTraits(); + traits.set_supports_stop(true); traits.set_supports_position(true); traits.set_supports_tilt(false); traits.set_is_assumed_state(false); diff --git a/esphome/components/am43/sensor.py b/esphome/components/am43/sensor/__init__.py similarity index 83% rename from esphome/components/am43/sensor.py rename to esphome/components/am43/sensor/__init__.py index 68c85d0e9c..01588f2299 100644 --- a/esphome/components/am43/sensor.py +++ b/esphome/components/am43/sensor/__init__.py @@ -11,6 +11,7 @@ from esphome.const import ( UNIT_PERCENT, ) +AUTO_LOAD = ["am43"] CODEOWNERS = ["@buxtronix"] am43_ns = cg.esphome_ns.namespace("am43") @@ -38,15 +39,15 @@ CONFIG_SCHEMA = ( ) -def to_code(config): +async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - yield cg.register_component(var, config) - yield ble_client.register_ble_node(var, config) + await cg.register_component(var, config) + await ble_client.register_ble_node(var, config) if CONF_BATTERY_LEVEL in config: - sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL]) + sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL]) cg.add(var.set_battery(sens)) if CONF_ILLUMINANCE in config: - sens = yield sensor.new_sensor(config[CONF_ILLUMINANCE]) + sens = await sensor.new_sensor(config[CONF_ILLUMINANCE]) cg.add(var.set_illuminance(sens)) diff --git a/esphome/components/am43/am43.cpp b/esphome/components/am43/sensor/am43_sensor.cpp similarity index 99% rename from esphome/components/am43/am43.cpp rename to esphome/components/am43/sensor/am43_sensor.cpp index 09723496d9..008c7768ed 100644 --- a/esphome/components/am43/am43.cpp +++ b/esphome/components/am43/sensor/am43_sensor.cpp @@ -1,6 +1,6 @@ -#include "am43.h" -#include "esphome/core/log.h" +#include "am43_sensor.h" #include "esphome/core/hal.h" +#include "esphome/core/log.h" #ifdef USE_ESP32 diff --git a/esphome/components/am43/am43.h b/esphome/components/am43/sensor/am43_sensor.h similarity index 100% rename from esphome/components/am43/am43.h rename to esphome/components/am43/sensor/am43_sensor.h diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index f31ef3ffc0..4cc98c91d9 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -288,6 +288,7 @@ message ListEntitiesCoverResponse { bool disabled_by_default = 9; string icon = 10; EntityCategory entity_category = 11; + bool supports_stop = 12; } enum LegacyCoverState { @@ -861,8 +862,7 @@ message ClimateStateResponse { float target_temperature = 4; float target_temperature_low = 5; float target_temperature_high = 6; - // For older peers, equal to preset == CLIMATE_PRESET_AWAY - bool legacy_away = 7; + bool unused_legacy_away = 7; ClimateAction action = 8; ClimateFanMode fan_mode = 9; ClimateSwingMode swing_mode = 10; @@ -885,9 +885,8 @@ message ClimateCommandRequest { float target_temperature_low = 7; bool has_target_temperature_high = 8; float target_temperature_high = 9; - // legacy, for older peers, newer ones should use CLIMATE_PRESET_AWAY in preset - bool has_legacy_away = 10; - bool legacy_away = 11; + bool unused_has_legacy_away = 10; + bool unused_legacy_away = 11; bool has_fan_mode = 12; ClimateFanMode fan_mode = 13; bool has_swing_mode = 14; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 96fb3ea9fa..a79444a7e9 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -530,7 +530,6 @@ bool APIConnection::send_climate_state(climate::Climate *climate) { resp.custom_fan_mode = climate->custom_fan_mode.value(); if (traits.get_supports_presets() && climate->preset.has_value()) { resp.preset = static_cast(climate->preset.value()); - resp.legacy_away = resp.preset == enums::CLIMATE_PRESET_AWAY; } if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value()) resp.custom_preset = climate->custom_preset.value(); @@ -591,8 +590,6 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) { call.set_target_temperature_low(msg.target_temperature_low); if (msg.has_target_temperature_high) call.set_target_temperature_high(msg.target_temperature_high); - if (msg.has_legacy_away) - call.set_preset(msg.legacy_away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME); if (msg.has_fan_mode) call.set_fan_mode(static_cast(msg.fan_mode)); if (msg.has_custom_fan_mode) @@ -944,7 +941,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) { HelloResponse resp; resp.api_version_major = 1; - resp.api_version_minor = 7; + resp.api_version_minor = 8; resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; resp.name = App.get_name(); diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 334cde16b3..1dd8c82e00 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -941,6 +941,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val this->entity_category = value.as_enum(); return true; } + case 12: { + this->supports_stop = value.as_bool(); + return true; + } default: return false; } @@ -993,6 +997,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(9, this->disabled_by_default); buffer.encode_string(10, this->icon); buffer.encode_enum(11, this->entity_category); + buffer.encode_bool(12, this->supports_stop); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCoverResponse::dump_to(std::string &out) const { @@ -1042,6 +1047,10 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const { out.append(" entity_category: "); out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); + + out.append(" supports_stop: "); + out.append(YESNO(this->supports_stop)); + out.append("\n"); out.append("}"); } #endif @@ -3649,7 +3658,7 @@ bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { return true; } case 7: { - this->legacy_away = value.as_bool(); + this->unused_legacy_away = value.as_bool(); return true; } case 8: { @@ -3719,7 +3728,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(4, this->target_temperature); buffer.encode_float(5, this->target_temperature_low); buffer.encode_float(6, this->target_temperature_high); - buffer.encode_bool(7, this->legacy_away); + buffer.encode_bool(7, this->unused_legacy_away); buffer.encode_enum(8, this->action); buffer.encode_enum(9, this->fan_mode); buffer.encode_enum(10, this->swing_mode); @@ -3760,8 +3769,8 @@ void ClimateStateResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" legacy_away: "); - out.append(YESNO(this->legacy_away)); + out.append(" unused_legacy_away: "); + out.append(YESNO(this->unused_legacy_away)); out.append("\n"); out.append(" action: "); @@ -3813,11 +3822,11 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) return true; } case 10: { - this->has_legacy_away = value.as_bool(); + this->unused_has_legacy_away = value.as_bool(); return true; } case 11: { - this->legacy_away = value.as_bool(); + this->unused_legacy_away = value.as_bool(); return true; } case 12: { @@ -3902,8 +3911,8 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(7, this->target_temperature_low); buffer.encode_bool(8, this->has_target_temperature_high); buffer.encode_float(9, this->target_temperature_high); - buffer.encode_bool(10, this->has_legacy_away); - buffer.encode_bool(11, this->legacy_away); + buffer.encode_bool(10, this->unused_has_legacy_away); + buffer.encode_bool(11, this->unused_legacy_away); buffer.encode_bool(12, this->has_fan_mode); buffer.encode_enum(13, this->fan_mode); buffer.encode_bool(14, this->has_swing_mode); @@ -3959,12 +3968,12 @@ void ClimateCommandRequest::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" has_legacy_away: "); - out.append(YESNO(this->has_legacy_away)); + out.append(" unused_has_legacy_away: "); + out.append(YESNO(this->unused_has_legacy_away)); out.append("\n"); - out.append(" legacy_away: "); - out.append(YESNO(this->legacy_away)); + out.append(" unused_legacy_away: "); + out.append(YESNO(this->unused_legacy_away)); out.append("\n"); out.append(" has_fan_mode: "); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 9f71c07913..0f4b79de19 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -375,6 +375,7 @@ class ListEntitiesCoverResponse : public ProtoMessage { bool disabled_by_default{false}; std::string icon{}; enums::EntityCategory entity_category{}; + bool supports_stop{false}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -958,7 +959,7 @@ class ClimateStateResponse : public ProtoMessage { float target_temperature{0.0f}; float target_temperature_low{0.0f}; float target_temperature_high{0.0f}; - bool legacy_away{false}; + bool unused_legacy_away{false}; enums::ClimateAction action{}; enums::ClimateFanMode fan_mode{}; enums::ClimateSwingMode swing_mode{}; @@ -986,8 +987,8 @@ class ClimateCommandRequest : public ProtoMessage { float target_temperature_low{0.0f}; bool has_target_temperature_high{false}; float target_temperature_high{0.0f}; - bool has_legacy_away{false}; - bool legacy_away{false}; + bool unused_has_legacy_away{false}; + bool unused_legacy_away{false}; bool has_fan_mode{false}; enums::ClimateFanMode fan_mode{}; bool has_swing_mode{false}; diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index fbef4b253f..068f74315c 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -428,14 +428,17 @@ void APIServer::on_shutdown() { } #ifdef USE_VOICE_ASSISTANT -void APIServer::start_voice_assistant() { +bool APIServer::start_voice_assistant() { for (auto &c : this->clients_) { - c->request_voice_assistant(true); + if (c->request_voice_assistant(true)) + return true; } + return false; } void APIServer::stop_voice_assistant() { for (auto &c : this->clients_) { - c->request_voice_assistant(false); + if (c->request_voice_assistant(false)) + return; } } #endif diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 30103b2e3f..a1bec2802f 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -96,7 +96,7 @@ class APIServer : public Component, public Controller { #endif #ifdef USE_VOICE_ASSISTANT - void start_voice_assistant(); + bool start_voice_assistant(); void stop_voice_assistant(); #endif diff --git a/esphome/components/binary_sensor/binary_sensor.cpp b/esphome/components/binary_sensor/binary_sensor.cpp index bd33b2af2d..20604a0b7e 100644 --- a/esphome/components/binary_sensor/binary_sensor.cpp +++ b/esphome/components/binary_sensor/binary_sensor.cpp @@ -43,12 +43,7 @@ void BinarySensor::send_state_internal(bool state, bool is_initial) { } BinarySensor::BinarySensor() : state(false) {} -void BinarySensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } -std::string BinarySensor::get_device_class() { - if (this->device_class_.has_value()) - return *this->device_class_; - return ""; -} + void BinarySensor::add_filter(Filter *filter) { filter->parent_ = this; if (this->filter_list_ == nullptr) { diff --git a/esphome/components/binary_sensor/binary_sensor.h b/esphome/components/binary_sensor/binary_sensor.h index 0bf8cf2cdc..301a472810 100644 --- a/esphome/components/binary_sensor/binary_sensor.h +++ b/esphome/components/binary_sensor/binary_sensor.h @@ -34,7 +34,7 @@ namespace binary_sensor { * The sub classes should notify the front-end of new states via the publish_state() method which * handles inverted inputs for you. */ -class BinarySensor : public EntityBase { +class BinarySensor : public EntityBase, public EntityBase_DeviceClass { public: explicit BinarySensor(); @@ -60,12 +60,6 @@ class BinarySensor : public EntityBase { /// The current reported state of the binary sensor. bool state; - /// Manually set the Home Assistant device class (see binary_sensor::device_class) - void set_device_class(const std::string &device_class); - - /// Get the device class for this binary sensor, using the manual override if specified. - std::string get_device_class(); - void add_filter(Filter *filter); void add_filters(const std::vector &filters); @@ -82,7 +76,6 @@ class BinarySensor : public EntityBase { protected: CallbackManager state_callback_{}; - optional device_class_{}; ///< Stores the override of the device class Filter *filter_list_{nullptr}; bool has_state_{false}; bool publish_initial_state_{false}; diff --git a/esphome/components/button/button.cpp b/esphome/components/button/button.cpp index dfa417de7b..4c4cb7740c 100644 --- a/esphome/components/button/button.cpp +++ b/esphome/components/button/button.cpp @@ -13,8 +13,5 @@ void Button::press() { } void Button::add_on_press_callback(std::function &&callback) { this->press_callback_.add(std::move(callback)); } -void Button::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } -std::string Button::get_device_class() { return this->device_class_; } - } // namespace button } // namespace esphome diff --git a/esphome/components/button/button.h b/esphome/components/button/button.h index a4902810b2..9488eca221 100644 --- a/esphome/components/button/button.h +++ b/esphome/components/button/button.h @@ -26,7 +26,7 @@ namespace button { * * A button is just a momentary switch that does not have a state, only a trigger. */ -class Button : public EntityBase { +class Button : public EntityBase, public EntityBase_DeviceClass { public: /** Press this button. This is called by the front-end. * @@ -40,19 +40,12 @@ class Button : public EntityBase { */ void add_on_press_callback(std::function &&callback); - /// Set the Home Assistant device class (see button::device_class). - void set_device_class(const std::string &device_class); - - /// Get the device class for this button. - std::string get_device_class(); - protected: /** You should implement this virtual method if you want to create your own button. */ virtual void press_action() = 0; CallbackManager press_callback_{}; - std::string device_class_{}; }; } // namespace button diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index 6734917bf3..bf167fe837 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -343,7 +343,7 @@ CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema( cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature), cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(cv.temperature), cv.Optional(CONF_TARGET_TEMPERATURE_HIGH): cv.templatable(cv.temperature), - cv.Optional(CONF_AWAY): cv.templatable(cv.boolean), + cv.Optional(CONF_AWAY): cv.invalid("Use preset instead"), cv.Exclusive(CONF_FAN_MODE, "fan_mode"): cv.templatable( validate_climate_fan_mode ), @@ -379,9 +379,6 @@ async def climate_control_to_code(config, action_id, template_arg, args): config[CONF_TARGET_TEMPERATURE_HIGH], args, float ) cg.add(var.set_target_temperature_high(template_)) - if CONF_AWAY in config: - template_ = await cg.templatable(config[CONF_AWAY], args, bool) - cg.add(var.set_away(template_)) if CONF_FAN_MODE in config: template_ = await cg.templatable(config[CONF_FAN_MODE], args, ClimateFanMode) cg.add(var.set_fan_mode(template_)) diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index b4d5ee9685..a032596eb3 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -264,25 +264,11 @@ const optional &ClimateCall::get_mode() const { return this->mode_; const optional &ClimateCall::get_target_temperature() const { return this->target_temperature_; } const optional &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; } const optional &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; } -optional ClimateCall::get_away() const { - if (!this->preset_.has_value()) - return {}; - return *this->preset_ == ClimatePreset::CLIMATE_PRESET_AWAY; -} const optional &ClimateCall::get_fan_mode() const { return this->fan_mode_; } const optional &ClimateCall::get_custom_fan_mode() const { return this->custom_fan_mode_; } const optional &ClimateCall::get_preset() const { return this->preset_; } const optional &ClimateCall::get_custom_preset() const { return this->custom_preset_; } const optional &ClimateCall::get_swing_mode() const { return this->swing_mode_; } -ClimateCall &ClimateCall::set_away(bool away) { - this->preset_ = away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME; - return *this; -} -ClimateCall &ClimateCall::set_away(optional away) { - if (away.has_value()) - this->preset_ = *away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME; - return *this; -} ClimateCall &ClimateCall::set_target_temperature_high(optional target_temperature_high) { this->target_temperature_high_ = target_temperature_high; return *this; diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index 43bd71657d..656e1c4852 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -64,10 +64,6 @@ class ClimateCall { * For climate devices with two point target temperature control */ ClimateCall &set_target_temperature_high(optional target_temperature_high); - ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead", "v1.20") - ClimateCall &set_away(bool away); - ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead", "v1.20") - ClimateCall &set_away(optional away); /// Set the fan mode of the climate device. ClimateCall &set_fan_mode(ClimateFanMode fan_mode); /// Set the fan mode of the climate device. @@ -97,8 +93,6 @@ class ClimateCall { const optional &get_target_temperature() const; const optional &get_target_temperature_low() const; const optional &get_target_temperature_high() const; - ESPDEPRECATED("get_away() is deprecated, please use .get_preset() instead", "v1.20") - optional get_away() const; const optional &get_fan_mode() const; const optional &get_swing_mode() const; const optional &get_custom_fan_mode() const; @@ -184,14 +178,6 @@ class Climate : public EntityBase { }; }; - /** Whether the climate device is in away mode. - * - * Away allows climate devices to have two different target temperature configs: - * one for normal mode and one for away mode. - */ - ESPDEPRECATED("away is deprecated, use preset instead", "v1.20") - bool away{false}; - /// The active fan mode of the climate device. optional fan_mode; diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index ffbd8c5ae0..e8c2db6c06 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -117,15 +117,6 @@ class ClimateTraits { bool supports_custom_preset(const std::string &custom_preset) const { return supported_custom_presets_.count(custom_preset); } - ESPDEPRECATED("This method is deprecated, use set_supported_presets() instead", "v1.20") - void set_supports_away(bool supports) { - if (supports) { - supported_presets_.insert(CLIMATE_PRESET_AWAY); - supported_presets_.insert(CLIMATE_PRESET_HOME); - } - } - ESPDEPRECATED("This method is deprecated, use supports_preset() instead", "v1.20") - bool get_supports_away() const { return supports_preset(CLIMATE_PRESET_AWAY); } void set_supported_swing_modes(std::set modes) { supported_swing_modes_ = std::move(modes); } void add_supported_swing_mode(ClimateSwingMode mode) { supported_swing_modes_.insert(mode); } diff --git a/esphome/components/copy/cover/copy_cover.cpp b/esphome/components/copy/cover/copy_cover.cpp index cf50473018..28f8c9877c 100644 --- a/esphome/components/copy/cover/copy_cover.cpp +++ b/esphome/components/copy/cover/copy_cover.cpp @@ -28,6 +28,7 @@ cover::CoverTraits CopyCover::get_traits() { // copy traits manually so it doesn't break when new options are added // but the control() method hasn't implemented them yet. traits.set_is_assumed_state(base.get_is_assumed_state()); + traits.set_supports_stop(base.get_supports_stop()); traits.set_supports_position(base.get_supports_position()); traits.set_supports_tilt(base.get_supports_tilt()); traits.set_supports_toggle(base.get_supports_toggle()); diff --git a/esphome/components/cover/cover.cpp b/esphome/components/cover/cover.cpp index 24dd88b698..d139bab8ee 100644 --- a/esphome/components/cover/cover.cpp +++ b/esphome/components/cover/cover.cpp @@ -145,7 +145,7 @@ CoverCall &CoverCall::set_stop(bool stop) { return *this; } bool CoverCall::get_stop() const { return this->stop_; } -void Cover::set_device_class(const std::string &device_class) { this->device_class_override_ = device_class; } + CoverCall Cover::make_call() { return {this}; } void Cover::open() { auto call = this->make_call(); @@ -204,11 +204,7 @@ optional Cover::restore_state_() { return {}; return recovered; } -std::string Cover::get_device_class() { - if (this->device_class_override_.has_value()) - return *this->device_class_override_; - return ""; -} + bool Cover::is_fully_open() const { return this->position == COVER_OPEN; } bool Cover::is_fully_closed() const { return this->position == COVER_CLOSED; } diff --git a/esphome/components/cover/cover.h b/esphome/components/cover/cover.h index c6a420fa97..d21fbe02be 100644 --- a/esphome/components/cover/cover.h +++ b/esphome/components/cover/cover.h @@ -108,7 +108,7 @@ const char *cover_operation_to_str(CoverOperation op); * to control all values of the cover. Also implement get_traits() to return what operations * the cover supports. */ -class Cover : public EntityBase { +class Cover : public EntityBase, public EntityBase_DeviceClass { public: explicit Cover(); @@ -156,8 +156,6 @@ class Cover : public EntityBase { void publish_state(bool save = true); virtual CoverTraits get_traits() = 0; - void set_device_class(const std::string &device_class); - std::string get_device_class(); /// Helper method to check if the cover is fully open. Equivalent to comparing .position against 1.0 bool is_fully_open() const; @@ -172,7 +170,6 @@ class Cover : public EntityBase { optional restore_state_(); CallbackManager state_callback_{}; - optional device_class_override_{}; ESPPreferenceObject rtc_; }; diff --git a/esphome/components/cover/cover_traits.h b/esphome/components/cover/cover_traits.h index fb30883f77..79001c3b03 100644 --- a/esphome/components/cover/cover_traits.h +++ b/esphome/components/cover/cover_traits.h @@ -15,12 +15,15 @@ class CoverTraits { void set_supports_tilt(bool supports_tilt) { this->supports_tilt_ = supports_tilt; } bool get_supports_toggle() const { return this->supports_toggle_; } void set_supports_toggle(bool supports_toggle) { this->supports_toggle_ = supports_toggle; } + bool get_supports_stop() const { return this->supports_stop_; } + void set_supports_stop(bool supports_stop) { this->supports_stop_ = supports_stop; } protected: bool is_assumed_state_{false}; bool supports_position_{false}; bool supports_tilt_{false}; bool supports_toggle_{false}; + bool supports_stop_{false}; }; } // namespace cover diff --git a/esphome/components/current_based/current_based_cover.cpp b/esphome/components/current_based/current_based_cover.cpp index 7edbdf5a72..ff5ad43997 100644 --- a/esphome/components/current_based/current_based_cover.cpp +++ b/esphome/components/current_based/current_based_cover.cpp @@ -12,6 +12,7 @@ using namespace esphome::cover; CoverTraits CurrentBasedCover::get_traits() { auto traits = CoverTraits(); + traits.set_supports_stop(true); traits.set_supports_position(true); traits.set_supports_toggle(true); traits.set_is_assumed_state(false); diff --git a/esphome/components/debug/__init__.py b/esphome/components/debug/__init__.py index c18baa1cca..9742b3b19e 100644 --- a/esphome/components/debug/__init__.py +++ b/esphome/components/debug/__init__.py @@ -17,26 +17,29 @@ debug_ns = cg.esphome_ns.namespace("debug") DebugComponent = debug_ns.class_("DebugComponent", cg.PollingComponent) -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(DebugComponent), - cv.Optional(CONF_DEVICE): cv.invalid( - "The 'device' option has been moved to the 'debug' text_sensor component" - ), - cv.Optional(CONF_FREE): cv.invalid( - "The 'free' option has been moved to the 'debug' sensor component" - ), - cv.Optional(CONF_BLOCK): cv.invalid( - "The 'block' option has been moved to the 'debug' sensor component" - ), - cv.Optional(CONF_FRAGMENTATION): cv.invalid( - "The 'fragmentation' option has been moved to the 'debug' sensor component" - ), - cv.Optional(CONF_LOOP_TIME): cv.invalid( - "The 'loop_time' option has been moved to the 'debug' sensor component" - ), - } -).extend(cv.polling_component_schema("60s")) +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(DebugComponent), + cv.Optional(CONF_DEVICE): cv.invalid( + "The 'device' option has been moved to the 'debug' text_sensor component" + ), + cv.Optional(CONF_FREE): cv.invalid( + "The 'free' option has been moved to the 'debug' sensor component" + ), + cv.Optional(CONF_BLOCK): cv.invalid( + "The 'block' option has been moved to the 'debug' sensor component" + ), + cv.Optional(CONF_FRAGMENTATION): cv.invalid( + "The 'fragmentation' option has been moved to the 'debug' sensor component" + ), + cv.Optional(CONF_LOOP_TIME): cv.invalid( + "The 'loop_time' option has been moved to the 'debug' sensor component" + ), + } + ).extend(cv.polling_component_schema("60s")), + cv.only_on(["esp32", "esp8266"]), +) async def to_code(config): diff --git a/esphome/components/demo/demo_cover.h b/esphome/components/demo/demo_cover.h index ab039736fb..ec266d46ab 100644 --- a/esphome/components/demo/demo_cover.h +++ b/esphome/components/demo/demo_cover.h @@ -72,6 +72,7 @@ class DemoCover : public cover::Cover, public Component { traits.set_supports_tilt(true); break; case DemoCoverType::TYPE_4: + traits.set_supports_stop(true); traits.set_is_assumed_state(true); traits.set_supports_tilt(true); break; diff --git a/esphome/components/endstop/endstop_cover.cpp b/esphome/components/endstop/endstop_cover.cpp index f468d13492..1190acc46b 100644 --- a/esphome/components/endstop/endstop_cover.cpp +++ b/esphome/components/endstop/endstop_cover.cpp @@ -11,6 +11,7 @@ using namespace esphome::cover; CoverTraits EndstopCover::get_traits() { auto traits = CoverTraits(); + traits.set_supports_stop(true); traits.set_supports_position(true); traits.set_supports_toggle(true); traits.set_is_assumed_state(false); diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 62021afea9..3ca140f0d4 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -163,7 +163,7 @@ RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 0, 5) # The platformio/espressif32 version to use for arduino frameworks # - https://github.com/platformio/platform-espressif32/releases # - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 -ARDUINO_PLATFORM_VERSION = cv.Version(5, 2, 0) +ARDUINO_PLATFORM_VERSION = cv.Version(5, 3, 0) # The default/recommended esp-idf framework version # - https://github.com/espressif/esp-idf/releases @@ -252,7 +252,7 @@ def _parse_platform_version(value): try: # if platform version is a valid version constraint, prefix the default package cv.platformio_version_constraint(value) - return f"platformio/espressif32 @ {value}" + return f"platformio/espressif32@{value}" except cv.Invalid: return value @@ -367,12 +367,12 @@ async def to_code(config): cg.add_build_flag("-Wno-nonnull-compare") cg.add_platformio_option( "platform_packages", - [f"platformio/framework-espidf @ {conf[CONF_SOURCE]}"], + [f"platformio/framework-espidf@{conf[CONF_SOURCE]}"], ) # platformio/toolchain-esp32ulp does not support linux_aarch64 yet and has not been updated for over 2 years # This is espressif's own published version which is more up to date. cg.add_platformio_option( - "platform_packages", ["espressif/toolchain-esp32ulp @ 2.35.0-20220830"] + "platform_packages", ["espressif/toolchain-esp32ulp@2.35.0-20220830"] ) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True) @@ -433,7 +433,7 @@ async def to_code(config): cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO") cg.add_platformio_option( "platform_packages", - [f"platformio/framework-arduinoespressif32 @ {conf[CONF_SOURCE]}"], + [f"platformio/framework-arduinoespressif32@{conf[CONF_SOURCE]}"], ) cg.add_platformio_option("board_build.partitions", "partitions.csv") diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp index b47392bc6b..512a8857b6 100644 --- a/esphome/components/esp32/core.cpp +++ b/esphome/components/esp32/core.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #if ESP_IDF_VERSION_MAJOR >= 4 diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index 3dc4af1058..7db6fff6b9 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -10,7 +10,7 @@ CONFLICTS_WITH = ["esp32_ble_beacon"] CONF_BLE_ID = "ble_id" -NO_BLUTOOTH_VARIANTS = [const.VARIANT_ESP32S2] +NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2] esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble") ESP32BLE = esp32_ble_ns.class_("ESP32BLE", cg.Component) @@ -29,7 +29,7 @@ CONFIG_SCHEMA = cv.Schema( def validate_variant(_): variant = get_esp32_variant() - if variant in NO_BLUTOOTH_VARIANTS: + if variant in NO_BLUETOOTH_VARIANTS: raise cv.Invalid(f"{variant} does not support Bluetooth") diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 59a1f2cd85..674f433d52 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -125,7 +125,7 @@ def _parse_platform_version(value): try: # if platform version is a valid version constraint, prefix the default package cv.platformio_version_constraint(value) - return f"platformio/espressif8266 @ {value}" + return f"platformio/espressif8266@{value}" except cv.Invalid: return value @@ -181,7 +181,7 @@ async def to_code(config): cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION]) cg.add_platformio_option( "platform_packages", - [f"platformio/framework-arduinoespressif8266 @ {conf[CONF_SOURCE]}"], + [f"platformio/framework-arduinoespressif8266@{conf[CONF_SOURCE]}"], ) # Default for platformio is LWIP2_LOW_MEMORY with: diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 447c5b8075..0487ea5498 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -26,8 +26,10 @@ EthernetComponent::EthernetComponent() { global_eth_component = this; } void EthernetComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up Ethernet..."); - // Delay here to allow power to stabilise before Ethernet is initialised. - delay(300); // NOLINT + if (esp_reset_reason() != ESP_RST_DEEPSLEEP) { + // Delay here to allow power to stabilise before Ethernet is initialized. + delay(300); // NOLINT + } esp_err_t err; err = esp_netif_init(); @@ -52,30 +54,29 @@ void EthernetComponent::setup() { esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config); - esp_eth_phy_t *phy; switch (this->type_) { case ETHERNET_TYPE_LAN8720: { - phy = esp_eth_phy_new_lan87xx(&phy_config); + this->phy_ = esp_eth_phy_new_lan87xx(&phy_config); break; } case ETHERNET_TYPE_RTL8201: { - phy = esp_eth_phy_new_rtl8201(&phy_config); + this->phy_ = esp_eth_phy_new_rtl8201(&phy_config); break; } case ETHERNET_TYPE_DP83848: { - phy = esp_eth_phy_new_dp83848(&phy_config); + this->phy_ = esp_eth_phy_new_dp83848(&phy_config); break; } case ETHERNET_TYPE_IP101: { - phy = esp_eth_phy_new_ip101(&phy_config); + this->phy_ = esp_eth_phy_new_ip101(&phy_config); break; } case ETHERNET_TYPE_JL1101: { - phy = esp_eth_phy_new_jl1101(&phy_config); + this->phy_ = esp_eth_phy_new_jl1101(&phy_config); break; } case ETHERNET_TYPE_KSZ8081: { - phy = esp_eth_phy_new_ksz8081(&phy_config); + this->phy_ = esp_eth_phy_new_ksz8081(&phy_config); break; } default: { @@ -84,7 +85,7 @@ void EthernetComponent::setup() { } } - esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy); + esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, this->phy_); this->eth_handle_ = nullptr; err = esp_eth_driver_install(ð_config, &this->eth_handle_); ESPHL_ERROR_CHECK(err, "ETH driver install error"); @@ -356,6 +357,21 @@ std::string EthernetComponent::get_use_address() const { void EthernetComponent::set_use_address(const std::string &use_address) { this->use_address_ = use_address; } +bool EthernetComponent::powerdown() { + ESP_LOGI(TAG, "Powering down ethernet PHY"); + if (this->phy_ == nullptr) { + ESP_LOGE(TAG, "Ethernet PHY not assigned"); + return false; + } + this->connected_ = false; + this->started_ = false; + if (this->phy_->pwrctl(this->phy_, false) != ESP_OK) { + ESP_LOGE(TAG, "Error powering down ethernet PHY"); + return false; + } + return true; +} + } // namespace ethernet } // namespace esphome diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index 0d9ebf29a8..918e47212f 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -45,6 +45,7 @@ class EthernetComponent : public Component { void dump_config() override; float get_setup_priority() const override; bool can_proceed() override; + void on_shutdown() override { powerdown(); } bool is_connected(); void set_phy_addr(uint8_t phy_addr); @@ -58,6 +59,7 @@ class EthernetComponent : public Component { network::IPAddress get_ip_address(); std::string get_use_address() const; void set_use_address(const std::string &use_address); + bool powerdown(); protected: static void eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); @@ -82,6 +84,7 @@ class EthernetComponent : public Component { uint32_t connect_begin_; esp_netif_t *eth_netif_{nullptr}; esp_eth_handle_t eth_handle_; + esp_eth_phy_t *phy_{nullptr}; }; // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/feedback/feedback_cover.cpp b/esphome/components/feedback/feedback_cover.cpp index 213ce7ff8f..117c626f58 100644 --- a/esphome/components/feedback/feedback_cover.cpp +++ b/esphome/components/feedback/feedback_cover.cpp @@ -41,6 +41,7 @@ void FeedbackCover::setup() { CoverTraits FeedbackCover::get_traits() { auto traits = CoverTraits(); + traits.set_supports_stop(true); traits.set_supports_position(true); traits.set_supports_toggle(true); traits.set_is_assumed_state(this->assumed_state_); diff --git a/esphome/components/fingerprint_grow/fingerprint_grow.cpp b/esphome/components/fingerprint_grow/fingerprint_grow.cpp index d27b0ca4cd..4043f32dcb 100644 --- a/esphome/components/fingerprint_grow/fingerprint_grow.cpp +++ b/esphome/components/fingerprint_grow/fingerprint_grow.cpp @@ -77,10 +77,12 @@ void FingerprintGrowComponent::finish_enrollment(uint8_t result) { this->enrollment_done_callback_.call(this->enrollment_slot_); this->get_fingerprint_count_(); } else { - this->enrollment_failed_callback_.call(this->enrollment_slot_); + if (this->enrollment_slot_ != ENROLLMENT_SLOT_UNUSED) { + this->enrollment_failed_callback_.call(this->enrollment_slot_); + } } this->enrollment_image_ = 0; - this->enrollment_slot_ = 0; + this->enrollment_slot_ = ENROLLMENT_SLOT_UNUSED; if (this->enrolling_binary_sensor_ != nullptr) { this->enrolling_binary_sensor_->publish_state(false); } diff --git a/esphome/components/fingerprint_grow/fingerprint_grow.h b/esphome/components/fingerprint_grow/fingerprint_grow.h index fd316237f7..f414146e64 100644 --- a/esphome/components/fingerprint_grow/fingerprint_grow.h +++ b/esphome/components/fingerprint_grow/fingerprint_grow.h @@ -13,6 +13,8 @@ namespace fingerprint_grow { static const uint16_t START_CODE = 0xEF01; +static const uint16_t ENROLLMENT_SLOT_UNUSED = 0xFFFF; + enum GrowPacketType { COMMAND = 0x01, DATA = 0x02, @@ -158,7 +160,7 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic uint32_t new_password_ = -1; GPIOPin *sensing_pin_{nullptr}; uint8_t enrollment_image_ = 0; - uint16_t enrollment_slot_ = 0; + uint16_t enrollment_slot_ = ENROLLMENT_SLOT_UNUSED; uint8_t enrollment_buffers_ = 5; bool waiting_removal_ = false; uint32_t last_aura_led_control_ = 0; diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index e08622a3ae..d80ab1fd1d 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -3,6 +3,7 @@ #include "i2c_bus_arduino.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" +#include "esphome/core/application.h" #include #include @@ -227,10 +228,14 @@ void ArduinoI2CBus::recover_() { // When SCL is kept LOW at this point, we might be looking at a device // that applies clock stretching. Wait for the release of the SCL line, // but not forever. There is no specification for the maximum allowed - // time. We'll stick to 500ms here. - auto wait = 20; + // time. We yield and reset the WDT, so as to avoid triggering reset. + // No point in trying to recover the bus by forcing a uC reset. Bus + // should recover in a few ms or less else not likely to recovery at + // all. + auto wait = 250; while (wait-- && digitalRead(scl_pin_) == LOW) { // NOLINT - delay(25); + App.feed_wdt(); + delayMicroseconds(half_period_usec * 2); } if (digitalRead(scl_pin_) == LOW) { // NOLINT ESP_LOGE(TAG, "Recovery failed: SCL is held LOW during clock pulse cycle"); diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index 5178f6d4f2..51688322f6 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -4,6 +4,7 @@ #include "esphome/core/hal.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" +#include "esphome/core/application.h" #include #include @@ -273,10 +274,14 @@ void IDFI2CBus::recover_() { // When SCL is kept LOW at this point, we might be looking at a device // that applies clock stretching. Wait for the release of the SCL line, // but not forever. There is no specification for the maximum allowed - // time. We'll stick to 500ms here. - auto wait = 20; + // time. We yield and reset the WDT, so as to avoid triggering reset. + // No point in trying to recover the bus by forcing a uC reset. Bus + // should recover in a few ms or less else not likely to recovery at + // all. + auto wait = 250; while (wait-- && gpio_get_level(scl_pin) == 0) { - delay(25); + App.feed_wdt(); + delayMicroseconds(half_period_usec * 2); } if (gpio_get_level(scl_pin) == 0) { ESP_LOGE(TAG, "Recovery failed: SCL is held LOW during clock pulse cycle"); diff --git a/esphome/components/max7219digit/display.py b/esphome/components/max7219digit/display.py index faa8a08f4a..8db9123a39 100644 --- a/esphome/components/max7219digit/display.py +++ b/esphome/components/max7219digit/display.py @@ -93,7 +93,7 @@ async def to_code(config): cg.add(var.set_scroll(config[CONF_SCROLL_ENABLE])) cg.add(var.set_scroll_mode(config[CONF_SCROLL_MODE])) cg.add(var.set_reverse(config[CONF_REVERSE_ENABLE])) - cg.add(var.set_flip_x([CONF_FLIP_X])) + cg.add(var.set_flip_x(config[CONF_FLIP_X])) if CONF_LAMBDA in config: lambda_ = await cg.process_lambda( diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py index e27786a98b..66c84da8d8 100644 --- a/esphome/components/mdns/__init__.py +++ b/esphome/components/mdns/__init__.py @@ -4,10 +4,13 @@ from esphome.const import ( CONF_PROTOCOL, CONF_SERVICES, CONF_SERVICE, + KEY_CORE, + KEY_FRAMEWORK_VERSION, ) import esphome.codegen as cg import esphome.config_validation as cv from esphome.core import CORE, coroutine_with_priority +from esphome.components.esp32 import add_idf_component CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network"] @@ -79,6 +82,16 @@ async def to_code(config): elif CORE.is_rp2040: cg.add_library("LEAmDNS", None) + if CORE.using_esp_idf and CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version( + 5, 0, 0 + ): + add_idf_component( + "mdns", + "https://github.com/espressif/esp-protocols.git", + "mdns-v1.0.9", + "components/mdns", + ) + if config[CONF_DISABLED]: return diff --git a/esphome/components/mlx90614/__init__.py b/esphome/components/mlx90614/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/mlx90614/mlx90614.cpp b/esphome/components/mlx90614/mlx90614.cpp new file mode 100644 index 0000000000..f681f3cc7e --- /dev/null +++ b/esphome/components/mlx90614/mlx90614.cpp @@ -0,0 +1,122 @@ +#include "mlx90614.h" + +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace mlx90614 { + +static const uint8_t MLX90614_RAW_IR_1 = 0x04; +static const uint8_t MLX90614_RAW_IR_2 = 0x05; +static const uint8_t MLX90614_TEMPERATURE_AMBIENT = 0x06; +static const uint8_t MLX90614_TEMPERATURE_OBJECT_1 = 0x07; +static const uint8_t MLX90614_TEMPERATURE_OBJECT_2 = 0x08; + +static const uint8_t MLX90614_TOMAX = 0x20; +static const uint8_t MLX90614_TOMIN = 0x21; +static const uint8_t MLX90614_PWMCTRL = 0x22; +static const uint8_t MLX90614_TARANGE = 0x23; +static const uint8_t MLX90614_EMISSIVITY = 0x24; +static const uint8_t MLX90614_CONFIG = 0x25; +static const uint8_t MLX90614_ADDR = 0x2E; +static const uint8_t MLX90614_ID1 = 0x3C; +static const uint8_t MLX90614_ID2 = 0x3D; +static const uint8_t MLX90614_ID3 = 0x3E; +static const uint8_t MLX90614_ID4 = 0x3F; + +static const char *const TAG = "mlx90614"; + +void MLX90614Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up MLX90614..."); + if (!this->write_emissivity_()) { + ESP_LOGE(TAG, "Communication with MLX90614 failed!"); + this->mark_failed(); + return; + } +} + +bool MLX90614Component::write_emissivity_() { + if (std::isnan(this->emissivity_)) + return true; + uint16_t value = (uint16_t) (this->emissivity_ * 65535); + if (!this->write_bytes_(MLX90614_EMISSIVITY, 0)) { + return false; + } + delay(10); + if (!this->write_bytes_(MLX90614_EMISSIVITY, value)) { + return false; + } + delay(10); + return true; +} + +uint8_t MLX90614Component::crc8_pec_(const uint8_t *data, uint8_t len) { + uint8_t crc = 0; + for (uint8_t i = 0; i < len; i++) { + uint8_t in = data[i]; + for (uint8_t j = 0; j < 8; j++) { + bool carry = (crc ^ in) & 0x80; + crc <<= 1; + if (carry) + crc ^= 0x07; + in <<= 1; + } + } + return crc; +} + +bool MLX90614Component::write_bytes_(uint8_t reg, uint16_t data) { + uint8_t buf[5]; + buf[0] = this->address_ << 1; + buf[1] = reg; + buf[2] = data & 0xFF; + buf[3] = data >> 8; + buf[4] = this->crc8_pec_(buf, 4); + return this->write_bytes(reg, buf + 2, 3); +} + +void MLX90614Component::dump_config() { + ESP_LOGCONFIG(TAG, "MLX90614:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with MLX90614 failed!"); + } + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "Ambient", this->ambient_sensor_); + LOG_SENSOR(" ", "Object", this->object_sensor_); +} + +float MLX90614Component::get_setup_priority() const { return setup_priority::DATA; } + +void MLX90614Component::update() { + uint8_t emissivity[3]; + if (this->read_register(MLX90614_EMISSIVITY, emissivity, 3, false) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + uint8_t raw_object[3]; + if (this->read_register(MLX90614_TEMPERATURE_OBJECT_1, raw_object, 3, false) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + + uint8_t raw_ambient[3]; + if (this->read_register(MLX90614_TEMPERATURE_AMBIENT, raw_ambient, 3, false) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + + float ambient = raw_ambient[1] & 0x80 ? NAN : encode_uint16(raw_ambient[1], raw_ambient[0]) * 0.02f - 273.15f; + float object = raw_object[1] & 0x80 ? NAN : encode_uint16(raw_object[1], raw_object[0]) * 0.02f - 273.15f; + + ESP_LOGD(TAG, "Got Temperature=%.1f°C Ambient=%.1f°C", object, ambient); + + if (this->ambient_sensor_ != nullptr && !std::isnan(ambient)) + this->ambient_sensor_->publish_state(ambient); + if (this->object_sensor_ != nullptr && !std::isnan(object)) + this->object_sensor_->publish_state(object); + this->status_clear_warning(); +} + +} // namespace mlx90614 +} // namespace esphome diff --git a/esphome/components/mlx90614/mlx90614.h b/esphome/components/mlx90614/mlx90614.h new file mode 100644 index 0000000000..b6bd44172d --- /dev/null +++ b/esphome/components/mlx90614/mlx90614.h @@ -0,0 +1,34 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace mlx90614 { + +class MLX90614Component : public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + void update() override; + float get_setup_priority() const override; + + void set_ambient_sensor(sensor::Sensor *ambient_sensor) { ambient_sensor_ = ambient_sensor; } + void set_object_sensor(sensor::Sensor *object_sensor) { object_sensor_ = object_sensor; } + + void set_emissivity(float emissivity) { emissivity_ = emissivity; } + + protected: + bool write_emissivity_(); + + uint8_t crc8_pec_(const uint8_t *data, uint8_t len); + bool write_bytes_(uint8_t reg, uint16_t data); + + sensor::Sensor *ambient_sensor_{nullptr}; + sensor::Sensor *object_sensor_{nullptr}; + + float emissivity_{NAN}; +}; +} // namespace mlx90614 +} // namespace esphome diff --git a/esphome/components/mlx90614/sensor.py b/esphome/components/mlx90614/sensor.py new file mode 100644 index 0000000000..3e90d19e45 --- /dev/null +++ b/esphome/components/mlx90614/sensor.py @@ -0,0 +1,63 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, +) + +CODEOWNERS = ["@jesserockz"] +DEPENDENCIES = ["i2c"] + +CONF_AMBIENT = "ambient" +CONF_EMISSIVITY = "emissivity" +CONF_OBJECT = "object" + +mlx90614_ns = cg.esphome_ns.namespace("mlx90614") +MLX90614Component = mlx90614_ns.class_( + "MLX90614Component", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MLX90614Component), + cv.Optional(CONF_AMBIENT): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_OBJECT): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ).extend( + { + cv.Optional(CONF_EMISSIVITY, default=1.0): cv.percentage, + } + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x5A)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if CONF_AMBIENT in config: + sens = await sensor.new_sensor(config[CONF_AMBIENT]) + cg.add(var.set_ambient_sensor(sens)) + + if CONF_OBJECT in config: + sens = await sensor.new_sensor(config[CONF_OBJECT]) + cg.add(var.set_object_sensor(sens)) + + cg.add(var.set_emissivity(config[CONF_OBJECT][CONF_EMISSIVITY])) diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index e88ffcc37c..d63885fa04 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -75,13 +75,8 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo JsonArray presets = root.createNestedArray("preset_modes"); if (traits.supports_preset(CLIMATE_PRESET_HOME)) presets.add("home"); - if (traits.supports_preset(CLIMATE_PRESET_AWAY)) { - // away_mode_command_topic - root[MQTT_AWAY_MODE_COMMAND_TOPIC] = this->get_away_command_topic(); - // away_mode_state_topic - root[MQTT_AWAY_MODE_STATE_TOPIC] = this->get_away_state_topic(); + if (traits.supports_preset(CLIMATE_PRESET_AWAY)) presets.add("away"); - } if (traits.supports_preset(CLIMATE_PRESET_BOOST)) presets.add("boost"); if (traits.supports_preset(CLIMATE_PRESET_COMFORT)) @@ -197,29 +192,6 @@ void MQTTClimateComponent::setup() { }); } - if (traits.supports_preset(CLIMATE_PRESET_AWAY)) { - this->subscribe(this->get_away_command_topic(), [this](const std::string &topic, const std::string &payload) { - auto onoff = parse_on_off(payload.c_str()); - auto call = this->device_->make_call(); - switch (onoff) { - case PARSE_ON: - call.set_preset(CLIMATE_PRESET_AWAY); - break; - case PARSE_OFF: - call.set_preset(CLIMATE_PRESET_HOME); - break; - case PARSE_TOGGLE: - call.set_preset(this->device_->preset == CLIMATE_PRESET_AWAY ? CLIMATE_PRESET_HOME : CLIMATE_PRESET_AWAY); - break; - case PARSE_NONE: - default: - ESP_LOGW(TAG, "Unknown payload '%s'", payload.c_str()); - return; - } - call.perform(); - }); - } - if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) { this->subscribe(this->get_preset_command_topic(), [this](const std::string &topic, const std::string &payload) { auto call = this->device_->make_call(); @@ -301,11 +273,6 @@ bool MQTTClimateComponent::publish_state_() { success = false; } - if (traits.supports_preset(CLIMATE_PRESET_AWAY)) { - std::string payload = ONOFF(this->device_->preset == CLIMATE_PRESET_AWAY); - if (!this->publish(this->get_away_state_topic(), payload)) - success = false; - } if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) { std::string payload; if (this->device_->preset.has_value()) { diff --git a/esphome/components/number/number_traits.cpp b/esphome/components/number/number_traits.cpp index 1554f8d9c9..dcd05daa2a 100644 --- a/esphome/components/number/number_traits.cpp +++ b/esphome/components/number/number_traits.cpp @@ -16,13 +16,5 @@ std::string NumberTraits::get_unit_of_measurement() { return ""; } -void NumberTraits::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } - -std::string NumberTraits::get_device_class() { - if (this->device_class_.has_value()) - return *this->device_class_; - return ""; -} - } // namespace number } // namespace esphome diff --git a/esphome/components/number/number_traits.h b/esphome/components/number/number_traits.h index ee10b0010c..5b14b77718 100644 --- a/esphome/components/number/number_traits.h +++ b/esphome/components/number/number_traits.h @@ -1,5 +1,6 @@ #pragma once +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" namespace esphome { @@ -11,7 +12,7 @@ enum NumberMode : uint8_t { NUMBER_MODE_SLIDER = 2, }; -class NumberTraits { +class NumberTraits : public EntityBase_DeviceClass { public: // Set/get the number value boundaries. void set_min_value(float min_value) { min_value_ = min_value; } @@ -32,17 +33,12 @@ class NumberTraits { void set_mode(NumberMode mode) { this->mode_ = mode; } NumberMode get_mode() const { return this->mode_; } - // Set/get the device class. - void set_device_class(const std::string &device_class); - std::string get_device_class(); - protected: float min_value_ = NAN; float max_value_ = NAN; float step_ = NAN; optional unit_of_measurement_; ///< Unit of measurement override NumberMode mode_{NUMBER_MODE_AUTO}; - optional device_class_; }; } // namespace number diff --git a/esphome/components/ota/ota_backend_esp_idf.cpp b/esphome/components/ota/ota_backend_esp_idf.cpp index 2fdc00c54d..7688629e39 100644 --- a/esphome/components/ota/ota_backend_esp_idf.cpp +++ b/esphome/components/ota/ota_backend_esp_idf.cpp @@ -8,6 +8,10 @@ #include #include "esphome/components/md5/md5.h" +#if ESP_IDF_VERSION_MAJOR >= 5 +#include +#endif + namespace esphome { namespace ota { @@ -16,9 +20,28 @@ OTAResponseTypes IDFOTABackend::begin(size_t image_size) { if (this->partition_ == nullptr) { return OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION; } - esp_task_wdt_init(15, false); // The following function takes longer than the 5 seconds timeout of WDT + + // The following function takes longer than the 5 seconds timeout of WDT +#if ESP_IDF_VERSION_MAJOR >= 5 + esp_task_wdt_config_t wdtc; + wdtc.timeout_ms = 15000; + wdtc.idle_core_mask = 0; + wdtc.trigger_panic = false; + esp_task_wdt_reconfigure(&wdtc); +#else + esp_task_wdt_init(15, false); +#endif + esp_err_t err = esp_ota_begin(this->partition_, image_size, &this->update_handle_); - esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false); // Set the WDT back to the configured timeout + + // Set the WDT back to the configured timeout +#if ESP_IDF_VERSION_MAJOR >= 5 + wdtc.timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S; + esp_task_wdt_reconfigure(&wdtc); +#else + esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false); +#endif + if (err != ESP_OK) { esp_ota_abort(this->update_handle_); this->update_handle_ = 0; diff --git a/esphome/components/rc522/__init__.py b/esphome/components/rc522/__init__.py index d64cf3c085..1a1e641623 100644 --- a/esphome/components/rc522/__init__.py +++ b/esphome/components/rc522/__init__.py @@ -2,7 +2,12 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation, pins from esphome.components import i2c -from esphome.const import CONF_ON_TAG, CONF_TRIGGER_ID, CONF_RESET_PIN +from esphome.const import ( + CONF_ON_TAG, + CONF_ON_TAG_REMOVED, + CONF_TRIGGER_ID, + CONF_RESET_PIN, +) CODEOWNERS = ["@glmnet"] AUTO_LOAD = ["binary_sensor"] @@ -24,6 +29,11 @@ RC522_SCHEMA = cv.Schema( cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RC522Trigger), } ), + cv.Optional(CONF_ON_TAG_REMOVED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RC522Trigger), + } + ), } ).extend(cv.polling_component_schema("1s")) @@ -37,5 +47,10 @@ async def setup_rc522(var, config): for conf in config.get(CONF_ON_TAG, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) - cg.add(var.register_trigger(trigger)) + cg.add(var.register_ontag_trigger(trigger)) + await automation.build_automation(trigger, [(cg.std_string, "x")], conf) + + for conf in config.get(CONF_ON_TAG_REMOVED, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) + cg.add(var.register_ontagremoved_trigger(trigger)) await automation.build_automation(trigger, [(cg.std_string, "x")], conf) diff --git a/esphome/components/rc522/rc522.cpp b/esphome/components/rc522/rc522.cpp index 5bfeb40156..4e74020e4c 100644 --- a/esphome/components/rc522/rc522.cpp +++ b/esphome/components/rc522/rc522.cpp @@ -256,7 +256,7 @@ void RC522::loop() { this->current_uid_ = rfid_uid; - for (auto *trigger : this->triggers_) + for (auto *trigger : this->triggers_ontag_) trigger->process(rfid_uid); if (report) { @@ -265,6 +265,11 @@ void RC522::loop() { break; } case STATE_DONE: { + if (!this->current_uid_.empty()) { + ESP_LOGV(TAG, "Tag '%s' removed", format_uid(this->current_uid_).c_str()); + for (auto *trigger : this->triggers_ontagremoved_) + trigger->process(this->current_uid_); + } this->current_uid_ = {}; state_ = STATE_INIT; break; diff --git a/esphome/components/rc522/rc522.h b/esphome/components/rc522/rc522.h index 5eea3c665e..c6c5e119f0 100644 --- a/esphome/components/rc522/rc522.h +++ b/esphome/components/rc522/rc522.h @@ -24,7 +24,8 @@ class RC522 : public PollingComponent { void loop() override; void register_tag(RC522BinarySensor *tag) { this->binary_sensors_.push_back(tag); } - void register_trigger(RC522Trigger *trig) { this->triggers_.push_back(trig); } + void register_ontag_trigger(RC522Trigger *trig) { this->triggers_ontag_.push_back(trig); } + void register_ontagremoved_trigger(RC522Trigger *trig) { this->triggers_ontagremoved_.push_back(trig); } void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; } @@ -242,7 +243,8 @@ class RC522 : public PollingComponent { uint8_t reset_count_{0}; uint32_t reset_timeout_{0}; std::vector binary_sensors_; - std::vector triggers_; + std::vector triggers_ontag_; + std::vector triggers_ontagremoved_; std::vector current_uid_; enum RC522Error { diff --git a/esphome/components/rp2040/__init__.py b/esphome/components/rp2040/__init__.py index c6fbcf8deb..3d0d6ec060 100644 --- a/esphome/components/rp2040/__init__.py +++ b/esphome/components/rp2040/__init__.py @@ -102,7 +102,7 @@ def _parse_platform_version(value): try: # if platform version is a valid version constraint, prefix the default package cv.platformio_version_constraint(value) - return f"platformio/raspberrypi @ {value}" + return f"platformio/raspberrypi@{value}" except cv.Invalid: return value @@ -148,7 +148,7 @@ async def to_code(config): cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION]) cg.add_platformio_option( "platform_packages", - [f"earlephilhower/framework-arduinopico @ {conf[CONF_SOURCE]}"], + [f"earlephilhower/framework-arduinopico@{conf[CONF_SOURCE]}"], ) cg.add_platformio_option("board_build.core", "earlephilhower") diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index ac25884697..f0a58d908c 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -25,10 +25,12 @@ from esphome.const import ( CONF_STATE_CLASS, CONF_TO, CONF_TRIGGER_ID, + CONF_TYPE, CONF_UNIT_OF_MEASUREMENT, CONF_WINDOW_SIZE, CONF_MQTT_ID, CONF_FORCE_UPDATE, + CONF_VALUE, DEVICE_CLASS_APPARENT_POWER, DEVICE_CLASS_AQI, DEVICE_CLASS_ATMOSPHERIC_PRESSURE, @@ -476,21 +478,38 @@ async def lambda_filter_to_code(config, filter_id): return cg.new_Pvariable(filter_id, lambda_) +DELTA_SCHEMA = cv.Schema( + { + cv.Required(CONF_VALUE): cv.positive_float, + cv.Optional(CONF_TYPE, default="absolute"): cv.one_of( + "absolute", "percentage", lower=True + ), + } +) + + def validate_delta(config): try: - return (cv.positive_float(config), False) + value = cv.positive_float(config) + return DELTA_SCHEMA({CONF_VALUE: value, CONF_TYPE: "absolute"}) except cv.Invalid: pass try: - return (cv.percentage(config), True) + value = cv.percentage(config) + return DELTA_SCHEMA({CONF_VALUE: value, CONF_TYPE: "percentage"}) except cv.Invalid: pass raise cv.Invalid("Delta filter requires a positive number or percentage value.") -@FILTER_REGISTRY.register("delta", DeltaFilter, validate_delta) +@FILTER_REGISTRY.register("delta", DeltaFilter, cv.Any(DELTA_SCHEMA, validate_delta)) async def delta_filter_to_code(config, filter_id): - return cg.new_Pvariable(filter_id, *config) + percentage = config[CONF_TYPE] == "percentage" + return cg.new_Pvariable( + filter_id, + config[CONF_VALUE], + percentage, + ) @FILTER_REGISTRY.register("or", OrFilter, validate_filters) diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index fc66e03d6b..6ce1e193f5 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -38,13 +38,6 @@ int8_t Sensor::get_accuracy_decimals() { } void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) { this->accuracy_decimals_ = accuracy_decimals; } -std::string Sensor::get_device_class() { - if (this->device_class_.has_value()) - return *this->device_class_; - return ""; -} -void Sensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } - void Sensor::set_state_class(StateClass state_class) { this->state_class_ = state_class; } StateClass Sensor::get_state_class() { if (this->state_class_.has_value()) diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index efcada1411..165d013b2a 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -54,7 +54,7 @@ std::string state_class_to_string(StateClass state_class); * * A sensor has unit of measurement and can use publish_state to send out a new value with the specified accuracy. */ -class Sensor : public EntityBase { +class Sensor : public EntityBase, public EntityBase_DeviceClass { public: explicit Sensor(); @@ -68,11 +68,6 @@ class Sensor : public EntityBase { /// Manually set the accuracy in decimals. void set_accuracy_decimals(int8_t accuracy_decimals); - /// Get the device class, using the manual override if set. - std::string get_device_class(); - /// Manually set the device class. - void set_device_class(const std::string &device_class); - /// Get the state class, using the manual override if set. StateClass get_state_class(); /// Manually set the state class. @@ -165,7 +160,6 @@ class Sensor : public EntityBase { optional unit_of_measurement_; ///< Unit of measurement override optional accuracy_decimals_; ///< Accuracy in decimals override - optional device_class_; ///< Device class override optional state_class_{STATE_CLASS_NONE}; ///< State class override bool force_update_{false}; ///< Force update mode bool has_state_{false}; diff --git a/esphome/components/sun/sun.cpp b/esphome/components/sun/sun.cpp index 54aaf0942b..5f9179682a 100644 --- a/esphome/components/sun/sun.cpp +++ b/esphome/components/sun/sun.cpp @@ -287,18 +287,17 @@ HorizontalCoordinate Sun::calc_coords_() { */ return sun.true_coordinate(m); } -optional Sun::calc_event_(bool rising, double zenith) { +optional Sun::calc_event_(time::ESPTime date, bool rising, double zenith) { SunAtLocation sun{location_}; - auto now = this->time_->utcnow(); - if (!now.is_valid()) + if (!date.is_valid()) return {}; // Calculate UT1 timestamp at 0h - auto today = now; + auto today = date; today.hour = today.minute = today.second = 0; today.recalc_timestamp_utc(); auto it = sun.event(rising, today, zenith); - if (it.has_value() && it->timestamp < now.timestamp) { + if (it.has_value() && it->timestamp < date.timestamp) { // We're calculating *next* sunrise/sunset, but calculated event // is today, so try again tomorrow time_t new_timestamp = today.timestamp + 24 * 60 * 60; @@ -307,9 +306,19 @@ optional Sun::calc_event_(bool rising, double zenith) { } return it; } +optional Sun::calc_event_(bool rising, double zenith) { + auto it = Sun::calc_event_(this->time_->utcnow(), rising, zenith); + return it; +} optional Sun::sunrise(double elevation) { return this->calc_event_(true, 90 - elevation); } optional Sun::sunset(double elevation) { return this->calc_event_(false, 90 - elevation); } +optional Sun::sunrise(time::ESPTime date, double elevation) { + return this->calc_event_(date, true, 90 - elevation); +} +optional Sun::sunset(time::ESPTime date, double elevation) { + return this->calc_event_(date, false, 90 - elevation); +} double Sun::elevation() { return this->calc_coords_().elevation; } double Sun::azimuth() { return this->calc_coords_().azimuth; } diff --git a/esphome/components/sun/sun.h b/esphome/components/sun/sun.h index efc6a1ab0a..9547b2f280 100644 --- a/esphome/components/sun/sun.h +++ b/esphome/components/sun/sun.h @@ -59,6 +59,8 @@ class Sun { optional sunrise(double elevation); optional sunset(double elevation); + optional sunrise(time::ESPTime date, double elevation); + optional sunset(time::ESPTime date, double elevation); double elevation(); double azimuth(); @@ -66,6 +68,7 @@ class Sun { protected: internal::HorizontalCoordinate calc_coords_(); optional calc_event_(bool rising, double zenith); + optional calc_event_(time::ESPTime date, bool rising, double zenith); time::RealTimeClock *time_; internal::GeoLocation location_; diff --git a/esphome/components/switch/switch.cpp b/esphome/components/switch/switch.cpp index 72e7add158..96611b0b87 100644 --- a/esphome/components/switch/switch.cpp +++ b/esphome/components/switch/switch.cpp @@ -63,13 +63,6 @@ void Switch::add_on_state_callback(std::function &&callback) { void Switch::set_inverted(bool inverted) { this->inverted_ = inverted; } bool Switch::is_inverted() const { return this->inverted_; } -std::string Switch::get_device_class() { - if (this->device_class_.has_value()) - return *this->device_class_; - return ""; -} -void Switch::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } - void log_switch(const char *tag, const char *prefix, const char *type, Switch *obj) { if (obj != nullptr) { ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str()); diff --git a/esphome/components/switch/switch.h b/esphome/components/switch/switch.h index 8bea3b36db..9daac4ee23 100644 --- a/esphome/components/switch/switch.h +++ b/esphome/components/switch/switch.h @@ -29,7 +29,7 @@ enum SwitchRestoreMode { * A switch is basically just a combination of a binary sensor (for reporting switch values) * and a write_state method that writes a state to the hardware. */ -class Switch : public EntityBase { +class Switch : public EntityBase, public EntityBase_DeviceClass { public: explicit Switch(); @@ -103,10 +103,6 @@ class Switch : public EntityBase { bool is_inverted() const; - /// Get the device class for this switch. - std::string get_device_class(); - /// Set the Home Assistant device class for this switch. - void set_device_class(const std::string &device_class); void set_restore_mode(SwitchRestoreMode restore_mode) { this->restore_mode = restore_mode; } protected: @@ -124,7 +120,6 @@ class Switch : public EntityBase { bool inverted_{false}; Deduplicator publish_dedup_; ESPPreferenceObject rtc_; - optional device_class_; }; #define LOG_SWITCH(prefix, type, obj) log_switch((TAG), (prefix), LOG_STR_LITERAL(type), (obj)) diff --git a/esphome/components/template/cover/__init__.py b/esphome/components/template/cover/__init__.py index a628da70d2..8844ddd6ab 100644 --- a/esphome/components/template/cover/__init__.py +++ b/esphome/components/template/cover/__init__.py @@ -73,6 +73,7 @@ async def to_code(config): await automation.build_automation( var.get_stop_trigger(), [], config[CONF_STOP_ACTION] ) + cg.add(var.set_has_stop(True)) if CONF_TILT_ACTION in config: await automation.build_automation( var.get_tilt_trigger(), [(float, "tilt")], config[CONF_TILT_ACTION] diff --git a/esphome/components/template/cover/template_cover.cpp b/esphome/components/template/cover/template_cover.cpp index 47c651e643..b16e439943 100644 --- a/esphome/components/template/cover/template_cover.cpp +++ b/esphome/components/template/cover/template_cover.cpp @@ -109,6 +109,7 @@ void TemplateCover::control(const CoverCall &call) { CoverTraits TemplateCover::get_traits() { auto traits = CoverTraits(); traits.set_is_assumed_state(this->assumed_state_); + traits.set_supports_stop(this->has_stop_); traits.set_supports_position(this->has_position_); traits.set_supports_tilt(this->has_tilt_); return traits; @@ -116,6 +117,7 @@ CoverTraits TemplateCover::get_traits() { Trigger *TemplateCover::get_position_trigger() const { return this->position_trigger_; } Trigger *TemplateCover::get_tilt_trigger() const { return this->tilt_trigger_; } void TemplateCover::set_tilt_lambda(std::function()> &&tilt_f) { this->tilt_f_ = tilt_f; } +void TemplateCover::set_has_stop(bool has_stop) { this->has_stop_ = has_stop; } void TemplateCover::set_has_position(bool has_position) { this->has_position_ = has_position; } void TemplateCover::set_has_tilt(bool has_tilt) { this->has_tilt_ = has_tilt; } void TemplateCover::stop_prev_trigger_() { diff --git a/esphome/components/template/cover/template_cover.h b/esphome/components/template/cover/template_cover.h index 3b9dcea50b..4ff5caf1db 100644 --- a/esphome/components/template/cover/template_cover.h +++ b/esphome/components/template/cover/template_cover.h @@ -26,6 +26,7 @@ class TemplateCover : public cover::Cover, public Component { void set_optimistic(bool optimistic); void set_assumed_state(bool assumed_state); void set_tilt_lambda(std::function()> &&tilt_f); + void set_has_stop(bool has_stop); void set_has_position(bool has_position); void set_has_tilt(bool has_tilt); void set_restore_mode(TemplateCoverRestoreMode restore_mode) { restore_mode_ = restore_mode; } @@ -48,6 +49,7 @@ class TemplateCover : public cover::Cover, public Component { bool optimistic_{false}; Trigger<> *open_trigger_; Trigger<> *close_trigger_; + bool has_stop_{false}; Trigger<> *stop_trigger_; Trigger<> *prev_command_trigger_{nullptr}; Trigger *position_trigger_; diff --git a/esphome/components/time_based/time_based_cover.cpp b/esphome/components/time_based/time_based_cover.cpp index a7ba6d0595..50376224a9 100644 --- a/esphome/components/time_based/time_based_cover.cpp +++ b/esphome/components/time_based/time_based_cover.cpp @@ -51,6 +51,7 @@ void TimeBasedCover::loop() { float TimeBasedCover::get_setup_priority() const { return setup_priority::DATA; } CoverTraits TimeBasedCover::get_traits() { auto traits = CoverTraits(); + traits.set_supports_stop(true); traits.set_supports_position(true); traits.set_supports_toggle(true); traits.set_is_assumed_state(this->assumed_state_); diff --git a/esphome/components/tuya/cover/tuya_cover.cpp b/esphome/components/tuya/cover/tuya_cover.cpp index 11a458449f..fcb961f45e 100644 --- a/esphome/components/tuya/cover/tuya_cover.cpp +++ b/esphome/components/tuya/cover/tuya_cover.cpp @@ -128,6 +128,7 @@ void TuyaCover::dump_config() { cover::CoverTraits TuyaCover::get_traits() { auto traits = cover::CoverTraits(); + traits.set_supports_stop(true); traits.set_supports_position(true); return traits; } diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 777bef4edb..e2d5bea90a 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -63,7 +63,10 @@ void VoiceAssistant::start(struct sockaddr_storage *addr, uint16_t port) { void VoiceAssistant::request_start() { ESP_LOGD(TAG, "Requesting start..."); - api::global_api_server->start_voice_assistant(); + if (!api::global_api_server->start_voice_assistant()) { + ESP_LOGW(TAG, "Could not request start."); + this->error_trigger_->trigger("not-connected", "Could not request start."); + } } void VoiceAssistant::signal_stop() { diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index f5684f06f7..c9da07795c 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -252,6 +252,7 @@ def _validate(config): CONF_OUTPUT_POWER = "output_power" +CONF_PASSIVE_SCAN = "passive_scan" CONFIG_SCHEMA = cv.All( cv.Schema( { @@ -280,6 +281,7 @@ CONFIG_SCHEMA = cv.All( cv.SplitDefault(CONF_ENABLE_RRM, esp32_idf=False): cv.All( cv.boolean, cv.only_with_esp_idf ), + cv.Optional(CONF_PASSIVE_SCAN, default=False): cv.boolean, cv.Optional("enable_mdns"): cv.invalid( "This option has been removed. Please use the [disabled] option under the " "new mdns component instead." @@ -379,6 +381,7 @@ async def to_code(config): cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) cg.add(var.set_power_save_mode(config[CONF_POWER_SAVE_MODE])) cg.add(var.set_fast_connect(config[CONF_FAST_CONNECT])) + cg.add(var.set_passive_scan(config[CONF_PASSIVE_SCAN])) if CONF_OUTPUT_POWER in config: cg.add(var.set_output_power(config[CONF_OUTPUT_POWER])) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index efb1af171d..9f047dd5ed 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -385,7 +385,7 @@ void WiFiComponent::print_connect_params_() { void WiFiComponent::start_scanning() { this->action_started_ = millis(); ESP_LOGD(TAG, "Starting scan..."); - this->wifi_scan_start_(); + this->wifi_scan_start_(this->passive_scan_); this->state_ = WIFI_COMPONENT_STATE_STA_SCANNING; } @@ -615,6 +615,8 @@ bool WiFiComponent::is_connected() { } void WiFiComponent::set_power_save_mode(WiFiPowerSaveMode power_save) { this->power_save_ = power_save; } +void WiFiComponent::set_passive_scan(bool passive) { this->passive_scan_ = passive; } + std::string WiFiComponent::format_mac_addr(const uint8_t *mac) { char buf[20]; sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 544cb3dc61..3f81b94cce 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -217,6 +217,8 @@ class WiFiComponent : public Component { void set_power_save_mode(WiFiPowerSaveMode power_save); void set_output_power(float output_power) { output_power_ = output_power; } + void set_passive_scan(bool passive); + void save_wifi_sta(const std::string &ssid, const std::string &password); // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) @@ -294,7 +296,7 @@ class WiFiComponent : public Component { bool wifi_sta_connect_(const WiFiAP &ap); void wifi_pre_setup_(); WiFiSTAConnectStatus wifi_sta_connect_status_(); - bool wifi_scan_start_(); + bool wifi_scan_start_(bool passive); bool wifi_ap_ip_config_(optional manual_ip); bool wifi_start_ap_(const WiFiAP &ap); bool wifi_disconnect_(); @@ -349,6 +351,7 @@ class WiFiComponent : public Component { bool scan_done_{false}; bool ap_setup_{false}; optional output_power_; + bool passive_scan_{false}; ESPPreferenceObject pref_; bool has_saved_wifi_settings_{false}; #ifdef USE_WIFI_11KV_SUPPORT diff --git a/esphome/components/wifi/wifi_component_esp32_arduino.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp index ab04224161..f35f5dfc43 100644 --- a/esphome/components/wifi/wifi_component_esp32_arduino.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -618,13 +618,13 @@ WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { } return WiFiSTAConnectStatus::IDLE; } -bool WiFiComponent::wifi_scan_start_() { +bool WiFiComponent::wifi_scan_start_(bool passive) { // enable STA if (!this->wifi_mode_(true, {})) return false; // need to use WiFi because of WiFiScanClass allocations :( - int16_t err = WiFi.scanNetworks(true, true, false, 200); + int16_t err = WiFi.scanNetworks(true, true, passive, 200); if (err != WIFI_SCAN_RUNNING) { ESP_LOGV(TAG, "WiFi.scanNetworks failed! %d", err); return false; diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index de4253fe41..8b38297b17 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -601,7 +601,7 @@ WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { return WiFiSTAConnectStatus::IDLE; } } -bool WiFiComponent::wifi_scan_start_() { +bool WiFiComponent::wifi_scan_start_(bool passive) { static bool first_scan = false; // enable STA @@ -615,13 +615,21 @@ bool WiFiComponent::wifi_scan_start_() { config.channel = 0; config.show_hidden = 1; #if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) - config.scan_type = WIFI_SCAN_TYPE_ACTIVE; + config.scan_type = passive ? WIFI_SCAN_TYPE_PASSIVE : WIFI_SCAN_TYPE_ACTIVE; if (first_scan) { - config.scan_time.active.min = 100; - config.scan_time.active.max = 200; + if (passive) { + config.scan_time.passive = 200; + } else { + config.scan_time.active.min = 100; + config.scan_time.active.max = 200; + } } else { - config.scan_time.active.min = 400; - config.scan_time.active.max = 500; + if (passive) { + config.scan_time.passive = 500; + } else { + config.scan_time.active.min = 400; + config.scan_time.active.max = 500; + } } #endif first_scan = false; diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 1edde74743..1c70f33040 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -17,6 +17,7 @@ #ifdef USE_WIFI_WPA2_EAP #include #endif +#include "dhcpserver/dhcpserver.h" #include "lwip/err.h" #include "lwip/dns.h" @@ -32,7 +33,7 @@ namespace wifi { static const char *const TAG = "wifi_esp32"; static EventGroupHandle_t s_wifi_event_group; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -static xQueueHandle s_event_queue; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static QueueHandle_t s_event_queue; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static esp_netif_t *s_sta_netif = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static esp_netif_t *s_ap_netif = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static bool s_sta_started = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) @@ -414,17 +415,17 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { if (!this->wifi_mode_(true, {})) return false; - tcpip_adapter_dhcp_status_t dhcp_status; - esp_err_t err = tcpip_adapter_dhcpc_get_status(TCPIP_ADAPTER_IF_STA, &dhcp_status); + esp_netif_dhcp_status_t dhcp_status; + esp_err_t err = esp_netif_dhcpc_get_status(s_sta_netif, &dhcp_status); if (err != ESP_OK) { - ESP_LOGV(TAG, "tcpip_adapter_dhcpc_get_status failed: %s", esp_err_to_name(err)); + ESP_LOGV(TAG, "esp_netif_dhcpc_get_status failed: %s", esp_err_to_name(err)); return false; } if (!manual_ip.has_value()) { - // Use DHCP client - if (dhcp_status != TCPIP_ADAPTER_DHCP_STARTED) { - err = tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA); + // No manual IP is set; use DHCP client + if (dhcp_status != ESP_NETIF_DHCP_STARTED) { + err = esp_netif_dhcpc_start(s_sta_netif); if (err != ESP_OK) { ESP_LOGV(TAG, "Starting DHCP client failed! %d", err); } @@ -433,43 +434,29 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { return true; } - tcpip_adapter_ip_info_t info; - memset(&info, 0, sizeof(info)); + esp_netif_ip_info_t info; // struct of ip4_addr_t with ip, netmask, gw info.ip.addr = static_cast(manual_ip->static_ip); info.gw.addr = static_cast(manual_ip->gateway); info.netmask.addr = static_cast(manual_ip->subnet); - - err = tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); + err = esp_netif_dhcpc_stop(s_sta_netif); if (err != ESP_OK && err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED) { - ESP_LOGV(TAG, "tcpip_adapter_dhcpc_stop failed: %s", esp_err_to_name(err)); + ESP_LOGV(TAG, "esp_netif_dhcpc_stop failed: %s", esp_err_to_name(err)); return false; } - - err = tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &info); + err = esp_netif_set_ip_info(s_sta_netif, &info); if (err != ESP_OK) { - ESP_LOGV(TAG, "tcpip_adapter_set_ip_info failed: %s", esp_err_to_name(err)); + ESP_LOGV(TAG, "esp_netif_set_ip_info failed: %s", esp_err_to_name(err)); return false; } - ip_addr_t dns; -#if LWIP_IPV6 - dns.type = IPADDR_TYPE_V4; -#endif + esp_netif_dns_info_t dns; if (uint32_t(manual_ip->dns1) != 0) { -#if LWIP_IPV6 - dns.u_addr.ip4.addr = static_cast(manual_ip->dns1); -#else - dns.addr = static_cast(manual_ip->dns1); -#endif - dns_setserver(0, &dns); + dns.ip.u_addr.ip4.addr = static_cast(manual_ip->dns1); + esp_netif_set_dns_info(s_sta_netif, ESP_NETIF_DNS_MAIN, &dns); } if (uint32_t(manual_ip->dns2) != 0) { -#if LWIP_IPV6 - dns.u_addr.ip4.addr = static_cast(manual_ip->dns2); -#else - dns.addr = static_cast(manual_ip->dns2); -#endif - dns_setserver(1, &dns); + dns.ip.u_addr.ip4.addr = static_cast(manual_ip->dns2); + esp_netif_set_dns_info(s_sta_netif, ESP_NETIF_DNS_BACKUP, &dns); } return true; @@ -478,10 +465,10 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { network::IPAddress WiFiComponent::wifi_sta_ip() { if (!this->has_sta()) return {}; - tcpip_adapter_ip_info_t ip; - esp_err_t err = tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip); + esp_netif_ip_info_t ip; + esp_err_t err = esp_netif_get_ip_info(s_sta_netif, &ip); if (err != ESP_OK) { - ESP_LOGV(TAG, "tcpip_adapter_get_ip_info failed: %s", esp_err_to_name(err)); + ESP_LOGV(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err)); return false; } return {ip.ip.addr}; @@ -601,9 +588,9 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_START) { ESP_LOGV(TAG, "Event: WiFi STA start"); // apply hostname - err = tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, App.get_name().c_str()); + err = esp_netif_set_hostname(s_sta_netif, App.get_name().c_str()); if (err != ERR_OK) { - ESP_LOGW(TAG, "tcpip_adapter_set_hostname failed: %s", esp_err_to_name(err)); + ESP_LOGW(TAG, "esp_netif_set_hostname failed: %s", esp_err_to_name(err)); } s_sta_started = true; @@ -651,7 +638,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_GOT_IP) { const auto &it = data->data.ip_got_ip; #if LWIP_IPV6_AUTOCONFIG - tcpip_adapter_create_ip6_linklocal(TCPIP_ADAPTER_IF_STA); + esp_netif_create_ip6_linklocal(s_sta_netif); #endif ESP_LOGV(TAG, "Event: Got IP static_ip=%s gateway=%s", format_ip4_addr(it.ip_info.ip).c_str(), format_ip4_addr(it.ip_info.gw).c_str()); @@ -736,7 +723,7 @@ WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { } return WiFiSTAConnectStatus::IDLE; } -bool WiFiComponent::wifi_scan_start_() { +bool WiFiComponent::wifi_scan_start_(bool passive) { // enable STA if (!this->wifi_mode_(true, {})) return false; @@ -746,9 +733,13 @@ bool WiFiComponent::wifi_scan_start_() { config.bssid = nullptr; config.channel = 0; config.show_hidden = true; - config.scan_type = WIFI_SCAN_TYPE_ACTIVE; - config.scan_time.active.min = 100; - config.scan_time.active.max = 300; + config.scan_type = passive ? WIFI_SCAN_TYPE_PASSIVE : WIFI_SCAN_TYPE_ACTIVE; + if (passive) { + config.scan_time.passive = 300; + } else { + config.scan_time.active.min = 100; + config.scan_time.active.max = 300; + } esp_err_t err = esp_wifi_scan_start(&config, false); if (err != ESP_OK) { @@ -766,8 +757,7 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { if (!this->wifi_mode_({}, true)) return false; - tcpip_adapter_ip_info_t info; - memset(&info, 0, sizeof(info)); + esp_netif_ip_info_t info; if (manual_ip.has_value()) { info.ip.addr = static_cast(manual_ip->static_ip); info.gw.addr = static_cast(manual_ip->gateway); @@ -777,17 +767,17 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { info.gw.addr = static_cast(network::IPAddress(192, 168, 4, 1)); info.netmask.addr = static_cast(network::IPAddress(255, 255, 255, 0)); } - tcpip_adapter_dhcp_status_t dhcp_status; - tcpip_adapter_dhcps_get_status(TCPIP_ADAPTER_IF_AP, &dhcp_status); - err = tcpip_adapter_dhcps_stop(TCPIP_ADAPTER_IF_AP); + esp_netif_dhcp_status_t dhcp_status; + esp_netif_dhcps_get_status(s_sta_netif, &dhcp_status); + err = esp_netif_dhcps_stop(s_sta_netif); if (err != ESP_OK) { - ESP_LOGV(TAG, "tcpip_adapter_dhcps_stop failed! %d", err); + ESP_LOGV(TAG, "esp_netif_dhcps_stop failed! %d", err); return false; } - err = tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_AP, &info); + err = esp_netif_set_ip_info(s_sta_netif, &info); if (err != ESP_OK) { - ESP_LOGV(TAG, "tcpip_adapter_set_ip_info failed! %d", err); + ESP_LOGV(TAG, "esp_netif_set_ip_info failed! %d", err); return false; } @@ -800,17 +790,17 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { start_address[3] += 100; lease.end_ip.addr = static_cast(start_address); ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str()); - err = tcpip_adapter_dhcps_option(TCPIP_ADAPTER_OP_SET, TCPIP_ADAPTER_REQUESTED_IP_ADDRESS, &lease, sizeof(lease)); + err = esp_netif_dhcps_option(s_sta_netif, ESP_NETIF_OP_SET, ESP_NETIF_REQUESTED_IP_ADDRESS, &lease, sizeof(lease)); if (err != ESP_OK) { - ESP_LOGV(TAG, "tcpip_adapter_dhcps_option failed! %d", err); + ESP_LOGV(TAG, "esp_netif_dhcps_option failed! %d", err); return false; } - err = tcpip_adapter_dhcps_start(TCPIP_ADAPTER_IF_AP); + err = esp_netif_dhcps_start(s_sta_netif); if (err != ESP_OK) { - ESP_LOGV(TAG, "tcpip_adapter_dhcps_start failed! %d", err); + ESP_LOGV(TAG, "esp_netif_dhcps_start failed! %d", err); return false; } @@ -856,8 +846,8 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { return true; } network::IPAddress WiFiComponent::wifi_soft_ap_ip() { - tcpip_adapter_ip_info_t ip; - tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ip); + esp_netif_ip_info_t ip; + esp_netif_get_ip_info(s_sta_netif, &ip); return {ip.ip.addr}; } bool WiFiComponent::wifi_disconnect_() { return esp_wifi_disconnect(); } diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index 8e64878f8e..489ebc3699 100644 --- a/esphome/components/wifi/wifi_component_pico_w.cpp +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -125,10 +125,11 @@ void WiFiComponent::wifi_scan_result(void *env, const cyw43_ev_scan_result_t *re } } -bool WiFiComponent::wifi_scan_start_() { +bool WiFiComponent::wifi_scan_start_(bool passive) { this->scan_result_.clear(); this->scan_done_ = false; cyw43_wifi_scan_options_t scan_options = {0}; + scan_options.scan_type = passive ? 1 : 0; int err = cyw43_wifi_scan(&cyw43_state, &scan_options, nullptr, &s_wifi_scan_result); if (err) { ESP_LOGV(TAG, "cyw43_wifi_scan failed!"); diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index 3d61e36fd1..1e2ccc35b5 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -72,6 +72,16 @@ void EntityBase::calc_object_id_() { this->object_id_hash_ = fnv1_hash(this->object_id_c_str_); } } + uint32_t EntityBase::get_object_id_hash() { return this->object_id_hash_; } +std::string EntityBase_DeviceClass::get_device_class() { + if (this->device_class_ == nullptr) { + return ""; + } + return this->device_class_; +} + +void EntityBase_DeviceClass::set_device_class(const char *device_class) { this->device_class_ = device_class; } + } // namespace esphome diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index e40a7013bf..d717674450 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -63,4 +63,15 @@ class EntityBase { EntityCategory entity_category_{ENTITY_CATEGORY_NONE}; }; +class EntityBase_DeviceClass { + public: + /// Get the device class, using the manual override if set. + std::string get_device_class(); + /// Manually set the device class. + void set_device_class(const char *device_class); + + protected: + const char *device_class_{nullptr}; ///< Device class override +}; + } // namespace esphome diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 7f5c3ad333..7e8ba41987 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -18,6 +18,8 @@ #elif defined(USE_ESP32_FRAMEWORK_ARDUINO) #include #elif defined(USE_ESP_IDF) +#include "esp_mac.h" +#include "esp_random.h" #include "esp_system.h" #include #include diff --git a/platformio.ini b/platformio.ini index 06823d5dd2..e10c400436 100644 --- a/platformio.ini +++ b/platformio.ini @@ -80,9 +80,9 @@ build_flags = ; This are common settings for the ESP8266 using Arduino. [common:esp8266-arduino] extends = common:arduino -platform = platformio/espressif8266 @ 3.2.0 +platform = platformio/espressif8266@3.2.0 platform_packages = - platformio/framework-arduinoespressif8266 @ ~3.30002.0 + platformio/framework-arduinoespressif8266@~3.30002.0 framework = arduino lib_deps = @@ -104,9 +104,9 @@ extra_scripts = post:esphome/components/esp8266/post_build.py.script ; This are common settings for the ESP32 (all variants) using Arduino. [common:esp32-arduino] extends = common:arduino -platform = platformio/espressif32 @ 5.2.0 +platform = platformio/espressif32@5.3.0 platform_packages = - platformio/framework-arduinoespressif32 @ ~3.20005.0 + platformio/framework-arduinoespressif32@~3.20005.0 framework = arduino board = nodemcu-32s @@ -134,9 +134,9 @@ extra_scripts = post:esphome/components/esp32/post_build.py.script ; This are common settings for the ESP32 (all variants) using IDF. [common:esp32-idf] extends = common:idf -platform = platformio/espressif32 @ 5.3.0 +platform = platformio/espressif32@5.3.0 platform_packages = - platformio/framework-espidf @ ~3.40404.0 + platformio/framework-espidf@~3.40404.0 framework = espidf lib_deps = @@ -157,8 +157,8 @@ board_build.filesystem_size = 0.5m platform = https://github.com/maxgerhardt/platform-raspberrypi.git platform_packages = - ; earlephilhower/framework-arduinopico @ ~1.20602.0 ; Cannot use the platformio package until old releases stop getting deleted - earlephilhower/framework-arduinopico @ https://github.com/earlephilhower/arduino-pico/releases/download/2.6.2/rp2040-2.6.2.zip + ; earlephilhower/framework-arduinopico@~1.20602.0 ; Cannot use the platformio package until old releases stop getting deleted + earlephilhower/framework-arduinopico@https://github.com/earlephilhower/arduino-pico/releases/download/2.6.2/rp2040-2.6.2.zip framework = arduino lib_deps = diff --git a/requirements.txt b/requirements.txt index 5f73cf3a06..41fedc88cf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ voluptuous==0.13.1 PyYAML==6.0 paho-mqtt==1.6.1 colorama==0.4.6 -tornado==6.2 +tornado==6.3.1 tzlocal==4.2 # from time tzdata>=2021.1 # from time pyserial==3.5 @@ -10,9 +10,12 @@ platformio==6.1.6 # When updating platformio, also update Dockerfile esptool==4.5.1 click==8.1.3 esphome-dashboard==20230214.0 -aioesphomeapi==13.7.0 +aioesphomeapi==13.7.1 zeroconf==0.56.0 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 kconfiglib==13.7.1 + +# esp-idf >= 5.0 requires this +pyparsing >= 3.0 diff --git a/requirements_test.txt b/requirements_test.txt index b063dd2797..b18aabe7b4 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,11 +1,11 @@ -pylint==2.17.2 +pylint==2.17.3 flake8==6.0.0 # also change in .pre-commit-config.yaml when updating black==23.3.0 # also change in .pre-commit-config.yaml when updating pyupgrade==3.3.1 # also change in .pre-commit-config.yaml when updating pre-commit # Unit tests -pytest==7.3.0 +pytest==7.3.1 pytest-cov==4.0.0 pytest-mock==3.10.0 pytest-asyncio==0.21.0 diff --git a/script/platformio_install_deps.py b/script/platformio_install_deps.py new file mode 100755 index 0000000000..2340410161 --- /dev/null +++ b/script/platformio_install_deps.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# This script is used to preinstall +# all platformio libraries in the global storage + +import configparser +import subprocess +import sys + +config = configparser.ConfigParser(inline_comment_prefixes=(";",)) +config.read(sys.argv[1]) + +libs = [] +tools = [] +platforms = [] +# Extract from every lib_deps key in all sections +for section in config.sections(): + conf = config[section] + if "lib_deps" in conf: + for lib_dep in conf["lib_deps"].splitlines(): + if not lib_dep: + # Empty line or comment + continue + if lib_dep.startswith("${"): + # Extending from another section + continue + if "@" not in lib_dep: + # No version pinned, this is an internal lib + continue + libs.append("-l") + libs.append(lib_dep) + if "platform" in conf: + platforms.append("-p") + platforms.append(conf["platform"]) + if "platform_packages" in conf: + for tool in conf["platform_packages"].splitlines(): + if not tool: + # Empty line or comment + continue + if tool.startswith("${"): + # Extending from another section + continue + if tool.find("https://github.com") != -1: + split = tool.find("@") + tool = tool[split + 1 :] + tools.append("-t") + tools.append(tool) + +subprocess.check_call(["platformio", "pkg", "install", "-g", *libs, *platforms, *tools]) diff --git a/script/setup b/script/setup index c650960f05..5acd1a9f13 100755 --- a/script/setup +++ b/script/setup @@ -14,3 +14,5 @@ pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_te pip3 install --no-use-pep517 -e . pre-commit install + +script/platformio_install_deps.py platformio.ini diff --git a/tests/test1.yaml b/tests/test1.yaml index 2d6dbcbd21..dc21c1c3dc 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1247,6 +1247,13 @@ sensor: temperature: name: Max9611 Temp update_interval: 1s + - platform: mlx90614 + i2c_id: i2c_bus + ambient: + name: Ambient + object: + name: Object + emissivity: 1.0 - platform: mpl3115a2 i2c_id: i2c_bus temperature: