Merge branch 'dev' into optolink

This commit is contained in:
j0ta29 2023-04-28 14:56:22 +02:00 committed by GitHub
commit d4a43157f3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
86 changed files with 669 additions and 368 deletions

View file

@ -11,6 +11,7 @@ on:
- ".github/workflows/**" - ".github/workflows/**"
- "requirements*.txt" - "requirements*.txt"
- "platformio.ini" - "platformio.ini"
- "script/platformio_install_deps.py"
pull_request: pull_request:
paths: paths:
@ -18,6 +19,7 @@ on:
- ".github/workflows/**" - ".github/workflows/**"
- "requirements*.txt" - "requirements*.txt"
- "platformio.ini" - "platformio.ini"
- "script/platformio_install_deps.py"
permissions: permissions:
contents: read contents: read

View file

@ -21,6 +21,7 @@ esphome/components/airthings_wave_mini/* @ncareau
esphome/components/airthings_wave_plus/* @jeromelaban esphome/components/airthings_wave_plus/* @jeromelaban
esphome/components/am43/* @buxtronix esphome/components/am43/* @buxtronix
esphome/components/am43/cover/* @buxtronix esphome/components/am43/cover/* @buxtronix
esphome/components/am43/sensor/* @buxtronix
esphome/components/analog_threshold/* @ianchi esphome/components/analog_threshold/* @ianchi
esphome/components/animation/* @syndlex esphome/components/animation/* @syndlex
esphome/components/anova/* @buxtronix esphome/components/anova/* @buxtronix
@ -162,6 +163,7 @@ esphome/components/midea/* @dudanov
esphome/components/midea_ir/* @dudanov esphome/components/midea_ir/* @dudanov
esphome/components/mitsubishi/* @RubyBailey esphome/components/mitsubishi/* @RubyBailey
esphome/components/mlx90393/* @functionpointer esphome/components/mlx90393/* @functionpointer
esphome/components/mlx90614/* @jesserockz
esphome/components/mmc5603/* @benhoff esphome/components/mmc5603/* @benhoff
esphome/components/modbus_controller/* @martgras esphome/components/modbus_controller/* @martgras
esphome/components/modbus_controller/binary_sensor/* @martgras esphome/components/modbus_controller/binary_sensor/* @martgras

View file

@ -24,6 +24,7 @@ RUN \
python3-setuptools=52.0.0-4 \ python3-setuptools=52.0.0-4 \
python3-pil=8.1.2+dfsg-0.3+deb11u1 \ python3-pil=8.1.2+dfsg-0.3+deb11u1 \
python3-cryptography=3.3.2-1 \ python3-cryptography=3.3.2-1 \
python3-venv=3.9.2-3 \
iputils-ping=3:20210202-1 \ iputils-ping=3:20210202-1 \
git=1:2.30.2-1 \ git=1:2.30.2-1 \
curl=7.74.0-1.3+deb11u7 \ curl=7.74.0-1.3+deb11u7 \
@ -59,7 +60,7 @@ RUN \
# First install requirements to leverage caching when requirements don't change # 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 \ RUN \
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
&& /platformio_install_deps.py /platformio.ini && /platformio_install_deps.py /platformio.ini

View file

@ -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])

View file

@ -0,0 +1 @@
CODEOWNERS = ["@buxtronix"]

View file

@ -5,7 +5,7 @@ from esphome.const import CONF_ID, CONF_PIN
CODEOWNERS = ["@buxtronix"] CODEOWNERS = ["@buxtronix"]
DEPENDENCIES = ["ble_client"] DEPENDENCIES = ["ble_client"]
AUTO_LOAD = ["am43", "sensor"] AUTO_LOAD = ["am43"]
CONF_INVERT_POSITION = "invert_position" 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]) var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_pin(config[CONF_PIN])) cg.add(var.set_pin(config[CONF_PIN]))
cg.add(var.set_invert_position(config[CONF_INVERT_POSITION])) cg.add(var.set_invert_position(config[CONF_INVERT_POSITION]))
yield cg.register_component(var, config) await cg.register_component(var, config)
yield cover.register_cover(var, config) await cover.register_cover(var, config)
yield ble_client.register_ble_node(var, config) await ble_client.register_ble_node(var, config)

View file

@ -40,6 +40,7 @@ void Am43Component::loop() {
CoverTraits Am43Component::get_traits() { CoverTraits Am43Component::get_traits() {
auto traits = CoverTraits(); auto traits = CoverTraits();
traits.set_supports_stop(true);
traits.set_supports_position(true); traits.set_supports_position(true);
traits.set_supports_tilt(false); traits.set_supports_tilt(false);
traits.set_is_assumed_state(false); traits.set_is_assumed_state(false);

View file

@ -11,6 +11,7 @@ from esphome.const import (
UNIT_PERCENT, UNIT_PERCENT,
) )
AUTO_LOAD = ["am43"]
CODEOWNERS = ["@buxtronix"] CODEOWNERS = ["@buxtronix"]
am43_ns = cg.esphome_ns.namespace("am43") 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]) var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config) await cg.register_component(var, config)
yield ble_client.register_ble_node(var, config) await ble_client.register_ble_node(var, config)
if CONF_BATTERY_LEVEL in 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)) cg.add(var.set_battery(sens))
if CONF_ILLUMINANCE in config: 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)) cg.add(var.set_illuminance(sens))

View file

@ -1,6 +1,6 @@
#include "am43.h" #include "am43_sensor.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32 #ifdef USE_ESP32

View file

@ -288,6 +288,7 @@ message ListEntitiesCoverResponse {
bool disabled_by_default = 9; bool disabled_by_default = 9;
string icon = 10; string icon = 10;
EntityCategory entity_category = 11; EntityCategory entity_category = 11;
bool supports_stop = 12;
} }
enum LegacyCoverState { enum LegacyCoverState {
@ -861,8 +862,7 @@ message ClimateStateResponse {
float target_temperature = 4; float target_temperature = 4;
float target_temperature_low = 5; float target_temperature_low = 5;
float target_temperature_high = 6; float target_temperature_high = 6;
// For older peers, equal to preset == CLIMATE_PRESET_AWAY bool unused_legacy_away = 7;
bool legacy_away = 7;
ClimateAction action = 8; ClimateAction action = 8;
ClimateFanMode fan_mode = 9; ClimateFanMode fan_mode = 9;
ClimateSwingMode swing_mode = 10; ClimateSwingMode swing_mode = 10;
@ -885,9 +885,8 @@ message ClimateCommandRequest {
float target_temperature_low = 7; float target_temperature_low = 7;
bool has_target_temperature_high = 8; bool has_target_temperature_high = 8;
float target_temperature_high = 9; float target_temperature_high = 9;
// legacy, for older peers, newer ones should use CLIMATE_PRESET_AWAY in preset bool unused_has_legacy_away = 10;
bool has_legacy_away = 10; bool unused_legacy_away = 11;
bool legacy_away = 11;
bool has_fan_mode = 12; bool has_fan_mode = 12;
ClimateFanMode fan_mode = 13; ClimateFanMode fan_mode = 13;
bool has_swing_mode = 14; bool has_swing_mode = 14;

View file

@ -530,7 +530,6 @@ bool APIConnection::send_climate_state(climate::Climate *climate) {
resp.custom_fan_mode = climate->custom_fan_mode.value(); resp.custom_fan_mode = climate->custom_fan_mode.value();
if (traits.get_supports_presets() && climate->preset.has_value()) { if (traits.get_supports_presets() && climate->preset.has_value()) {
resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value()); resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value());
resp.legacy_away = resp.preset == enums::CLIMATE_PRESET_AWAY;
} }
if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value()) if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value())
resp.custom_preset = climate->custom_preset.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); call.set_target_temperature_low(msg.target_temperature_low);
if (msg.has_target_temperature_high) if (msg.has_target_temperature_high)
call.set_target_temperature_high(msg.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) if (msg.has_fan_mode)
call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode)); call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode));
if (msg.has_custom_fan_mode) if (msg.has_custom_fan_mode)
@ -944,7 +941,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
HelloResponse resp; HelloResponse resp;
resp.api_version_major = 1; 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.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
resp.name = App.get_name(); resp.name = App.get_name();

View file

@ -941,6 +941,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val
this->entity_category = value.as_enum<enums::EntityCategory>(); this->entity_category = value.as_enum<enums::EntityCategory>();
return true; return true;
} }
case 12: {
this->supports_stop = value.as_bool();
return true;
}
default: default:
return false; return false;
} }
@ -993,6 +997,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(9, this->disabled_by_default); buffer.encode_bool(9, this->disabled_by_default);
buffer.encode_string(10, this->icon); buffer.encode_string(10, this->icon);
buffer.encode_enum<enums::EntityCategory>(11, this->entity_category); buffer.encode_enum<enums::EntityCategory>(11, this->entity_category);
buffer.encode_bool(12, this->supports_stop);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesCoverResponse::dump_to(std::string &out) const { 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(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n"); out.append("\n");
out.append(" supports_stop: ");
out.append(YESNO(this->supports_stop));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -3649,7 +3658,7 @@ bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
return true; return true;
} }
case 7: { case 7: {
this->legacy_away = value.as_bool(); this->unused_legacy_away = value.as_bool();
return true; return true;
} }
case 8: { case 8: {
@ -3719,7 +3728,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(4, this->target_temperature); buffer.encode_float(4, this->target_temperature);
buffer.encode_float(5, this->target_temperature_low); buffer.encode_float(5, this->target_temperature_low);
buffer.encode_float(6, this->target_temperature_high); 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<enums::ClimateAction>(8, this->action); buffer.encode_enum<enums::ClimateAction>(8, this->action);
buffer.encode_enum<enums::ClimateFanMode>(9, this->fan_mode); buffer.encode_enum<enums::ClimateFanMode>(9, this->fan_mode);
buffer.encode_enum<enums::ClimateSwingMode>(10, this->swing_mode); buffer.encode_enum<enums::ClimateSwingMode>(10, this->swing_mode);
@ -3760,8 +3769,8 @@ void ClimateStateResponse::dump_to(std::string &out) const {
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append(" legacy_away: "); out.append(" unused_legacy_away: ");
out.append(YESNO(this->legacy_away)); out.append(YESNO(this->unused_legacy_away));
out.append("\n"); out.append("\n");
out.append(" action: "); out.append(" action: ");
@ -3813,11 +3822,11 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
return true; return true;
} }
case 10: { case 10: {
this->has_legacy_away = value.as_bool(); this->unused_has_legacy_away = value.as_bool();
return true; return true;
} }
case 11: { case 11: {
this->legacy_away = value.as_bool(); this->unused_legacy_away = value.as_bool();
return true; return true;
} }
case 12: { case 12: {
@ -3902,8 +3911,8 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(7, this->target_temperature_low); buffer.encode_float(7, this->target_temperature_low);
buffer.encode_bool(8, this->has_target_temperature_high); buffer.encode_bool(8, this->has_target_temperature_high);
buffer.encode_float(9, this->target_temperature_high); buffer.encode_float(9, this->target_temperature_high);
buffer.encode_bool(10, this->has_legacy_away); buffer.encode_bool(10, this->unused_has_legacy_away);
buffer.encode_bool(11, this->legacy_away); buffer.encode_bool(11, this->unused_legacy_away);
buffer.encode_bool(12, this->has_fan_mode); buffer.encode_bool(12, this->has_fan_mode);
buffer.encode_enum<enums::ClimateFanMode>(13, this->fan_mode); buffer.encode_enum<enums::ClimateFanMode>(13, this->fan_mode);
buffer.encode_bool(14, this->has_swing_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(buffer);
out.append("\n"); out.append("\n");
out.append(" has_legacy_away: "); out.append(" unused_has_legacy_away: ");
out.append(YESNO(this->has_legacy_away)); out.append(YESNO(this->unused_has_legacy_away));
out.append("\n"); out.append("\n");
out.append(" legacy_away: "); out.append(" unused_legacy_away: ");
out.append(YESNO(this->legacy_away)); out.append(YESNO(this->unused_legacy_away));
out.append("\n"); out.append("\n");
out.append(" has_fan_mode: "); out.append(" has_fan_mode: ");

View file

@ -375,6 +375,7 @@ class ListEntitiesCoverResponse : public ProtoMessage {
bool disabled_by_default{false}; bool disabled_by_default{false};
std::string icon{}; std::string icon{};
enums::EntityCategory entity_category{}; enums::EntityCategory entity_category{};
bool supports_stop{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -958,7 +959,7 @@ class ClimateStateResponse : public ProtoMessage {
float target_temperature{0.0f}; float target_temperature{0.0f};
float target_temperature_low{0.0f}; float target_temperature_low{0.0f};
float target_temperature_high{0.0f}; float target_temperature_high{0.0f};
bool legacy_away{false}; bool unused_legacy_away{false};
enums::ClimateAction action{}; enums::ClimateAction action{};
enums::ClimateFanMode fan_mode{}; enums::ClimateFanMode fan_mode{};
enums::ClimateSwingMode swing_mode{}; enums::ClimateSwingMode swing_mode{};
@ -986,8 +987,8 @@ class ClimateCommandRequest : public ProtoMessage {
float target_temperature_low{0.0f}; float target_temperature_low{0.0f};
bool has_target_temperature_high{false}; bool has_target_temperature_high{false};
float target_temperature_high{0.0f}; float target_temperature_high{0.0f};
bool has_legacy_away{false}; bool unused_has_legacy_away{false};
bool legacy_away{false}; bool unused_legacy_away{false};
bool has_fan_mode{false}; bool has_fan_mode{false};
enums::ClimateFanMode fan_mode{}; enums::ClimateFanMode fan_mode{};
bool has_swing_mode{false}; bool has_swing_mode{false};

View file

@ -428,14 +428,17 @@ void APIServer::on_shutdown() {
} }
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
void APIServer::start_voice_assistant() { bool APIServer::start_voice_assistant() {
for (auto &c : this->clients_) { 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() { void APIServer::stop_voice_assistant() {
for (auto &c : this->clients_) { for (auto &c : this->clients_) {
c->request_voice_assistant(false); if (c->request_voice_assistant(false))
return;
} }
} }
#endif #endif

View file

@ -96,7 +96,7 @@ class APIServer : public Component, public Controller {
#endif #endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
void start_voice_assistant(); bool start_voice_assistant();
void stop_voice_assistant(); void stop_voice_assistant();
#endif #endif

View file

@ -43,12 +43,7 @@ void BinarySensor::send_state_internal(bool state, bool is_initial) {
} }
BinarySensor::BinarySensor() : state(false) {} 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) { void BinarySensor::add_filter(Filter *filter) {
filter->parent_ = this; filter->parent_ = this;
if (this->filter_list_ == nullptr) { if (this->filter_list_ == nullptr) {

View file

@ -34,7 +34,7 @@ namespace binary_sensor {
* The sub classes should notify the front-end of new states via the publish_state() method which * The sub classes should notify the front-end of new states via the publish_state() method which
* handles inverted inputs for you. * handles inverted inputs for you.
*/ */
class BinarySensor : public EntityBase { class BinarySensor : public EntityBase, public EntityBase_DeviceClass {
public: public:
explicit BinarySensor(); explicit BinarySensor();
@ -60,12 +60,6 @@ class BinarySensor : public EntityBase {
/// The current reported state of the binary sensor. /// The current reported state of the binary sensor.
bool state; 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_filter(Filter *filter);
void add_filters(const std::vector<Filter *> &filters); void add_filters(const std::vector<Filter *> &filters);
@ -82,7 +76,6 @@ class BinarySensor : public EntityBase {
protected: protected:
CallbackManager<void(bool)> state_callback_{}; CallbackManager<void(bool)> state_callback_{};
optional<std::string> device_class_{}; ///< Stores the override of the device class
Filter *filter_list_{nullptr}; Filter *filter_list_{nullptr};
bool has_state_{false}; bool has_state_{false};
bool publish_initial_state_{false}; bool publish_initial_state_{false};

View file

@ -13,8 +13,5 @@ void Button::press() {
} }
void Button::add_on_press_callback(std::function<void()> &&callback) { this->press_callback_.add(std::move(callback)); } void Button::add_on_press_callback(std::function<void()> &&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 button
} // namespace esphome } // namespace esphome

View file

@ -26,7 +26,7 @@ namespace button {
* *
* A button is just a momentary switch that does not have a state, only a trigger. * 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: public:
/** Press this button. This is called by the front-end. /** 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<void()> &&callback); void add_on_press_callback(std::function<void()> &&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: protected:
/** You should implement this virtual method if you want to create your own button. /** You should implement this virtual method if you want to create your own button.
*/ */
virtual void press_action() = 0; virtual void press_action() = 0;
CallbackManager<void()> press_callback_{}; CallbackManager<void()> press_callback_{};
std::string device_class_{};
}; };
} // namespace button } // namespace button

View file

@ -343,7 +343,7 @@ CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema(
cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature), cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature),
cv.Optional(CONF_TARGET_TEMPERATURE_LOW): 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_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( cv.Exclusive(CONF_FAN_MODE, "fan_mode"): cv.templatable(
validate_climate_fan_mode 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 config[CONF_TARGET_TEMPERATURE_HIGH], args, float
) )
cg.add(var.set_target_temperature_high(template_)) 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: if CONF_FAN_MODE in config:
template_ = await cg.templatable(config[CONF_FAN_MODE], args, ClimateFanMode) template_ = await cg.templatable(config[CONF_FAN_MODE], args, ClimateFanMode)
cg.add(var.set_fan_mode(template_)) cg.add(var.set_fan_mode(template_))

View file

@ -264,25 +264,11 @@ const optional<ClimateMode> &ClimateCall::get_mode() const { return this->mode_;
const optional<float> &ClimateCall::get_target_temperature() const { return this->target_temperature_; } const optional<float> &ClimateCall::get_target_temperature() const { return this->target_temperature_; }
const optional<float> &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; } const optional<float> &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; }
const optional<float> &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; } const optional<float> &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; }
optional<bool> ClimateCall::get_away() const {
if (!this->preset_.has_value())
return {};
return *this->preset_ == ClimatePreset::CLIMATE_PRESET_AWAY;
}
const optional<ClimateFanMode> &ClimateCall::get_fan_mode() const { return this->fan_mode_; } const optional<ClimateFanMode> &ClimateCall::get_fan_mode() const { return this->fan_mode_; }
const optional<std::string> &ClimateCall::get_custom_fan_mode() const { return this->custom_fan_mode_; } const optional<std::string> &ClimateCall::get_custom_fan_mode() const { return this->custom_fan_mode_; }
const optional<ClimatePreset> &ClimateCall::get_preset() const { return this->preset_; } const optional<ClimatePreset> &ClimateCall::get_preset() const { return this->preset_; }
const optional<std::string> &ClimateCall::get_custom_preset() const { return this->custom_preset_; } const optional<std::string> &ClimateCall::get_custom_preset() const { return this->custom_preset_; }
const optional<ClimateSwingMode> &ClimateCall::get_swing_mode() const { return this->swing_mode_; } const optional<ClimateSwingMode> &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<bool> away) {
if (away.has_value())
this->preset_ = *away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME;
return *this;
}
ClimateCall &ClimateCall::set_target_temperature_high(optional<float> target_temperature_high) { ClimateCall &ClimateCall::set_target_temperature_high(optional<float> target_temperature_high) {
this->target_temperature_high_ = target_temperature_high; this->target_temperature_high_ = target_temperature_high;
return *this; return *this;

View file

@ -64,10 +64,6 @@ class ClimateCall {
* For climate devices with two point target temperature control * For climate devices with two point target temperature control
*/ */
ClimateCall &set_target_temperature_high(optional<float> target_temperature_high); ClimateCall &set_target_temperature_high(optional<float> 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<bool> away);
/// Set the fan mode of the climate device. /// Set the fan mode of the climate device.
ClimateCall &set_fan_mode(ClimateFanMode fan_mode); ClimateCall &set_fan_mode(ClimateFanMode fan_mode);
/// Set the fan mode of the climate device. /// Set the fan mode of the climate device.
@ -97,8 +93,6 @@ class ClimateCall {
const optional<float> &get_target_temperature() const; const optional<float> &get_target_temperature() const;
const optional<float> &get_target_temperature_low() const; const optional<float> &get_target_temperature_low() const;
const optional<float> &get_target_temperature_high() const; const optional<float> &get_target_temperature_high() const;
ESPDEPRECATED("get_away() is deprecated, please use .get_preset() instead", "v1.20")
optional<bool> get_away() const;
const optional<ClimateFanMode> &get_fan_mode() const; const optional<ClimateFanMode> &get_fan_mode() const;
const optional<ClimateSwingMode> &get_swing_mode() const; const optional<ClimateSwingMode> &get_swing_mode() const;
const optional<std::string> &get_custom_fan_mode() const; const optional<std::string> &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. /// The active fan mode of the climate device.
optional<ClimateFanMode> fan_mode; optional<ClimateFanMode> fan_mode;

View file

@ -117,15 +117,6 @@ class ClimateTraits {
bool supports_custom_preset(const std::string &custom_preset) const { bool supports_custom_preset(const std::string &custom_preset) const {
return supported_custom_presets_.count(custom_preset); 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<ClimateSwingMode> modes) { supported_swing_modes_ = std::move(modes); } void set_supported_swing_modes(std::set<ClimateSwingMode> modes) { supported_swing_modes_ = std::move(modes); }
void add_supported_swing_mode(ClimateSwingMode mode) { supported_swing_modes_.insert(mode); } void add_supported_swing_mode(ClimateSwingMode mode) { supported_swing_modes_.insert(mode); }

View file

@ -28,6 +28,7 @@ cover::CoverTraits CopyCover::get_traits() {
// copy traits manually so it doesn't break when new options are added // copy traits manually so it doesn't break when new options are added
// but the control() method hasn't implemented them yet. // but the control() method hasn't implemented them yet.
traits.set_is_assumed_state(base.get_is_assumed_state()); 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_position(base.get_supports_position());
traits.set_supports_tilt(base.get_supports_tilt()); traits.set_supports_tilt(base.get_supports_tilt());
traits.set_supports_toggle(base.get_supports_toggle()); traits.set_supports_toggle(base.get_supports_toggle());

View file

@ -145,7 +145,7 @@ CoverCall &CoverCall::set_stop(bool stop) {
return *this; return *this;
} }
bool CoverCall::get_stop() const { return this->stop_; } 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}; } CoverCall Cover::make_call() { return {this}; }
void Cover::open() { void Cover::open() {
auto call = this->make_call(); auto call = this->make_call();
@ -204,11 +204,7 @@ optional<CoverRestoreState> Cover::restore_state_() {
return {}; return {};
return recovered; 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_open() const { return this->position == COVER_OPEN; }
bool Cover::is_fully_closed() const { return this->position == COVER_CLOSED; } bool Cover::is_fully_closed() const { return this->position == COVER_CLOSED; }

View file

@ -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 * to control all values of the cover. Also implement get_traits() to return what operations
* the cover supports. * the cover supports.
*/ */
class Cover : public EntityBase { class Cover : public EntityBase, public EntityBase_DeviceClass {
public: public:
explicit Cover(); explicit Cover();
@ -156,8 +156,6 @@ class Cover : public EntityBase {
void publish_state(bool save = true); void publish_state(bool save = true);
virtual CoverTraits get_traits() = 0; 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 /// Helper method to check if the cover is fully open. Equivalent to comparing .position against 1.0
bool is_fully_open() const; bool is_fully_open() const;
@ -172,7 +170,6 @@ class Cover : public EntityBase {
optional<CoverRestoreState> restore_state_(); optional<CoverRestoreState> restore_state_();
CallbackManager<void()> state_callback_{}; CallbackManager<void()> state_callback_{};
optional<std::string> device_class_override_{};
ESPPreferenceObject rtc_; ESPPreferenceObject rtc_;
}; };

View file

@ -15,12 +15,15 @@ class CoverTraits {
void set_supports_tilt(bool supports_tilt) { this->supports_tilt_ = supports_tilt; } void set_supports_tilt(bool supports_tilt) { this->supports_tilt_ = supports_tilt; }
bool get_supports_toggle() const { return this->supports_toggle_; } bool get_supports_toggle() const { return this->supports_toggle_; }
void set_supports_toggle(bool supports_toggle) { this->supports_toggle_ = 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: protected:
bool is_assumed_state_{false}; bool is_assumed_state_{false};
bool supports_position_{false}; bool supports_position_{false};
bool supports_tilt_{false}; bool supports_tilt_{false};
bool supports_toggle_{false}; bool supports_toggle_{false};
bool supports_stop_{false};
}; };
} // namespace cover } // namespace cover

View file

@ -12,6 +12,7 @@ using namespace esphome::cover;
CoverTraits CurrentBasedCover::get_traits() { CoverTraits CurrentBasedCover::get_traits() {
auto traits = CoverTraits(); auto traits = CoverTraits();
traits.set_supports_stop(true);
traits.set_supports_position(true); traits.set_supports_position(true);
traits.set_supports_toggle(true); traits.set_supports_toggle(true);
traits.set_is_assumed_state(false); traits.set_is_assumed_state(false);

View file

@ -17,7 +17,8 @@ debug_ns = cg.esphome_ns.namespace("debug")
DebugComponent = debug_ns.class_("DebugComponent", cg.PollingComponent) DebugComponent = debug_ns.class_("DebugComponent", cg.PollingComponent)
CONFIG_SCHEMA = cv.Schema( CONFIG_SCHEMA = cv.All(
cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(DebugComponent), cv.GenerateID(): cv.declare_id(DebugComponent),
cv.Optional(CONF_DEVICE): cv.invalid( cv.Optional(CONF_DEVICE): cv.invalid(
@ -36,7 +37,9 @@ CONFIG_SCHEMA = cv.Schema(
"The 'loop_time' option has been moved to the 'debug' sensor component" "The 'loop_time' option has been moved to the 'debug' sensor component"
), ),
} }
).extend(cv.polling_component_schema("60s")) ).extend(cv.polling_component_schema("60s")),
cv.only_on(["esp32", "esp8266"]),
)
async def to_code(config): async def to_code(config):

View file

@ -72,6 +72,7 @@ class DemoCover : public cover::Cover, public Component {
traits.set_supports_tilt(true); traits.set_supports_tilt(true);
break; break;
case DemoCoverType::TYPE_4: case DemoCoverType::TYPE_4:
traits.set_supports_stop(true);
traits.set_is_assumed_state(true); traits.set_is_assumed_state(true);
traits.set_supports_tilt(true); traits.set_supports_tilt(true);
break; break;

View file

@ -11,6 +11,7 @@ using namespace esphome::cover;
CoverTraits EndstopCover::get_traits() { CoverTraits EndstopCover::get_traits() {
auto traits = CoverTraits(); auto traits = CoverTraits();
traits.set_supports_stop(true);
traits.set_supports_position(true); traits.set_supports_position(true);
traits.set_supports_toggle(true); traits.set_supports_toggle(true);
traits.set_is_assumed_state(false); traits.set_is_assumed_state(false);

View file

@ -163,7 +163,7 @@ RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 0, 5)
# The platformio/espressif32 version to use for arduino frameworks # The platformio/espressif32 version to use for arduino frameworks
# - https://github.com/platformio/platform-espressif32/releases # - https://github.com/platformio/platform-espressif32/releases
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 # - 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 # The default/recommended esp-idf framework version
# - https://github.com/espressif/esp-idf/releases # - https://github.com/espressif/esp-idf/releases

View file

@ -7,6 +7,7 @@
#include <freertos/task.h> #include <freertos/task.h>
#include <esp_idf_version.h> #include <esp_idf_version.h>
#include <esp_task_wdt.h> #include <esp_task_wdt.h>
#include <esp_timer.h>
#include <soc/rtc.h> #include <soc/rtc.h>
#if ESP_IDF_VERSION_MAJOR >= 4 #if ESP_IDF_VERSION_MAJOR >= 4

View file

@ -10,7 +10,7 @@ CONFLICTS_WITH = ["esp32_ble_beacon"]
CONF_BLE_ID = "ble_id" 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") esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble")
ESP32BLE = esp32_ble_ns.class_("ESP32BLE", cg.Component) ESP32BLE = esp32_ble_ns.class_("ESP32BLE", cg.Component)
@ -29,7 +29,7 @@ CONFIG_SCHEMA = cv.Schema(
def validate_variant(_): def validate_variant(_):
variant = get_esp32_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") raise cv.Invalid(f"{variant} does not support Bluetooth")

View file

@ -26,8 +26,10 @@ EthernetComponent::EthernetComponent() { global_eth_component = this; }
void EthernetComponent::setup() { void EthernetComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up Ethernet..."); ESP_LOGCONFIG(TAG, "Setting up Ethernet...");
// Delay here to allow power to stabilise before Ethernet is initialised. if (esp_reset_reason() != ESP_RST_DEEPSLEEP) {
// Delay here to allow power to stabilise before Ethernet is initialized.
delay(300); // NOLINT delay(300); // NOLINT
}
esp_err_t err; esp_err_t err;
err = esp_netif_init(); 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_mac_t *mac = esp_eth_mac_new_esp32(&mac_config);
esp_eth_phy_t *phy;
switch (this->type_) { switch (this->type_) {
case ETHERNET_TYPE_LAN8720: { case ETHERNET_TYPE_LAN8720: {
phy = esp_eth_phy_new_lan87xx(&phy_config); this->phy_ = esp_eth_phy_new_lan87xx(&phy_config);
break; break;
} }
case ETHERNET_TYPE_RTL8201: { case ETHERNET_TYPE_RTL8201: {
phy = esp_eth_phy_new_rtl8201(&phy_config); this->phy_ = esp_eth_phy_new_rtl8201(&phy_config);
break; break;
} }
case ETHERNET_TYPE_DP83848: { case ETHERNET_TYPE_DP83848: {
phy = esp_eth_phy_new_dp83848(&phy_config); this->phy_ = esp_eth_phy_new_dp83848(&phy_config);
break; break;
} }
case ETHERNET_TYPE_IP101: { case ETHERNET_TYPE_IP101: {
phy = esp_eth_phy_new_ip101(&phy_config); this->phy_ = esp_eth_phy_new_ip101(&phy_config);
break; break;
} }
case ETHERNET_TYPE_JL1101: { case ETHERNET_TYPE_JL1101: {
phy = esp_eth_phy_new_jl1101(&phy_config); this->phy_ = esp_eth_phy_new_jl1101(&phy_config);
break; break;
} }
case ETHERNET_TYPE_KSZ8081: { case ETHERNET_TYPE_KSZ8081: {
phy = esp_eth_phy_new_ksz8081(&phy_config); this->phy_ = esp_eth_phy_new_ksz8081(&phy_config);
break; break;
} }
default: { 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; this->eth_handle_ = nullptr;
err = esp_eth_driver_install(&eth_config, &this->eth_handle_); err = esp_eth_driver_install(&eth_config, &this->eth_handle_);
ESPHL_ERROR_CHECK(err, "ETH driver install error"); 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; } 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 ethernet
} // namespace esphome } // namespace esphome

View file

@ -45,6 +45,7 @@ class EthernetComponent : public Component {
void dump_config() override; void dump_config() override;
float get_setup_priority() const override; float get_setup_priority() const override;
bool can_proceed() override; bool can_proceed() override;
void on_shutdown() override { powerdown(); }
bool is_connected(); bool is_connected();
void set_phy_addr(uint8_t phy_addr); void set_phy_addr(uint8_t phy_addr);
@ -58,6 +59,7 @@ class EthernetComponent : public Component {
network::IPAddress get_ip_address(); network::IPAddress get_ip_address();
std::string get_use_address() const; std::string get_use_address() const;
void set_use_address(const std::string &use_address); void set_use_address(const std::string &use_address);
bool powerdown();
protected: protected:
static void eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); 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_; uint32_t connect_begin_;
esp_netif_t *eth_netif_{nullptr}; esp_netif_t *eth_netif_{nullptr};
esp_eth_handle_t eth_handle_; esp_eth_handle_t eth_handle_;
esp_eth_phy_t *phy_{nullptr};
}; };
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)

View file

@ -41,6 +41,7 @@ void FeedbackCover::setup() {
CoverTraits FeedbackCover::get_traits() { CoverTraits FeedbackCover::get_traits() {
auto traits = CoverTraits(); auto traits = CoverTraits();
traits.set_supports_stop(true);
traits.set_supports_position(true); traits.set_supports_position(true);
traits.set_supports_toggle(true); traits.set_supports_toggle(true);
traits.set_is_assumed_state(this->assumed_state_); traits.set_is_assumed_state(this->assumed_state_);

View file

@ -77,10 +77,12 @@ void FingerprintGrowComponent::finish_enrollment(uint8_t result) {
this->enrollment_done_callback_.call(this->enrollment_slot_); this->enrollment_done_callback_.call(this->enrollment_slot_);
this->get_fingerprint_count_(); this->get_fingerprint_count_();
} else { } else {
if (this->enrollment_slot_ != ENROLLMENT_SLOT_UNUSED) {
this->enrollment_failed_callback_.call(this->enrollment_slot_); this->enrollment_failed_callback_.call(this->enrollment_slot_);
} }
}
this->enrollment_image_ = 0; this->enrollment_image_ = 0;
this->enrollment_slot_ = 0; this->enrollment_slot_ = ENROLLMENT_SLOT_UNUSED;
if (this->enrolling_binary_sensor_ != nullptr) { if (this->enrolling_binary_sensor_ != nullptr) {
this->enrolling_binary_sensor_->publish_state(false); this->enrolling_binary_sensor_->publish_state(false);
} }

View file

@ -13,6 +13,8 @@ namespace fingerprint_grow {
static const uint16_t START_CODE = 0xEF01; static const uint16_t START_CODE = 0xEF01;
static const uint16_t ENROLLMENT_SLOT_UNUSED = 0xFFFF;
enum GrowPacketType { enum GrowPacketType {
COMMAND = 0x01, COMMAND = 0x01,
DATA = 0x02, DATA = 0x02,
@ -158,7 +160,7 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic
uint32_t new_password_ = -1; uint32_t new_password_ = -1;
GPIOPin *sensing_pin_{nullptr}; GPIOPin *sensing_pin_{nullptr};
uint8_t enrollment_image_ = 0; uint8_t enrollment_image_ = 0;
uint16_t enrollment_slot_ = 0; uint16_t enrollment_slot_ = ENROLLMENT_SLOT_UNUSED;
uint8_t enrollment_buffers_ = 5; uint8_t enrollment_buffers_ = 5;
bool waiting_removal_ = false; bool waiting_removal_ = false;
uint32_t last_aura_led_control_ = 0; uint32_t last_aura_led_control_ = 0;

View file

@ -3,6 +3,7 @@
#include "i2c_bus_arduino.h" #include "i2c_bus_arduino.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/application.h"
#include <Arduino.h> #include <Arduino.h>
#include <cstring> #include <cstring>
@ -227,10 +228,14 @@ void ArduinoI2CBus::recover_() {
// When SCL is kept LOW at this point, we might be looking at a device // 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, // that applies clock stretching. Wait for the release of the SCL line,
// but not forever. There is no specification for the maximum allowed // but not forever. There is no specification for the maximum allowed
// time. We'll stick to 500ms here. // time. We yield and reset the WDT, so as to avoid triggering reset.
auto wait = 20; // 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 while (wait-- && digitalRead(scl_pin_) == LOW) { // NOLINT
delay(25); App.feed_wdt();
delayMicroseconds(half_period_usec * 2);
} }
if (digitalRead(scl_pin_) == LOW) { // NOLINT if (digitalRead(scl_pin_) == LOW) { // NOLINT
ESP_LOGE(TAG, "Recovery failed: SCL is held LOW during clock pulse cycle"); ESP_LOGE(TAG, "Recovery failed: SCL is held LOW during clock pulse cycle");

View file

@ -4,6 +4,7 @@
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/application.h"
#include <cstring> #include <cstring>
#include <cinttypes> #include <cinttypes>
@ -273,10 +274,14 @@ void IDFI2CBus::recover_() {
// When SCL is kept LOW at this point, we might be looking at a device // 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, // that applies clock stretching. Wait for the release of the SCL line,
// but not forever. There is no specification for the maximum allowed // but not forever. There is no specification for the maximum allowed
// time. We'll stick to 500ms here. // time. We yield and reset the WDT, so as to avoid triggering reset.
auto wait = 20; // 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) { 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) { if (gpio_get_level(scl_pin) == 0) {
ESP_LOGE(TAG, "Recovery failed: SCL is held LOW during clock pulse cycle"); ESP_LOGE(TAG, "Recovery failed: SCL is held LOW during clock pulse cycle");

View file

@ -93,7 +93,7 @@ async def to_code(config):
cg.add(var.set_scroll(config[CONF_SCROLL_ENABLE])) cg.add(var.set_scroll(config[CONF_SCROLL_ENABLE]))
cg.add(var.set_scroll_mode(config[CONF_SCROLL_MODE])) cg.add(var.set_scroll_mode(config[CONF_SCROLL_MODE]))
cg.add(var.set_reverse(config[CONF_REVERSE_ENABLE])) 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: if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(

View file

@ -4,10 +4,13 @@ from esphome.const import (
CONF_PROTOCOL, CONF_PROTOCOL,
CONF_SERVICES, CONF_SERVICES,
CONF_SERVICE, CONF_SERVICE,
KEY_CORE,
KEY_FRAMEWORK_VERSION,
) )
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.components.esp32 import add_idf_component
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
DEPENDENCIES = ["network"] DEPENDENCIES = ["network"]
@ -79,6 +82,16 @@ async def to_code(config):
elif CORE.is_rp2040: elif CORE.is_rp2040:
cg.add_library("LEAmDNS", None) 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]: if config[CONF_DISABLED]:
return return

View file

View file

@ -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

View file

@ -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

View file

@ -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]))

View file

@ -75,13 +75,8 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo
JsonArray presets = root.createNestedArray("preset_modes"); JsonArray presets = root.createNestedArray("preset_modes");
if (traits.supports_preset(CLIMATE_PRESET_HOME)) if (traits.supports_preset(CLIMATE_PRESET_HOME))
presets.add("home"); presets.add("home");
if (traits.supports_preset(CLIMATE_PRESET_AWAY)) { 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();
presets.add("away"); presets.add("away");
}
if (traits.supports_preset(CLIMATE_PRESET_BOOST)) if (traits.supports_preset(CLIMATE_PRESET_BOOST))
presets.add("boost"); presets.add("boost");
if (traits.supports_preset(CLIMATE_PRESET_COMFORT)) 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()) { 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) { this->subscribe(this->get_preset_command_topic(), [this](const std::string &topic, const std::string &payload) {
auto call = this->device_->make_call(); auto call = this->device_->make_call();
@ -301,11 +273,6 @@ bool MQTTClimateComponent::publish_state_() {
success = false; 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()) { if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) {
std::string payload; std::string payload;
if (this->device_->preset.has_value()) { if (this->device_->preset.has_value()) {

View file

@ -16,13 +16,5 @@ std::string NumberTraits::get_unit_of_measurement() {
return ""; 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 number
} // namespace esphome } // namespace esphome

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "esphome/core/entity_base.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
namespace esphome { namespace esphome {
@ -11,7 +12,7 @@ enum NumberMode : uint8_t {
NUMBER_MODE_SLIDER = 2, NUMBER_MODE_SLIDER = 2,
}; };
class NumberTraits { class NumberTraits : public EntityBase_DeviceClass {
public: public:
// Set/get the number value boundaries. // Set/get the number value boundaries.
void set_min_value(float min_value) { min_value_ = min_value; } 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; } void set_mode(NumberMode mode) { this->mode_ = mode; }
NumberMode get_mode() const { return this->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: protected:
float min_value_ = NAN; float min_value_ = NAN;
float max_value_ = NAN; float max_value_ = NAN;
float step_ = NAN; float step_ = NAN;
optional<std::string> unit_of_measurement_; ///< Unit of measurement override optional<std::string> unit_of_measurement_; ///< Unit of measurement override
NumberMode mode_{NUMBER_MODE_AUTO}; NumberMode mode_{NUMBER_MODE_AUTO};
optional<std::string> device_class_;
}; };
} // namespace number } // namespace number

View file

@ -8,6 +8,10 @@
#include <esp_ota_ops.h> #include <esp_ota_ops.h>
#include "esphome/components/md5/md5.h" #include "esphome/components/md5/md5.h"
#if ESP_IDF_VERSION_MAJOR >= 5
#include <spi_flash_mmap.h>
#endif
namespace esphome { namespace esphome {
namespace ota { namespace ota {
@ -16,9 +20,28 @@ OTAResponseTypes IDFOTABackend::begin(size_t image_size) {
if (this->partition_ == nullptr) { if (this->partition_ == nullptr) {
return OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION; 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_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) { if (err != ESP_OK) {
esp_ota_abort(this->update_handle_); esp_ota_abort(this->update_handle_);
this->update_handle_ = 0; this->update_handle_ = 0;

View file

@ -2,7 +2,12 @@ import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import automation, pins from esphome import automation, pins
from esphome.components import i2c 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"] CODEOWNERS = ["@glmnet"]
AUTO_LOAD = ["binary_sensor"] AUTO_LOAD = ["binary_sensor"]
@ -24,6 +29,11 @@ RC522_SCHEMA = cv.Schema(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RC522Trigger), 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")) ).extend(cv.polling_component_schema("1s"))
@ -37,5 +47,10 @@ async def setup_rc522(var, config):
for conf in config.get(CONF_ON_TAG, []): for conf in config.get(CONF_ON_TAG, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) 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) await automation.build_automation(trigger, [(cg.std_string, "x")], conf)

View file

@ -256,7 +256,7 @@ void RC522::loop() {
this->current_uid_ = rfid_uid; this->current_uid_ = rfid_uid;
for (auto *trigger : this->triggers_) for (auto *trigger : this->triggers_ontag_)
trigger->process(rfid_uid); trigger->process(rfid_uid);
if (report) { if (report) {
@ -265,6 +265,11 @@ void RC522::loop() {
break; break;
} }
case STATE_DONE: { 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_ = {}; this->current_uid_ = {};
state_ = STATE_INIT; state_ = STATE_INIT;
break; break;

View file

@ -24,7 +24,8 @@ class RC522 : public PollingComponent {
void loop() override; void loop() override;
void register_tag(RC522BinarySensor *tag) { this->binary_sensors_.push_back(tag); } 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; } void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; }
@ -242,7 +243,8 @@ class RC522 : public PollingComponent {
uint8_t reset_count_{0}; uint8_t reset_count_{0};
uint32_t reset_timeout_{0}; uint32_t reset_timeout_{0};
std::vector<RC522BinarySensor *> binary_sensors_; std::vector<RC522BinarySensor *> binary_sensors_;
std::vector<RC522Trigger *> triggers_; std::vector<RC522Trigger *> triggers_ontag_;
std::vector<RC522Trigger *> triggers_ontagremoved_;
std::vector<uint8_t> current_uid_; std::vector<uint8_t> current_uid_;
enum RC522Error { enum RC522Error {

View file

@ -25,10 +25,12 @@ from esphome.const import (
CONF_STATE_CLASS, CONF_STATE_CLASS,
CONF_TO, CONF_TO,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_TYPE,
CONF_UNIT_OF_MEASUREMENT, CONF_UNIT_OF_MEASUREMENT,
CONF_WINDOW_SIZE, CONF_WINDOW_SIZE,
CONF_MQTT_ID, CONF_MQTT_ID,
CONF_FORCE_UPDATE, CONF_FORCE_UPDATE,
CONF_VALUE,
DEVICE_CLASS_APPARENT_POWER, DEVICE_CLASS_APPARENT_POWER,
DEVICE_CLASS_AQI, DEVICE_CLASS_AQI,
DEVICE_CLASS_ATMOSPHERIC_PRESSURE, DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
@ -476,21 +478,38 @@ async def lambda_filter_to_code(config, filter_id):
return cg.new_Pvariable(filter_id, lambda_) 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): def validate_delta(config):
try: try:
return (cv.positive_float(config), False) value = cv.positive_float(config)
return DELTA_SCHEMA({CONF_VALUE: value, CONF_TYPE: "absolute"})
except cv.Invalid: except cv.Invalid:
pass pass
try: try:
return (cv.percentage(config), True) value = cv.percentage(config)
return DELTA_SCHEMA({CONF_VALUE: value, CONF_TYPE: "percentage"})
except cv.Invalid: except cv.Invalid:
pass pass
raise cv.Invalid("Delta filter requires a positive number or percentage value.") 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): 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) @FILTER_REGISTRY.register("or", OrFilter, validate_filters)

View file

@ -38,13 +38,6 @@ int8_t Sensor::get_accuracy_decimals() {
} }
void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) { this->accuracy_decimals_ = 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; } void Sensor::set_state_class(StateClass state_class) { this->state_class_ = state_class; }
StateClass Sensor::get_state_class() { StateClass Sensor::get_state_class() {
if (this->state_class_.has_value()) if (this->state_class_.has_value())

View file

@ -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. * 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: public:
explicit Sensor(); explicit Sensor();
@ -68,11 +68,6 @@ class Sensor : public EntityBase {
/// Manually set the accuracy in decimals. /// Manually set the accuracy in decimals.
void set_accuracy_decimals(int8_t accuracy_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. /// Get the state class, using the manual override if set.
StateClass get_state_class(); StateClass get_state_class();
/// Manually set the state class. /// Manually set the state class.
@ -165,7 +160,6 @@ class Sensor : public EntityBase {
optional<std::string> unit_of_measurement_; ///< Unit of measurement override optional<std::string> unit_of_measurement_; ///< Unit of measurement override
optional<int8_t> accuracy_decimals_; ///< Accuracy in decimals override optional<int8_t> accuracy_decimals_; ///< Accuracy in decimals override
optional<std::string> device_class_; ///< Device class override
optional<StateClass> state_class_{STATE_CLASS_NONE}; ///< State class override optional<StateClass> state_class_{STATE_CLASS_NONE}; ///< State class override
bool force_update_{false}; ///< Force update mode bool force_update_{false}; ///< Force update mode
bool has_state_{false}; bool has_state_{false};

View file

@ -287,18 +287,17 @@ HorizontalCoordinate Sun::calc_coords_() {
*/ */
return sun.true_coordinate(m); return sun.true_coordinate(m);
} }
optional<time::ESPTime> Sun::calc_event_(bool rising, double zenith) { optional<time::ESPTime> Sun::calc_event_(time::ESPTime date, bool rising, double zenith) {
SunAtLocation sun{location_}; SunAtLocation sun{location_};
auto now = this->time_->utcnow(); if (!date.is_valid())
if (!now.is_valid())
return {}; return {};
// Calculate UT1 timestamp at 0h // Calculate UT1 timestamp at 0h
auto today = now; auto today = date;
today.hour = today.minute = today.second = 0; today.hour = today.minute = today.second = 0;
today.recalc_timestamp_utc(); today.recalc_timestamp_utc();
auto it = sun.event(rising, today, zenith); 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 // We're calculating *next* sunrise/sunset, but calculated event
// is today, so try again tomorrow // is today, so try again tomorrow
time_t new_timestamp = today.timestamp + 24 * 60 * 60; time_t new_timestamp = today.timestamp + 24 * 60 * 60;
@ -307,9 +306,19 @@ optional<time::ESPTime> Sun::calc_event_(bool rising, double zenith) {
} }
return it; return it;
} }
optional<time::ESPTime> Sun::calc_event_(bool rising, double zenith) {
auto it = Sun::calc_event_(this->time_->utcnow(), rising, zenith);
return it;
}
optional<time::ESPTime> Sun::sunrise(double elevation) { return this->calc_event_(true, 90 - elevation); } optional<time::ESPTime> Sun::sunrise(double elevation) { return this->calc_event_(true, 90 - elevation); }
optional<time::ESPTime> Sun::sunset(double elevation) { return this->calc_event_(false, 90 - elevation); } optional<time::ESPTime> Sun::sunset(double elevation) { return this->calc_event_(false, 90 - elevation); }
optional<time::ESPTime> Sun::sunrise(time::ESPTime date, double elevation) {
return this->calc_event_(date, true, 90 - elevation);
}
optional<time::ESPTime> 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::elevation() { return this->calc_coords_().elevation; }
double Sun::azimuth() { return this->calc_coords_().azimuth; } double Sun::azimuth() { return this->calc_coords_().azimuth; }

View file

@ -59,6 +59,8 @@ class Sun {
optional<time::ESPTime> sunrise(double elevation); optional<time::ESPTime> sunrise(double elevation);
optional<time::ESPTime> sunset(double elevation); optional<time::ESPTime> sunset(double elevation);
optional<time::ESPTime> sunrise(time::ESPTime date, double elevation);
optional<time::ESPTime> sunset(time::ESPTime date, double elevation);
double elevation(); double elevation();
double azimuth(); double azimuth();
@ -66,6 +68,7 @@ class Sun {
protected: protected:
internal::HorizontalCoordinate calc_coords_(); internal::HorizontalCoordinate calc_coords_();
optional<time::ESPTime> calc_event_(bool rising, double zenith); optional<time::ESPTime> calc_event_(bool rising, double zenith);
optional<time::ESPTime> calc_event_(time::ESPTime date, bool rising, double zenith);
time::RealTimeClock *time_; time::RealTimeClock *time_;
internal::GeoLocation location_; internal::GeoLocation location_;

View file

@ -63,13 +63,6 @@ void Switch::add_on_state_callback(std::function<void(bool)> &&callback) {
void Switch::set_inverted(bool inverted) { this->inverted_ = inverted; } void Switch::set_inverted(bool inverted) { this->inverted_ = inverted; }
bool Switch::is_inverted() const { return this->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) { void log_switch(const char *tag, const char *prefix, const char *type, Switch *obj) {
if (obj != nullptr) { if (obj != nullptr) {
ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str()); ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());

View file

@ -29,7 +29,7 @@ enum SwitchRestoreMode {
* A switch is basically just a combination of a binary sensor (for reporting switch values) * 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. * and a write_state method that writes a state to the hardware.
*/ */
class Switch : public EntityBase { class Switch : public EntityBase, public EntityBase_DeviceClass {
public: public:
explicit Switch(); explicit Switch();
@ -103,10 +103,6 @@ class Switch : public EntityBase {
bool is_inverted() const; 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; } void set_restore_mode(SwitchRestoreMode restore_mode) { this->restore_mode = restore_mode; }
protected: protected:
@ -124,7 +120,6 @@ class Switch : public EntityBase {
bool inverted_{false}; bool inverted_{false};
Deduplicator<bool> publish_dedup_; Deduplicator<bool> publish_dedup_;
ESPPreferenceObject rtc_; ESPPreferenceObject rtc_;
optional<std::string> device_class_;
}; };
#define LOG_SWITCH(prefix, type, obj) log_switch((TAG), (prefix), LOG_STR_LITERAL(type), (obj)) #define LOG_SWITCH(prefix, type, obj) log_switch((TAG), (prefix), LOG_STR_LITERAL(type), (obj))

View file

@ -73,6 +73,7 @@ async def to_code(config):
await automation.build_automation( await automation.build_automation(
var.get_stop_trigger(), [], config[CONF_STOP_ACTION] var.get_stop_trigger(), [], config[CONF_STOP_ACTION]
) )
cg.add(var.set_has_stop(True))
if CONF_TILT_ACTION in config: if CONF_TILT_ACTION in config:
await automation.build_automation( await automation.build_automation(
var.get_tilt_trigger(), [(float, "tilt")], config[CONF_TILT_ACTION] var.get_tilt_trigger(), [(float, "tilt")], config[CONF_TILT_ACTION]

View file

@ -109,6 +109,7 @@ void TemplateCover::control(const CoverCall &call) {
CoverTraits TemplateCover::get_traits() { CoverTraits TemplateCover::get_traits() {
auto traits = CoverTraits(); auto traits = CoverTraits();
traits.set_is_assumed_state(this->assumed_state_); 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_position(this->has_position_);
traits.set_supports_tilt(this->has_tilt_); traits.set_supports_tilt(this->has_tilt_);
return traits; return traits;
@ -116,6 +117,7 @@ CoverTraits TemplateCover::get_traits() {
Trigger<float> *TemplateCover::get_position_trigger() const { return this->position_trigger_; } Trigger<float> *TemplateCover::get_position_trigger() const { return this->position_trigger_; }
Trigger<float> *TemplateCover::get_tilt_trigger() const { return this->tilt_trigger_; } Trigger<float> *TemplateCover::get_tilt_trigger() const { return this->tilt_trigger_; }
void TemplateCover::set_tilt_lambda(std::function<optional<float>()> &&tilt_f) { this->tilt_f_ = tilt_f; } void TemplateCover::set_tilt_lambda(std::function<optional<float>()> &&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_position(bool has_position) { this->has_position_ = has_position; }
void TemplateCover::set_has_tilt(bool has_tilt) { this->has_tilt_ = has_tilt; } void TemplateCover::set_has_tilt(bool has_tilt) { this->has_tilt_ = has_tilt; }
void TemplateCover::stop_prev_trigger_() { void TemplateCover::stop_prev_trigger_() {

View file

@ -26,6 +26,7 @@ class TemplateCover : public cover::Cover, public Component {
void set_optimistic(bool optimistic); void set_optimistic(bool optimistic);
void set_assumed_state(bool assumed_state); void set_assumed_state(bool assumed_state);
void set_tilt_lambda(std::function<optional<float>()> &&tilt_f); void set_tilt_lambda(std::function<optional<float>()> &&tilt_f);
void set_has_stop(bool has_stop);
void set_has_position(bool has_position); void set_has_position(bool has_position);
void set_has_tilt(bool has_tilt); void set_has_tilt(bool has_tilt);
void set_restore_mode(TemplateCoverRestoreMode restore_mode) { restore_mode_ = restore_mode; } 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}; bool optimistic_{false};
Trigger<> *open_trigger_; Trigger<> *open_trigger_;
Trigger<> *close_trigger_; Trigger<> *close_trigger_;
bool has_stop_{false};
Trigger<> *stop_trigger_; Trigger<> *stop_trigger_;
Trigger<> *prev_command_trigger_{nullptr}; Trigger<> *prev_command_trigger_{nullptr};
Trigger<float> *position_trigger_; Trigger<float> *position_trigger_;

View file

@ -51,6 +51,7 @@ void TimeBasedCover::loop() {
float TimeBasedCover::get_setup_priority() const { return setup_priority::DATA; } float TimeBasedCover::get_setup_priority() const { return setup_priority::DATA; }
CoverTraits TimeBasedCover::get_traits() { CoverTraits TimeBasedCover::get_traits() {
auto traits = CoverTraits(); auto traits = CoverTraits();
traits.set_supports_stop(true);
traits.set_supports_position(true); traits.set_supports_position(true);
traits.set_supports_toggle(true); traits.set_supports_toggle(true);
traits.set_is_assumed_state(this->assumed_state_); traits.set_is_assumed_state(this->assumed_state_);

View file

@ -128,6 +128,7 @@ void TuyaCover::dump_config() {
cover::CoverTraits TuyaCover::get_traits() { cover::CoverTraits TuyaCover::get_traits() {
auto traits = cover::CoverTraits(); auto traits = cover::CoverTraits();
traits.set_supports_stop(true);
traits.set_supports_position(true); traits.set_supports_position(true);
return traits; return traits;
} }

View file

@ -63,7 +63,10 @@ void VoiceAssistant::start(struct sockaddr_storage *addr, uint16_t port) {
void VoiceAssistant::request_start() { void VoiceAssistant::request_start() {
ESP_LOGD(TAG, "Requesting 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() { void VoiceAssistant::signal_stop() {

View file

@ -252,6 +252,7 @@ def _validate(config):
CONF_OUTPUT_POWER = "output_power" CONF_OUTPUT_POWER = "output_power"
CONF_PASSIVE_SCAN = "passive_scan"
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
cv.Schema( cv.Schema(
{ {
@ -280,6 +281,7 @@ CONFIG_SCHEMA = cv.All(
cv.SplitDefault(CONF_ENABLE_RRM, esp32_idf=False): cv.All( cv.SplitDefault(CONF_ENABLE_RRM, esp32_idf=False): cv.All(
cv.boolean, cv.only_with_esp_idf cv.boolean, cv.only_with_esp_idf
), ),
cv.Optional(CONF_PASSIVE_SCAN, default=False): cv.boolean,
cv.Optional("enable_mdns"): cv.invalid( cv.Optional("enable_mdns"): cv.invalid(
"This option has been removed. Please use the [disabled] option under the " "This option has been removed. Please use the [disabled] option under the "
"new mdns component instead." "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_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
cg.add(var.set_power_save_mode(config[CONF_POWER_SAVE_MODE])) 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_fast_connect(config[CONF_FAST_CONNECT]))
cg.add(var.set_passive_scan(config[CONF_PASSIVE_SCAN]))
if CONF_OUTPUT_POWER in config: if CONF_OUTPUT_POWER in config:
cg.add(var.set_output_power(config[CONF_OUTPUT_POWER])) cg.add(var.set_output_power(config[CONF_OUTPUT_POWER]))

View file

@ -385,7 +385,7 @@ void WiFiComponent::print_connect_params_() {
void WiFiComponent::start_scanning() { void WiFiComponent::start_scanning() {
this->action_started_ = millis(); this->action_started_ = millis();
ESP_LOGD(TAG, "Starting scan..."); ESP_LOGD(TAG, "Starting scan...");
this->wifi_scan_start_(); this->wifi_scan_start_(this->passive_scan_);
this->state_ = WIFI_COMPONENT_STATE_STA_SCANNING; 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_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) { std::string WiFiComponent::format_mac_addr(const uint8_t *mac) {
char buf[20]; char buf[20];
sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

View file

@ -217,6 +217,8 @@ class WiFiComponent : public Component {
void set_power_save_mode(WiFiPowerSaveMode power_save); void set_power_save_mode(WiFiPowerSaveMode power_save);
void set_output_power(float output_power) { output_power_ = output_power; } 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); void save_wifi_sta(const std::string &ssid, const std::string &password);
// ========== INTERNAL METHODS ========== // ========== INTERNAL METHODS ==========
// (In most use cases you won't need these) // (In most use cases you won't need these)
@ -294,7 +296,7 @@ class WiFiComponent : public Component {
bool wifi_sta_connect_(const WiFiAP &ap); bool wifi_sta_connect_(const WiFiAP &ap);
void wifi_pre_setup_(); void wifi_pre_setup_();
WiFiSTAConnectStatus wifi_sta_connect_status_(); WiFiSTAConnectStatus wifi_sta_connect_status_();
bool wifi_scan_start_(); bool wifi_scan_start_(bool passive);
bool wifi_ap_ip_config_(optional<ManualIP> manual_ip); bool wifi_ap_ip_config_(optional<ManualIP> manual_ip);
bool wifi_start_ap_(const WiFiAP &ap); bool wifi_start_ap_(const WiFiAP &ap);
bool wifi_disconnect_(); bool wifi_disconnect_();
@ -349,6 +351,7 @@ class WiFiComponent : public Component {
bool scan_done_{false}; bool scan_done_{false};
bool ap_setup_{false}; bool ap_setup_{false};
optional<float> output_power_; optional<float> output_power_;
bool passive_scan_{false};
ESPPreferenceObject pref_; ESPPreferenceObject pref_;
bool has_saved_wifi_settings_{false}; bool has_saved_wifi_settings_{false};
#ifdef USE_WIFI_11KV_SUPPORT #ifdef USE_WIFI_11KV_SUPPORT

View file

@ -618,13 +618,13 @@ WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() {
} }
return WiFiSTAConnectStatus::IDLE; return WiFiSTAConnectStatus::IDLE;
} }
bool WiFiComponent::wifi_scan_start_() { bool WiFiComponent::wifi_scan_start_(bool passive) {
// enable STA // enable STA
if (!this->wifi_mode_(true, {})) if (!this->wifi_mode_(true, {}))
return false; return false;
// need to use WiFi because of WiFiScanClass allocations :( // 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) { if (err != WIFI_SCAN_RUNNING) {
ESP_LOGV(TAG, "WiFi.scanNetworks failed! %d", err); ESP_LOGV(TAG, "WiFi.scanNetworks failed! %d", err);
return false; return false;

View file

@ -601,7 +601,7 @@ WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() {
return WiFiSTAConnectStatus::IDLE; return WiFiSTAConnectStatus::IDLE;
} }
} }
bool WiFiComponent::wifi_scan_start_() { bool WiFiComponent::wifi_scan_start_(bool passive) {
static bool first_scan = false; static bool first_scan = false;
// enable STA // enable STA
@ -615,14 +615,22 @@ bool WiFiComponent::wifi_scan_start_() {
config.channel = 0; config.channel = 0;
config.show_hidden = 1; config.show_hidden = 1;
#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) #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) { if (first_scan) {
if (passive) {
config.scan_time.passive = 200;
} else {
config.scan_time.active.min = 100; config.scan_time.active.min = 100;
config.scan_time.active.max = 200; config.scan_time.active.max = 200;
}
} else {
if (passive) {
config.scan_time.passive = 500;
} else { } else {
config.scan_time.active.min = 400; config.scan_time.active.min = 400;
config.scan_time.active.max = 500; config.scan_time.active.max = 500;
} }
}
#endif #endif
first_scan = false; first_scan = false;
bool ret = wifi_station_scan(&config, &WiFiComponent::s_wifi_scan_done_callback); bool ret = wifi_station_scan(&config, &WiFiComponent::s_wifi_scan_done_callback);

View file

@ -17,6 +17,7 @@
#ifdef USE_WIFI_WPA2_EAP #ifdef USE_WIFI_WPA2_EAP
#include <esp_wpa2.h> #include <esp_wpa2.h>
#endif #endif
#include "dhcpserver/dhcpserver.h"
#include "lwip/err.h" #include "lwip/err.h"
#include "lwip/dns.h" #include "lwip/dns.h"
@ -32,7 +33,7 @@ namespace wifi {
static const char *const TAG = "wifi_esp32"; static const char *const TAG = "wifi_esp32";
static EventGroupHandle_t s_wifi_event_group; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) 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_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 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) static bool s_sta_started = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
@ -414,17 +415,17 @@ bool WiFiComponent::wifi_sta_ip_config_(optional<ManualIP> manual_ip) {
if (!this->wifi_mode_(true, {})) if (!this->wifi_mode_(true, {}))
return false; return false;
tcpip_adapter_dhcp_status_t dhcp_status; esp_netif_dhcp_status_t dhcp_status;
esp_err_t err = tcpip_adapter_dhcpc_get_status(TCPIP_ADAPTER_IF_STA, &dhcp_status); esp_err_t err = esp_netif_dhcpc_get_status(s_sta_netif, &dhcp_status);
if (err != ESP_OK) { 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; return false;
} }
if (!manual_ip.has_value()) { if (!manual_ip.has_value()) {
// Use DHCP client // No manual IP is set; use DHCP client
if (dhcp_status != TCPIP_ADAPTER_DHCP_STARTED) { if (dhcp_status != ESP_NETIF_DHCP_STARTED) {
err = tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA); err = esp_netif_dhcpc_start(s_sta_netif);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGV(TAG, "Starting DHCP client failed! %d", err); ESP_LOGV(TAG, "Starting DHCP client failed! %d", err);
} }
@ -433,43 +434,29 @@ bool WiFiComponent::wifi_sta_ip_config_(optional<ManualIP> manual_ip) {
return true; return true;
} }
tcpip_adapter_ip_info_t info; esp_netif_ip_info_t info; // struct of ip4_addr_t with ip, netmask, gw
memset(&info, 0, sizeof(info));
info.ip.addr = static_cast<uint32_t>(manual_ip->static_ip); info.ip.addr = static_cast<uint32_t>(manual_ip->static_ip);
info.gw.addr = static_cast<uint32_t>(manual_ip->gateway); info.gw.addr = static_cast<uint32_t>(manual_ip->gateway);
info.netmask.addr = static_cast<uint32_t>(manual_ip->subnet); info.netmask.addr = static_cast<uint32_t>(manual_ip->subnet);
err = esp_netif_dhcpc_stop(s_sta_netif);
err = tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA);
if (err != ESP_OK && err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED) { 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; return false;
} }
err = esp_netif_set_ip_info(s_sta_netif, &info);
err = tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &info);
if (err != ESP_OK) { 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; return false;
} }
ip_addr_t dns; esp_netif_dns_info_t dns;
#if LWIP_IPV6
dns.type = IPADDR_TYPE_V4;
#endif
if (uint32_t(manual_ip->dns1) != 0) { if (uint32_t(manual_ip->dns1) != 0) {
#if LWIP_IPV6 dns.ip.u_addr.ip4.addr = static_cast<uint32_t>(manual_ip->dns1);
dns.u_addr.ip4.addr = static_cast<uint32_t>(manual_ip->dns1); esp_netif_set_dns_info(s_sta_netif, ESP_NETIF_DNS_MAIN, &dns);
#else
dns.addr = static_cast<uint32_t>(manual_ip->dns1);
#endif
dns_setserver(0, &dns);
} }
if (uint32_t(manual_ip->dns2) != 0) { if (uint32_t(manual_ip->dns2) != 0) {
#if LWIP_IPV6 dns.ip.u_addr.ip4.addr = static_cast<uint32_t>(manual_ip->dns2);
dns.u_addr.ip4.addr = static_cast<uint32_t>(manual_ip->dns2); esp_netif_set_dns_info(s_sta_netif, ESP_NETIF_DNS_BACKUP, &dns);
#else
dns.addr = static_cast<uint32_t>(manual_ip->dns2);
#endif
dns_setserver(1, &dns);
} }
return true; return true;
@ -478,10 +465,10 @@ bool WiFiComponent::wifi_sta_ip_config_(optional<ManualIP> manual_ip) {
network::IPAddress WiFiComponent::wifi_sta_ip() { network::IPAddress WiFiComponent::wifi_sta_ip() {
if (!this->has_sta()) if (!this->has_sta())
return {}; return {};
tcpip_adapter_ip_info_t ip; esp_netif_ip_info_t ip;
esp_err_t err = tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip); esp_err_t err = esp_netif_get_ip_info(s_sta_netif, &ip);
if (err != ESP_OK) { 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 false;
} }
return {ip.ip.addr}; 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) { if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_START) {
ESP_LOGV(TAG, "Event: WiFi STA start"); ESP_LOGV(TAG, "Event: WiFi STA start");
// apply hostname // 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) { 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; 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) { } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_GOT_IP) {
const auto &it = data->data.ip_got_ip; const auto &it = data->data.ip_got_ip;
#if LWIP_IPV6_AUTOCONFIG #if LWIP_IPV6_AUTOCONFIG
tcpip_adapter_create_ip6_linklocal(TCPIP_ADAPTER_IF_STA); esp_netif_create_ip6_linklocal(s_sta_netif);
#endif #endif
ESP_LOGV(TAG, "Event: Got IP static_ip=%s gateway=%s", format_ip4_addr(it.ip_info.ip).c_str(), 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()); format_ip4_addr(it.ip_info.gw).c_str());
@ -736,7 +723,7 @@ WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() {
} }
return WiFiSTAConnectStatus::IDLE; return WiFiSTAConnectStatus::IDLE;
} }
bool WiFiComponent::wifi_scan_start_() { bool WiFiComponent::wifi_scan_start_(bool passive) {
// enable STA // enable STA
if (!this->wifi_mode_(true, {})) if (!this->wifi_mode_(true, {}))
return false; return false;
@ -746,9 +733,13 @@ bool WiFiComponent::wifi_scan_start_() {
config.bssid = nullptr; config.bssid = nullptr;
config.channel = 0; config.channel = 0;
config.show_hidden = true; config.show_hidden = true;
config.scan_type = WIFI_SCAN_TYPE_ACTIVE; 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.min = 100;
config.scan_time.active.max = 300; config.scan_time.active.max = 300;
}
esp_err_t err = esp_wifi_scan_start(&config, false); esp_err_t err = esp_wifi_scan_start(&config, false);
if (err != ESP_OK) { if (err != ESP_OK) {
@ -766,8 +757,7 @@ bool WiFiComponent::wifi_ap_ip_config_(optional<ManualIP> manual_ip) {
if (!this->wifi_mode_({}, true)) if (!this->wifi_mode_({}, true))
return false; return false;
tcpip_adapter_ip_info_t info; esp_netif_ip_info_t info;
memset(&info, 0, sizeof(info));
if (manual_ip.has_value()) { if (manual_ip.has_value()) {
info.ip.addr = static_cast<uint32_t>(manual_ip->static_ip); info.ip.addr = static_cast<uint32_t>(manual_ip->static_ip);
info.gw.addr = static_cast<uint32_t>(manual_ip->gateway); info.gw.addr = static_cast<uint32_t>(manual_ip->gateway);
@ -777,17 +767,17 @@ bool WiFiComponent::wifi_ap_ip_config_(optional<ManualIP> manual_ip) {
info.gw.addr = static_cast<uint32_t>(network::IPAddress(192, 168, 4, 1)); info.gw.addr = static_cast<uint32_t>(network::IPAddress(192, 168, 4, 1));
info.netmask.addr = static_cast<uint32_t>(network::IPAddress(255, 255, 255, 0)); info.netmask.addr = static_cast<uint32_t>(network::IPAddress(255, 255, 255, 0));
} }
tcpip_adapter_dhcp_status_t dhcp_status; esp_netif_dhcp_status_t dhcp_status;
tcpip_adapter_dhcps_get_status(TCPIP_ADAPTER_IF_AP, &dhcp_status); esp_netif_dhcps_get_status(s_sta_netif, &dhcp_status);
err = tcpip_adapter_dhcps_stop(TCPIP_ADAPTER_IF_AP); err = esp_netif_dhcps_stop(s_sta_netif);
if (err != ESP_OK) { 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; 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) { 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; return false;
} }
@ -800,17 +790,17 @@ bool WiFiComponent::wifi_ap_ip_config_(optional<ManualIP> manual_ip) {
start_address[3] += 100; start_address[3] += 100;
lease.end_ip.addr = static_cast<uint32_t>(start_address); lease.end_ip.addr = static_cast<uint32_t>(start_address);
ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str()); 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) { 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; return false;
} }
err = tcpip_adapter_dhcps_start(TCPIP_ADAPTER_IF_AP); err = esp_netif_dhcps_start(s_sta_netif);
if (err != ESP_OK) { 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; return false;
} }
@ -856,8 +846,8 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) {
return true; return true;
} }
network::IPAddress WiFiComponent::wifi_soft_ap_ip() { network::IPAddress WiFiComponent::wifi_soft_ap_ip() {
tcpip_adapter_ip_info_t ip; esp_netif_ip_info_t ip;
tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ip); esp_netif_get_ip_info(s_sta_netif, &ip);
return {ip.ip.addr}; return {ip.ip.addr};
} }
bool WiFiComponent::wifi_disconnect_() { return esp_wifi_disconnect(); } bool WiFiComponent::wifi_disconnect_() { return esp_wifi_disconnect(); }

View file

@ -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_result_.clear();
this->scan_done_ = false; this->scan_done_ = false;
cyw43_wifi_scan_options_t scan_options = {0}; 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); int err = cyw43_wifi_scan(&cyw43_state, &scan_options, nullptr, &s_wifi_scan_result);
if (err) { if (err) {
ESP_LOGV(TAG, "cyw43_wifi_scan failed!"); ESP_LOGV(TAG, "cyw43_wifi_scan failed!");

View file

@ -72,6 +72,16 @@ void EntityBase::calc_object_id_() {
this->object_id_hash_ = fnv1_hash(this->object_id_c_str_); this->object_id_hash_ = fnv1_hash(this->object_id_c_str_);
} }
} }
uint32_t EntityBase::get_object_id_hash() { return this->object_id_hash_; } 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 } // namespace esphome

View file

@ -63,4 +63,15 @@ class EntityBase {
EntityCategory entity_category_{ENTITY_CATEGORY_NONE}; 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 } // namespace esphome

View file

@ -18,6 +18,8 @@
#elif defined(USE_ESP32_FRAMEWORK_ARDUINO) #elif defined(USE_ESP32_FRAMEWORK_ARDUINO)
#include <Esp.h> #include <Esp.h>
#elif defined(USE_ESP_IDF) #elif defined(USE_ESP_IDF)
#include "esp_mac.h"
#include "esp_random.h"
#include "esp_system.h" #include "esp_system.h"
#include <freertos/FreeRTOS.h> #include <freertos/FreeRTOS.h>
#include <freertos/portmacro.h> #include <freertos/portmacro.h>

View file

@ -104,7 +104,7 @@ extra_scripts = post:esphome/components/esp8266/post_build.py.script
; This are common settings for the ESP32 (all variants) using Arduino. ; This are common settings for the ESP32 (all variants) using Arduino.
[common:esp32-arduino] [common:esp32-arduino]
extends = common:arduino extends = common:arduino
platform = platformio/espressif32 @ 5.2.0 platform = platformio/espressif32@5.3.0
platform_packages = platform_packages =
platformio/framework-arduinoespressif32@~3.20005.0 platformio/framework-arduinoespressif32@~3.20005.0

View file

@ -2,7 +2,7 @@ voluptuous==0.13.1
PyYAML==6.0 PyYAML==6.0
paho-mqtt==1.6.1 paho-mqtt==1.6.1
colorama==0.4.6 colorama==0.4.6
tornado==6.2 tornado==6.3.1
tzlocal==4.2 # from time tzlocal==4.2 # from time
tzdata>=2021.1 # from time tzdata>=2021.1 # from time
pyserial==3.5 pyserial==3.5
@ -10,9 +10,12 @@ platformio==6.1.6 # When updating platformio, also update Dockerfile
esptool==4.5.1 esptool==4.5.1
click==8.1.3 click==8.1.3
esphome-dashboard==20230214.0 esphome-dashboard==20230214.0
aioesphomeapi==13.7.0 aioesphomeapi==13.7.1
zeroconf==0.56.0 zeroconf==0.56.0
# esp-idf requires this, but doesn't bundle it by default # esp-idf requires this, but doesn't bundle it by default
# https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24
kconfiglib==13.7.1 kconfiglib==13.7.1
# esp-idf >= 5.0 requires this
pyparsing >= 3.0

View file

@ -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 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 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 pyupgrade==3.3.1 # also change in .pre-commit-config.yaml when updating
pre-commit pre-commit
# Unit tests # Unit tests
pytest==7.3.0 pytest==7.3.1
pytest-cov==4.0.0 pytest-cov==4.0.0
pytest-mock==3.10.0 pytest-mock==3.10.0
pytest-asyncio==0.21.0 pytest-asyncio==0.21.0

View file

@ -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])

View file

@ -14,3 +14,5 @@ pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_te
pip3 install --no-use-pep517 -e . pip3 install --no-use-pep517 -e .
pre-commit install pre-commit install
script/platformio_install_deps.py platformio.ini

View file

@ -1247,6 +1247,13 @@ sensor:
temperature: temperature:
name: Max9611 Temp name: Max9611 Temp
update_interval: 1s update_interval: 1s
- platform: mlx90614
i2c_id: i2c_bus
ambient:
name: Ambient
object:
name: Object
emissivity: 1.0
- platform: mpl3115a2 - platform: mpl3115a2
i2c_id: i2c_bus i2c_id: i2c_bus
temperature: temperature: