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

View file

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

View file

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

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"]
DEPENDENCIES = ["ble_client"]
AUTO_LOAD = ["am43", "sensor"]
AUTO_LOAD = ["am43"]
CONF_INVERT_POSITION = "invert_position"
@ -27,10 +27,10 @@ CONFIG_SCHEMA = (
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_pin(config[CONF_PIN]))
cg.add(var.set_invert_position(config[CONF_INVERT_POSITION]))
yield cg.register_component(var, config)
yield cover.register_cover(var, config)
yield ble_client.register_ble_node(var, config)
await cg.register_component(var, config)
await cover.register_cover(var, config)
await ble_client.register_ble_node(var, config)

View file

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

View file

@ -11,6 +11,7 @@ from esphome.const import (
UNIT_PERCENT,
)
AUTO_LOAD = ["am43"]
CODEOWNERS = ["@buxtronix"]
am43_ns = cg.esphome_ns.namespace("am43")
@ -38,15 +39,15 @@ CONFIG_SCHEMA = (
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield ble_client.register_ble_node(var, config)
await cg.register_component(var, config)
await ble_client.register_ble_node(var, config)
if CONF_BATTERY_LEVEL in config:
sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL])
sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL])
cg.add(var.set_battery(sens))
if CONF_ILLUMINANCE in config:
sens = yield sensor.new_sensor(config[CONF_ILLUMINANCE])
sens = await sensor.new_sensor(config[CONF_ILLUMINANCE])
cg.add(var.set_illuminance(sens))

View file

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

View file

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

View file

@ -530,7 +530,6 @@ bool APIConnection::send_climate_state(climate::Climate *climate) {
resp.custom_fan_mode = climate->custom_fan_mode.value();
if (traits.get_supports_presets() && climate->preset.has_value()) {
resp.preset = static_cast<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())
resp.custom_preset = climate->custom_preset.value();
@ -591,8 +590,6 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
call.set_target_temperature_low(msg.target_temperature_low);
if (msg.has_target_temperature_high)
call.set_target_temperature_high(msg.target_temperature_high);
if (msg.has_legacy_away)
call.set_preset(msg.legacy_away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME);
if (msg.has_fan_mode)
call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode));
if (msg.has_custom_fan_mode)
@ -944,7 +941,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
HelloResponse resp;
resp.api_version_major = 1;
resp.api_version_minor = 7;
resp.api_version_minor = 8;
resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
resp.name = App.get_name();

View file

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

View file

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

View file

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

View file

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

View file

@ -43,12 +43,7 @@ void BinarySensor::send_state_internal(bool state, bool is_initial) {
}
BinarySensor::BinarySensor() : state(false) {}
void BinarySensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
std::string BinarySensor::get_device_class() {
if (this->device_class_.has_value())
return *this->device_class_;
return "";
}
void BinarySensor::add_filter(Filter *filter) {
filter->parent_ = this;
if (this->filter_list_ == nullptr) {

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
* handles inverted inputs for you.
*/
class BinarySensor : public EntityBase {
class BinarySensor : public EntityBase, public EntityBase_DeviceClass {
public:
explicit BinarySensor();
@ -60,12 +60,6 @@ class BinarySensor : public EntityBase {
/// The current reported state of the binary sensor.
bool state;
/// Manually set the Home Assistant device class (see binary_sensor::device_class)
void set_device_class(const std::string &device_class);
/// Get the device class for this binary sensor, using the manual override if specified.
std::string get_device_class();
void add_filter(Filter *filter);
void add_filters(const std::vector<Filter *> &filters);
@ -82,7 +76,6 @@ class BinarySensor : public EntityBase {
protected:
CallbackManager<void(bool)> state_callback_{};
optional<std::string> device_class_{}; ///< Stores the override of the device class
Filter *filter_list_{nullptr};
bool has_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::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
std::string Button::get_device_class() { return this->device_class_; }
} // namespace button
} // namespace esphome

View file

@ -26,7 +26,7 @@ namespace button {
*
* A button is just a momentary switch that does not have a state, only a trigger.
*/
class Button : public EntityBase {
class Button : public EntityBase, public EntityBase_DeviceClass {
public:
/** Press this button. This is called by the front-end.
*
@ -40,19 +40,12 @@ class Button : public EntityBase {
*/
void add_on_press_callback(std::function<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:
/** You should implement this virtual method if you want to create your own button.
*/
virtual void press_action() = 0;
CallbackManager<void()> press_callback_{};
std::string device_class_{};
};
} // 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_LOW): cv.templatable(cv.temperature),
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH): cv.templatable(cv.temperature),
cv.Optional(CONF_AWAY): cv.templatable(cv.boolean),
cv.Optional(CONF_AWAY): cv.invalid("Use preset instead"),
cv.Exclusive(CONF_FAN_MODE, "fan_mode"): cv.templatable(
validate_climate_fan_mode
),
@ -379,9 +379,6 @@ async def climate_control_to_code(config, action_id, template_arg, args):
config[CONF_TARGET_TEMPERATURE_HIGH], args, float
)
cg.add(var.set_target_temperature_high(template_))
if CONF_AWAY in config:
template_ = await cg.templatable(config[CONF_AWAY], args, bool)
cg.add(var.set_away(template_))
if CONF_FAN_MODE in config:
template_ = await cg.templatable(config[CONF_FAN_MODE], args, ClimateFanMode)
cg.add(var.set_fan_mode(template_))

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_low() const { return this->target_temperature_low_; }
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<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<std::string> &ClimateCall::get_custom_preset() const { return this->custom_preset_; }
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) {
this->target_temperature_high_ = target_temperature_high;
return *this;

View file

@ -64,10 +64,6 @@ class ClimateCall {
* For climate devices with two point target temperature control
*/
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.
ClimateCall &set_fan_mode(ClimateFanMode fan_mode);
/// 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_low() 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<ClimateSwingMode> &get_swing_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.
optional<ClimateFanMode> fan_mode;

View file

@ -117,15 +117,6 @@ class ClimateTraits {
bool supports_custom_preset(const std::string &custom_preset) const {
return supported_custom_presets_.count(custom_preset);
}
ESPDEPRECATED("This method is deprecated, use set_supported_presets() instead", "v1.20")
void set_supports_away(bool supports) {
if (supports) {
supported_presets_.insert(CLIMATE_PRESET_AWAY);
supported_presets_.insert(CLIMATE_PRESET_HOME);
}
}
ESPDEPRECATED("This method is deprecated, use supports_preset() instead", "v1.20")
bool get_supports_away() const { return supports_preset(CLIMATE_PRESET_AWAY); }
void set_supported_swing_modes(std::set<ClimateSwingMode> modes) { supported_swing_modes_ = std::move(modes); }
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
// but the control() method hasn't implemented them yet.
traits.set_is_assumed_state(base.get_is_assumed_state());
traits.set_supports_stop(base.get_supports_stop());
traits.set_supports_position(base.get_supports_position());
traits.set_supports_tilt(base.get_supports_tilt());
traits.set_supports_toggle(base.get_supports_toggle());

View file

@ -145,7 +145,7 @@ CoverCall &CoverCall::set_stop(bool stop) {
return *this;
}
bool CoverCall::get_stop() const { return this->stop_; }
void Cover::set_device_class(const std::string &device_class) { this->device_class_override_ = device_class; }
CoverCall Cover::make_call() { return {this}; }
void Cover::open() {
auto call = this->make_call();
@ -204,11 +204,7 @@ optional<CoverRestoreState> Cover::restore_state_() {
return {};
return recovered;
}
std::string Cover::get_device_class() {
if (this->device_class_override_.has_value())
return *this->device_class_override_;
return "";
}
bool Cover::is_fully_open() const { return this->position == COVER_OPEN; }
bool Cover::is_fully_closed() const { return this->position == COVER_CLOSED; }

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

View file

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

View file

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

View file

@ -17,26 +17,29 @@ debug_ns = cg.esphome_ns.namespace("debug")
DebugComponent = debug_ns.class_("DebugComponent", cg.PollingComponent)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(DebugComponent),
cv.Optional(CONF_DEVICE): cv.invalid(
"The 'device' option has been moved to the 'debug' text_sensor component"
),
cv.Optional(CONF_FREE): cv.invalid(
"The 'free' option has been moved to the 'debug' sensor component"
),
cv.Optional(CONF_BLOCK): cv.invalid(
"The 'block' option has been moved to the 'debug' sensor component"
),
cv.Optional(CONF_FRAGMENTATION): cv.invalid(
"The 'fragmentation' option has been moved to the 'debug' sensor component"
),
cv.Optional(CONF_LOOP_TIME): cv.invalid(
"The 'loop_time' option has been moved to the 'debug' sensor component"
),
}
).extend(cv.polling_component_schema("60s"))
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(DebugComponent),
cv.Optional(CONF_DEVICE): cv.invalid(
"The 'device' option has been moved to the 'debug' text_sensor component"
),
cv.Optional(CONF_FREE): cv.invalid(
"The 'free' option has been moved to the 'debug' sensor component"
),
cv.Optional(CONF_BLOCK): cv.invalid(
"The 'block' option has been moved to the 'debug' sensor component"
),
cv.Optional(CONF_FRAGMENTATION): cv.invalid(
"The 'fragmentation' option has been moved to the 'debug' sensor component"
),
cv.Optional(CONF_LOOP_TIME): cv.invalid(
"The 'loop_time' option has been moved to the 'debug' sensor component"
),
}
).extend(cv.polling_component_schema("60s")),
cv.only_on(["esp32", "esp8266"]),
)
async def to_code(config):

View file

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

View file

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

View file

@ -163,7 +163,7 @@ RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 0, 5)
# The platformio/espressif32 version to use for arduino frameworks
# - https://github.com/platformio/platform-espressif32/releases
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
ARDUINO_PLATFORM_VERSION = cv.Version(5, 2, 0)
ARDUINO_PLATFORM_VERSION = cv.Version(5, 3, 0)
# The default/recommended esp-idf framework version
# - https://github.com/espressif/esp-idf/releases
@ -252,7 +252,7 @@ def _parse_platform_version(value):
try:
# if platform version is a valid version constraint, prefix the default package
cv.platformio_version_constraint(value)
return f"platformio/espressif32 @ {value}"
return f"platformio/espressif32@{value}"
except cv.Invalid:
return value
@ -367,12 +367,12 @@ async def to_code(config):
cg.add_build_flag("-Wno-nonnull-compare")
cg.add_platformio_option(
"platform_packages",
[f"platformio/framework-espidf @ {conf[CONF_SOURCE]}"],
[f"platformio/framework-espidf@{conf[CONF_SOURCE]}"],
)
# platformio/toolchain-esp32ulp does not support linux_aarch64 yet and has not been updated for over 2 years
# This is espressif's own published version which is more up to date.
cg.add_platformio_option(
"platform_packages", ["espressif/toolchain-esp32ulp @ 2.35.0-20220830"]
"platform_packages", ["espressif/toolchain-esp32ulp@2.35.0-20220830"]
)
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False)
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True)
@ -433,7 +433,7 @@ async def to_code(config):
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO")
cg.add_platformio_option(
"platform_packages",
[f"platformio/framework-arduinoespressif32 @ {conf[CONF_SOURCE]}"],
[f"platformio/framework-arduinoespressif32@{conf[CONF_SOURCE]}"],
)
cg.add_platformio_option("board_build.partitions", "partitions.csv")

View file

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

View file

@ -10,7 +10,7 @@ CONFLICTS_WITH = ["esp32_ble_beacon"]
CONF_BLE_ID = "ble_id"
NO_BLUTOOTH_VARIANTS = [const.VARIANT_ESP32S2]
NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2]
esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble")
ESP32BLE = esp32_ble_ns.class_("ESP32BLE", cg.Component)
@ -29,7 +29,7 @@ CONFIG_SCHEMA = cv.Schema(
def validate_variant(_):
variant = get_esp32_variant()
if variant in NO_BLUTOOTH_VARIANTS:
if variant in NO_BLUETOOTH_VARIANTS:
raise cv.Invalid(f"{variant} does not support Bluetooth")

View file

@ -125,7 +125,7 @@ def _parse_platform_version(value):
try:
# if platform version is a valid version constraint, prefix the default package
cv.platformio_version_constraint(value)
return f"platformio/espressif8266 @ {value}"
return f"platformio/espressif8266@{value}"
except cv.Invalid:
return value
@ -181,7 +181,7 @@ async def to_code(config):
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
cg.add_platformio_option(
"platform_packages",
[f"platformio/framework-arduinoespressif8266 @ {conf[CONF_SOURCE]}"],
[f"platformio/framework-arduinoespressif8266@{conf[CONF_SOURCE]}"],
)
# Default for platformio is LWIP2_LOW_MEMORY with:

View file

@ -26,8 +26,10 @@ EthernetComponent::EthernetComponent() { global_eth_component = this; }
void EthernetComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up Ethernet...");
// Delay here to allow power to stabilise before Ethernet is initialised.
delay(300); // NOLINT
if (esp_reset_reason() != ESP_RST_DEEPSLEEP) {
// Delay here to allow power to stabilise before Ethernet is initialized.
delay(300); // NOLINT
}
esp_err_t err;
err = esp_netif_init();
@ -52,30 +54,29 @@ void EthernetComponent::setup() {
esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config);
esp_eth_phy_t *phy;
switch (this->type_) {
case ETHERNET_TYPE_LAN8720: {
phy = esp_eth_phy_new_lan87xx(&phy_config);
this->phy_ = esp_eth_phy_new_lan87xx(&phy_config);
break;
}
case ETHERNET_TYPE_RTL8201: {
phy = esp_eth_phy_new_rtl8201(&phy_config);
this->phy_ = esp_eth_phy_new_rtl8201(&phy_config);
break;
}
case ETHERNET_TYPE_DP83848: {
phy = esp_eth_phy_new_dp83848(&phy_config);
this->phy_ = esp_eth_phy_new_dp83848(&phy_config);
break;
}
case ETHERNET_TYPE_IP101: {
phy = esp_eth_phy_new_ip101(&phy_config);
this->phy_ = esp_eth_phy_new_ip101(&phy_config);
break;
}
case ETHERNET_TYPE_JL1101: {
phy = esp_eth_phy_new_jl1101(&phy_config);
this->phy_ = esp_eth_phy_new_jl1101(&phy_config);
break;
}
case ETHERNET_TYPE_KSZ8081: {
phy = esp_eth_phy_new_ksz8081(&phy_config);
this->phy_ = esp_eth_phy_new_ksz8081(&phy_config);
break;
}
default: {
@ -84,7 +85,7 @@ void EthernetComponent::setup() {
}
}
esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy);
esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, this->phy_);
this->eth_handle_ = nullptr;
err = esp_eth_driver_install(&eth_config, &this->eth_handle_);
ESPHL_ERROR_CHECK(err, "ETH driver install error");
@ -356,6 +357,21 @@ std::string EthernetComponent::get_use_address() const {
void EthernetComponent::set_use_address(const std::string &use_address) { this->use_address_ = use_address; }
bool EthernetComponent::powerdown() {
ESP_LOGI(TAG, "Powering down ethernet PHY");
if (this->phy_ == nullptr) {
ESP_LOGE(TAG, "Ethernet PHY not assigned");
return false;
}
this->connected_ = false;
this->started_ = false;
if (this->phy_->pwrctl(this->phy_, false) != ESP_OK) {
ESP_LOGE(TAG, "Error powering down ethernet PHY");
return false;
}
return true;
}
} // namespace ethernet
} // namespace esphome

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,6 +4,7 @@
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include "esphome/core/application.h"
#include <cstring>
#include <cinttypes>
@ -273,10 +274,14 @@ void IDFI2CBus::recover_() {
// When SCL is kept LOW at this point, we might be looking at a device
// that applies clock stretching. Wait for the release of the SCL line,
// but not forever. There is no specification for the maximum allowed
// time. We'll stick to 500ms here.
auto wait = 20;
// time. We yield and reset the WDT, so as to avoid triggering reset.
// No point in trying to recover the bus by forcing a uC reset. Bus
// should recover in a few ms or less else not likely to recovery at
// all.
auto wait = 250;
while (wait-- && gpio_get_level(scl_pin) == 0) {
delay(25);
App.feed_wdt();
delayMicroseconds(half_period_usec * 2);
}
if (gpio_get_level(scl_pin) == 0) {
ESP_LOGE(TAG, "Recovery failed: SCL is held LOW during clock pulse cycle");

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_mode(config[CONF_SCROLL_MODE]))
cg.add(var.set_reverse(config[CONF_REVERSE_ENABLE]))
cg.add(var.set_flip_x([CONF_FLIP_X]))
cg.add(var.set_flip_x(config[CONF_FLIP_X]))
if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda(

View file

@ -4,10 +4,13 @@ from esphome.const import (
CONF_PROTOCOL,
CONF_SERVICES,
CONF_SERVICE,
KEY_CORE,
KEY_FRAMEWORK_VERSION,
)
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.core import CORE, coroutine_with_priority
from esphome.components.esp32 import add_idf_component
CODEOWNERS = ["@esphome/core"]
DEPENDENCIES = ["network"]
@ -79,6 +82,16 @@ async def to_code(config):
elif CORE.is_rp2040:
cg.add_library("LEAmDNS", None)
if CORE.using_esp_idf and CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version(
5, 0, 0
):
add_idf_component(
"mdns",
"https://github.com/espressif/esp-protocols.git",
"mdns-v1.0.9",
"components/mdns",
)
if config[CONF_DISABLED]:
return

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");
if (traits.supports_preset(CLIMATE_PRESET_HOME))
presets.add("home");
if (traits.supports_preset(CLIMATE_PRESET_AWAY)) {
// away_mode_command_topic
root[MQTT_AWAY_MODE_COMMAND_TOPIC] = this->get_away_command_topic();
// away_mode_state_topic
root[MQTT_AWAY_MODE_STATE_TOPIC] = this->get_away_state_topic();
if (traits.supports_preset(CLIMATE_PRESET_AWAY))
presets.add("away");
}
if (traits.supports_preset(CLIMATE_PRESET_BOOST))
presets.add("boost");
if (traits.supports_preset(CLIMATE_PRESET_COMFORT))
@ -197,29 +192,6 @@ void MQTTClimateComponent::setup() {
});
}
if (traits.supports_preset(CLIMATE_PRESET_AWAY)) {
this->subscribe(this->get_away_command_topic(), [this](const std::string &topic, const std::string &payload) {
auto onoff = parse_on_off(payload.c_str());
auto call = this->device_->make_call();
switch (onoff) {
case PARSE_ON:
call.set_preset(CLIMATE_PRESET_AWAY);
break;
case PARSE_OFF:
call.set_preset(CLIMATE_PRESET_HOME);
break;
case PARSE_TOGGLE:
call.set_preset(this->device_->preset == CLIMATE_PRESET_AWAY ? CLIMATE_PRESET_HOME : CLIMATE_PRESET_AWAY);
break;
case PARSE_NONE:
default:
ESP_LOGW(TAG, "Unknown payload '%s'", payload.c_str());
return;
}
call.perform();
});
}
if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) {
this->subscribe(this->get_preset_command_topic(), [this](const std::string &topic, const std::string &payload) {
auto call = this->device_->make_call();
@ -301,11 +273,6 @@ bool MQTTClimateComponent::publish_state_() {
success = false;
}
if (traits.supports_preset(CLIMATE_PRESET_AWAY)) {
std::string payload = ONOFF(this->device_->preset == CLIMATE_PRESET_AWAY);
if (!this->publish(this->get_away_state_topic(), payload))
success = false;
}
if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) {
std::string payload;
if (this->device_->preset.has_value()) {

View file

@ -16,13 +16,5 @@ std::string NumberTraits::get_unit_of_measurement() {
return "";
}
void NumberTraits::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
std::string NumberTraits::get_device_class() {
if (this->device_class_.has_value())
return *this->device_class_;
return "";
}
} // namespace number
} // namespace esphome

View file

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

View file

@ -8,6 +8,10 @@
#include <esp_ota_ops.h>
#include "esphome/components/md5/md5.h"
#if ESP_IDF_VERSION_MAJOR >= 5
#include <spi_flash_mmap.h>
#endif
namespace esphome {
namespace ota {
@ -16,9 +20,28 @@ OTAResponseTypes IDFOTABackend::begin(size_t image_size) {
if (this->partition_ == nullptr) {
return OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION;
}
esp_task_wdt_init(15, false); // The following function takes longer than the 5 seconds timeout of WDT
// The following function takes longer than the 5 seconds timeout of WDT
#if ESP_IDF_VERSION_MAJOR >= 5
esp_task_wdt_config_t wdtc;
wdtc.timeout_ms = 15000;
wdtc.idle_core_mask = 0;
wdtc.trigger_panic = false;
esp_task_wdt_reconfigure(&wdtc);
#else
esp_task_wdt_init(15, false);
#endif
esp_err_t err = esp_ota_begin(this->partition_, image_size, &this->update_handle_);
esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false); // Set the WDT back to the configured timeout
// Set the WDT back to the configured timeout
#if ESP_IDF_VERSION_MAJOR >= 5
wdtc.timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S;
esp_task_wdt_reconfigure(&wdtc);
#else
esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false);
#endif
if (err != ESP_OK) {
esp_ota_abort(this->update_handle_);
this->update_handle_ = 0;

View file

@ -2,7 +2,12 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation, pins
from esphome.components import i2c
from esphome.const import CONF_ON_TAG, CONF_TRIGGER_ID, CONF_RESET_PIN
from esphome.const import (
CONF_ON_TAG,
CONF_ON_TAG_REMOVED,
CONF_TRIGGER_ID,
CONF_RESET_PIN,
)
CODEOWNERS = ["@glmnet"]
AUTO_LOAD = ["binary_sensor"]
@ -24,6 +29,11 @@ RC522_SCHEMA = cv.Schema(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RC522Trigger),
}
),
cv.Optional(CONF_ON_TAG_REMOVED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RC522Trigger),
}
),
}
).extend(cv.polling_component_schema("1s"))
@ -37,5 +47,10 @@ async def setup_rc522(var, config):
for conf in config.get(CONF_ON_TAG, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
cg.add(var.register_trigger(trigger))
cg.add(var.register_ontag_trigger(trigger))
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
for conf in config.get(CONF_ON_TAG_REMOVED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
cg.add(var.register_ontagremoved_trigger(trigger))
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)

View file

@ -256,7 +256,7 @@ void RC522::loop() {
this->current_uid_ = rfid_uid;
for (auto *trigger : this->triggers_)
for (auto *trigger : this->triggers_ontag_)
trigger->process(rfid_uid);
if (report) {
@ -265,6 +265,11 @@ void RC522::loop() {
break;
}
case STATE_DONE: {
if (!this->current_uid_.empty()) {
ESP_LOGV(TAG, "Tag '%s' removed", format_uid(this->current_uid_).c_str());
for (auto *trigger : this->triggers_ontagremoved_)
trigger->process(this->current_uid_);
}
this->current_uid_ = {};
state_ = STATE_INIT;
break;

View file

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

View file

@ -102,7 +102,7 @@ def _parse_platform_version(value):
try:
# if platform version is a valid version constraint, prefix the default package
cv.platformio_version_constraint(value)
return f"platformio/raspberrypi @ {value}"
return f"platformio/raspberrypi@{value}"
except cv.Invalid:
return value
@ -148,7 +148,7 @@ async def to_code(config):
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
cg.add_platformio_option(
"platform_packages",
[f"earlephilhower/framework-arduinopico @ {conf[CONF_SOURCE]}"],
[f"earlephilhower/framework-arduinopico@{conf[CONF_SOURCE]}"],
)
cg.add_platformio_option("board_build.core", "earlephilhower")

View file

@ -25,10 +25,12 @@ from esphome.const import (
CONF_STATE_CLASS,
CONF_TO,
CONF_TRIGGER_ID,
CONF_TYPE,
CONF_UNIT_OF_MEASUREMENT,
CONF_WINDOW_SIZE,
CONF_MQTT_ID,
CONF_FORCE_UPDATE,
CONF_VALUE,
DEVICE_CLASS_APPARENT_POWER,
DEVICE_CLASS_AQI,
DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
@ -476,21 +478,38 @@ async def lambda_filter_to_code(config, filter_id):
return cg.new_Pvariable(filter_id, lambda_)
DELTA_SCHEMA = cv.Schema(
{
cv.Required(CONF_VALUE): cv.positive_float,
cv.Optional(CONF_TYPE, default="absolute"): cv.one_of(
"absolute", "percentage", lower=True
),
}
)
def validate_delta(config):
try:
return (cv.positive_float(config), False)
value = cv.positive_float(config)
return DELTA_SCHEMA({CONF_VALUE: value, CONF_TYPE: "absolute"})
except cv.Invalid:
pass
try:
return (cv.percentage(config), True)
value = cv.percentage(config)
return DELTA_SCHEMA({CONF_VALUE: value, CONF_TYPE: "percentage"})
except cv.Invalid:
pass
raise cv.Invalid("Delta filter requires a positive number or percentage value.")
@FILTER_REGISTRY.register("delta", DeltaFilter, validate_delta)
@FILTER_REGISTRY.register("delta", DeltaFilter, cv.Any(DELTA_SCHEMA, validate_delta))
async def delta_filter_to_code(config, filter_id):
return cg.new_Pvariable(filter_id, *config)
percentage = config[CONF_TYPE] == "percentage"
return cg.new_Pvariable(
filter_id,
config[CONF_VALUE],
percentage,
)
@FILTER_REGISTRY.register("or", OrFilter, validate_filters)

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; }
std::string Sensor::get_device_class() {
if (this->device_class_.has_value())
return *this->device_class_;
return "";
}
void Sensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
void Sensor::set_state_class(StateClass state_class) { this->state_class_ = state_class; }
StateClass Sensor::get_state_class() {
if (this->state_class_.has_value())

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.
*/
class Sensor : public EntityBase {
class Sensor : public EntityBase, public EntityBase_DeviceClass {
public:
explicit Sensor();
@ -68,11 +68,6 @@ class Sensor : public EntityBase {
/// Manually set the accuracy in decimals.
void set_accuracy_decimals(int8_t accuracy_decimals);
/// Get the device class, using the manual override if set.
std::string get_device_class();
/// Manually set the device class.
void set_device_class(const std::string &device_class);
/// Get the state class, using the manual override if set.
StateClass get_state_class();
/// Manually set the state class.
@ -165,7 +160,6 @@ class Sensor : public EntityBase {
optional<std::string> unit_of_measurement_; ///< Unit of measurement 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
bool force_update_{false}; ///< Force update mode
bool has_state_{false};

View file

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

View file

@ -59,6 +59,8 @@ class Sun {
optional<time::ESPTime> sunrise(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 azimuth();
@ -66,6 +68,7 @@ class Sun {
protected:
internal::HorizontalCoordinate calc_coords_();
optional<time::ESPTime> calc_event_(bool rising, double zenith);
optional<time::ESPTime> calc_event_(time::ESPTime date, bool rising, double zenith);
time::RealTimeClock *time_;
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; }
bool Switch::is_inverted() const { return this->inverted_; }
std::string Switch::get_device_class() {
if (this->device_class_.has_value())
return *this->device_class_;
return "";
}
void Switch::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
void log_switch(const char *tag, const char *prefix, const char *type, Switch *obj) {
if (obj != nullptr) {
ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());

View file

@ -29,7 +29,7 @@ enum SwitchRestoreMode {
* A switch is basically just a combination of a binary sensor (for reporting switch values)
* and a write_state method that writes a state to the hardware.
*/
class Switch : public EntityBase {
class Switch : public EntityBase, public EntityBase_DeviceClass {
public:
explicit Switch();
@ -103,10 +103,6 @@ class Switch : public EntityBase {
bool is_inverted() const;
/// Get the device class for this switch.
std::string get_device_class();
/// Set the Home Assistant device class for this switch.
void set_device_class(const std::string &device_class);
void set_restore_mode(SwitchRestoreMode restore_mode) { this->restore_mode = restore_mode; }
protected:
@ -124,7 +120,6 @@ class Switch : public EntityBase {
bool inverted_{false};
Deduplicator<bool> publish_dedup_;
ESPPreferenceObject rtc_;
optional<std::string> device_class_;
};
#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(
var.get_stop_trigger(), [], config[CONF_STOP_ACTION]
)
cg.add(var.set_has_stop(True))
if CONF_TILT_ACTION in config:
await automation.build_automation(
var.get_tilt_trigger(), [(float, "tilt")], config[CONF_TILT_ACTION]

View file

@ -109,6 +109,7 @@ void TemplateCover::control(const CoverCall &call) {
CoverTraits TemplateCover::get_traits() {
auto traits = CoverTraits();
traits.set_is_assumed_state(this->assumed_state_);
traits.set_supports_stop(this->has_stop_);
traits.set_supports_position(this->has_position_);
traits.set_supports_tilt(this->has_tilt_);
return traits;
@ -116,6 +117,7 @@ CoverTraits TemplateCover::get_traits() {
Trigger<float> *TemplateCover::get_position_trigger() const { return this->position_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_has_stop(bool has_stop) { this->has_stop_ = has_stop; }
void TemplateCover::set_has_position(bool has_position) { this->has_position_ = has_position; }
void TemplateCover::set_has_tilt(bool has_tilt) { this->has_tilt_ = has_tilt; }
void TemplateCover::stop_prev_trigger_() {

View file

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

View file

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

View file

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

View file

@ -63,7 +63,10 @@ void VoiceAssistant::start(struct sockaddr_storage *addr, uint16_t port) {
void VoiceAssistant::request_start() {
ESP_LOGD(TAG, "Requesting start...");
api::global_api_server->start_voice_assistant();
if (!api::global_api_server->start_voice_assistant()) {
ESP_LOGW(TAG, "Could not request start.");
this->error_trigger_->trigger("not-connected", "Could not request start.");
}
}
void VoiceAssistant::signal_stop() {

View file

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

View file

@ -385,7 +385,7 @@ void WiFiComponent::print_connect_params_() {
void WiFiComponent::start_scanning() {
this->action_started_ = millis();
ESP_LOGD(TAG, "Starting scan...");
this->wifi_scan_start_();
this->wifi_scan_start_(this->passive_scan_);
this->state_ = WIFI_COMPONENT_STATE_STA_SCANNING;
}
@ -615,6 +615,8 @@ bool WiFiComponent::is_connected() {
}
void WiFiComponent::set_power_save_mode(WiFiPowerSaveMode power_save) { this->power_save_ = power_save; }
void WiFiComponent::set_passive_scan(bool passive) { this->passive_scan_ = passive; }
std::string WiFiComponent::format_mac_addr(const uint8_t *mac) {
char buf[20];
sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

View file

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

View file

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

View file

@ -601,7 +601,7 @@ WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() {
return WiFiSTAConnectStatus::IDLE;
}
}
bool WiFiComponent::wifi_scan_start_() {
bool WiFiComponent::wifi_scan_start_(bool passive) {
static bool first_scan = false;
// enable STA
@ -615,13 +615,21 @@ bool WiFiComponent::wifi_scan_start_() {
config.channel = 0;
config.show_hidden = 1;
#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0)
config.scan_type = WIFI_SCAN_TYPE_ACTIVE;
config.scan_type = passive ? WIFI_SCAN_TYPE_PASSIVE : WIFI_SCAN_TYPE_ACTIVE;
if (first_scan) {
config.scan_time.active.min = 100;
config.scan_time.active.max = 200;
if (passive) {
config.scan_time.passive = 200;
} else {
config.scan_time.active.min = 100;
config.scan_time.active.max = 200;
}
} else {
config.scan_time.active.min = 400;
config.scan_time.active.max = 500;
if (passive) {
config.scan_time.passive = 500;
} else {
config.scan_time.active.min = 400;
config.scan_time.active.max = 500;
}
}
#endif
first_scan = false;

View file

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

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_done_ = false;
cyw43_wifi_scan_options_t scan_options = {0};
scan_options.scan_type = passive ? 1 : 0;
int err = cyw43_wifi_scan(&cyw43_state, &scan_options, nullptr, &s_wifi_scan_result);
if (err) {
ESP_LOGV(TAG, "cyw43_wifi_scan failed!");

View file

@ -72,6 +72,16 @@ void EntityBase::calc_object_id_() {
this->object_id_hash_ = fnv1_hash(this->object_id_c_str_);
}
}
uint32_t EntityBase::get_object_id_hash() { return this->object_id_hash_; }
std::string EntityBase_DeviceClass::get_device_class() {
if (this->device_class_ == nullptr) {
return "";
}
return this->device_class_;
}
void EntityBase_DeviceClass::set_device_class(const char *device_class) { this->device_class_ = device_class; }
} // namespace esphome

View file

@ -63,4 +63,15 @@ class EntityBase {
EntityCategory entity_category_{ENTITY_CATEGORY_NONE};
};
class EntityBase_DeviceClass {
public:
/// Get the device class, using the manual override if set.
std::string get_device_class();
/// Manually set the device class.
void set_device_class(const char *device_class);
protected:
const char *device_class_{nullptr}; ///< Device class override
};
} // namespace esphome

View file

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

View file

@ -80,9 +80,9 @@ build_flags =
; This are common settings for the ESP8266 using Arduino.
[common:esp8266-arduino]
extends = common:arduino
platform = platformio/espressif8266 @ 3.2.0
platform = platformio/espressif8266@3.2.0
platform_packages =
platformio/framework-arduinoespressif8266 @ ~3.30002.0
platformio/framework-arduinoespressif8266@~3.30002.0
framework = arduino
lib_deps =
@ -104,9 +104,9 @@ extra_scripts = post:esphome/components/esp8266/post_build.py.script
; This are common settings for the ESP32 (all variants) using Arduino.
[common:esp32-arduino]
extends = common:arduino
platform = platformio/espressif32 @ 5.2.0
platform = platformio/espressif32@5.3.0
platform_packages =
platformio/framework-arduinoespressif32 @ ~3.20005.0
platformio/framework-arduinoespressif32@~3.20005.0
framework = arduino
board = nodemcu-32s
@ -134,9 +134,9 @@ extra_scripts = post:esphome/components/esp32/post_build.py.script
; This are common settings for the ESP32 (all variants) using IDF.
[common:esp32-idf]
extends = common:idf
platform = platformio/espressif32 @ 5.3.0
platform = platformio/espressif32@5.3.0
platform_packages =
platformio/framework-espidf @ ~3.40404.0
platformio/framework-espidf@~3.40404.0
framework = espidf
lib_deps =
@ -157,8 +157,8 @@ board_build.filesystem_size = 0.5m
platform = https://github.com/maxgerhardt/platform-raspberrypi.git
platform_packages =
; earlephilhower/framework-arduinopico @ ~1.20602.0 ; Cannot use the platformio package until old releases stop getting deleted
earlephilhower/framework-arduinopico @ https://github.com/earlephilhower/arduino-pico/releases/download/2.6.2/rp2040-2.6.2.zip
; earlephilhower/framework-arduinopico@~1.20602.0 ; Cannot use the platformio package until old releases stop getting deleted
earlephilhower/framework-arduinopico@https://github.com/earlephilhower/arduino-pico/releases/download/2.6.2/rp2040-2.6.2.zip
framework = arduino
lib_deps =

View file

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

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

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 .
pre-commit install
script/platformio_install_deps.py platformio.ini

View file

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