diff --git a/CODEOWNERS b/CODEOWNERS index dc567ca5c08..e91116795a5 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -548,3 +548,4 @@ esphome/components/xxtea/* @clydebarrow esphome/components/zephyr/* @tomaszduda23 esphome/components/zhlt01/* @cfeenstra1024 esphome/components/zio_ultrasonic/* @kahrendt +esphome/components/zwave_proxy/* @kbx81 diff --git a/esphome/__main__.py b/esphome/__main__.py index 0147a82530d..f54fa8e3c67 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -212,7 +212,7 @@ def has_mqtt_logging() -> bool: if CONF_TOPIC not in log_topic: return False - return log_topic[CONF_LEVEL] != "NONE" + return log_topic.get(CONF_LEVEL, None) != "NONE" def has_mqtt() -> bool: diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index f82e762345b..fa64649df59 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -7,7 +7,7 @@ service APIConnection { option (needs_setup_connection) = false; option (needs_authentication) = false; } - rpc connect (ConnectRequest) returns (ConnectResponse) { + rpc authenticate (AuthenticationRequest) returns (AuthenticationResponse) { option (needs_setup_connection) = false; option (needs_authentication) = false; } @@ -66,6 +66,9 @@ service APIConnection { rpc voice_assistant_set_configuration(VoiceAssistantSetConfiguration) returns (void) {} rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {} + + rpc zwave_proxy_frame(ZWaveProxyFrame) returns (void) {} + rpc zwave_proxy_request(ZWaveProxyRequest) returns (void) {} } @@ -129,7 +132,7 @@ message HelloResponse { // Message sent at the beginning of each connection to authenticate the client // Can only be sent by the client and only at the beginning of the connection -message ConnectRequest { +message AuthenticationRequest { option (id) = 3; option (source) = SOURCE_CLIENT; option (no_delay) = true; @@ -141,7 +144,7 @@ message ConnectRequest { // Confirmation of successful connection. After this the connection is available for all traffic. // Can only be sent by the server and only at the beginning of the connection -message ConnectResponse { +message AuthenticationResponse { option (id) = 4; option (source) = SOURCE_SERVER; option (no_delay) = true; @@ -254,6 +257,9 @@ message DeviceInfoResponse { // Top-level area info to phase out suggested_area AreaInfo area = 22 [(field_ifdef) = "USE_AREAS"]; + + // Indicates if Z-Wave proxy support is available and features supported + uint32 zwave_proxy_feature_flags = 23 [(field_ifdef) = "USE_ZWAVE_PROXY"]; } message ListEntitiesRequest { @@ -2276,3 +2282,26 @@ message UpdateCommandRequest { UpdateCommand command = 2; uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"]; } + +// ==================== Z-WAVE ==================== + +message ZWaveProxyFrame { + option (id) = 128; + option (source) = SOURCE_BOTH; + option (ifdef) = "USE_ZWAVE_PROXY"; + option (no_delay) = true; + + bytes data = 1 [(fixed_array_size) = 257]; +} + +enum ZWaveProxyRequestType { + ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE = 0; + ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE = 1; +} +message ZWaveProxyRequest { + option (id) = 129; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_ZWAVE_PROXY"; + + ZWaveProxyRequestType type = 1; +} diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 79f9fe9a089..697b02b915d 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -30,6 +30,9 @@ #ifdef USE_VOICE_ASSISTANT #include "esphome/components/voice_assistant/voice_assistant.h" #endif +#ifdef USE_ZWAVE_PROXY +#include "esphome/components/zwave_proxy/zwave_proxy.h" +#endif namespace esphome::api { @@ -1203,7 +1206,16 @@ void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetCon voice_assistant::global_voice_assistant->on_set_configuration(msg.active_wake_words); } } +#endif +#ifdef USE_ZWAVE_PROXY +void APIConnection::zwave_proxy_frame(const ZWaveProxyFrame &msg) { + zwave_proxy::global_zwave_proxy->send_frame(msg.data, msg.data_len); +} + +void APIConnection::zwave_proxy_request(const ZWaveProxyRequest &msg) { + zwave_proxy::global_zwave_proxy->zwave_proxy_request(this, msg.type); +} #endif #ifdef USE_ALARM_CONTROL_PANEL @@ -1387,14 +1399,14 @@ bool APIConnection::send_hello_response(const HelloRequest &msg) { return this->send_message(resp, HelloResponse::MESSAGE_TYPE); } #ifdef USE_API_PASSWORD -bool APIConnection::send_connect_response(const ConnectRequest &msg) { - ConnectResponse resp; +bool APIConnection::send_authenticate_response(const AuthenticationRequest &msg) { + AuthenticationResponse resp; // bool invalid_password = 1; resp.invalid_password = !this->parent_->check_password(msg.password); if (!resp.invalid_password) { this->complete_authentication_(); } - return this->send_message(resp, ConnectResponse::MESSAGE_TYPE); + return this->send_message(resp, AuthenticationResponse::MESSAGE_TYPE); } #endif // USE_API_PASSWORD @@ -1460,6 +1472,9 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { #ifdef USE_VOICE_ASSISTANT resp.voice_assistant_feature_flags = voice_assistant::global_voice_assistant->get_feature_flags(); #endif +#ifdef USE_ZWAVE_PROXY + resp.zwave_proxy_feature_flags = zwave_proxy::global_zwave_proxy->get_feature_flags(); +#endif #ifdef USE_API_NOISE resp.api_encryption_supported = true; #endif diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 70fc881a827..380894a4ff0 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -171,6 +171,11 @@ class APIConnection final : public APIServerConnection { void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override; #endif +#ifdef USE_ZWAVE_PROXY + void zwave_proxy_frame(const ZWaveProxyFrame &msg) override; + void zwave_proxy_request(const ZWaveProxyRequest &msg) override; +#endif + #ifdef USE_ALARM_CONTROL_PANEL bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel); void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override; @@ -198,7 +203,7 @@ class APIConnection final : public APIServerConnection { #endif bool send_hello_response(const HelloRequest &msg) override; #ifdef USE_API_PASSWORD - bool send_connect_response(const ConnectRequest &msg) override; + bool send_authenticate_response(const AuthenticationRequest &msg) override; #endif bool send_disconnect_response(const DisconnectRequest &msg) override; bool send_ping_response(const PingRequest &msg) override; diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 7b1601515e9..58a083ad060 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -43,7 +43,7 @@ void HelloResponse::calculate_size(ProtoSize &size) const { size.add_length(1, this->name_ref_.size()); } #ifdef USE_API_PASSWORD -bool ConnectRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { +bool AuthenticationRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: this->password = value.as_string(); @@ -53,8 +53,8 @@ bool ConnectRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value } return true; } -void ConnectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); } -void ConnectResponse::calculate_size(ProtoSize &size) const { size.add_bool(1, this->invalid_password); } +void AuthenticationResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); } +void AuthenticationResponse::calculate_size(ProtoSize &size) const { size.add_bool(1, this->invalid_password); } #endif #ifdef USE_AREAS void AreaInfo::encode(ProtoWriteBuffer buffer) const { @@ -129,6 +129,9 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { #ifdef USE_AREAS buffer.encode_message(22, this->area); #endif +#ifdef USE_ZWAVE_PROXY + buffer.encode_uint32(23, this->zwave_proxy_feature_flags); +#endif } void DeviceInfoResponse::calculate_size(ProtoSize &size) const { #ifdef USE_API_PASSWORD @@ -181,6 +184,9 @@ void DeviceInfoResponse::calculate_size(ProtoSize &size) const { #ifdef USE_AREAS size.add_message_object(2, this->area); #endif +#ifdef USE_ZWAVE_PROXY + size.add_uint32(2, this->zwave_proxy_feature_flags); +#endif } #ifdef USE_BINARY_SENSOR void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { @@ -3013,5 +3019,35 @@ bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { return true; } #endif +#ifdef USE_ZWAVE_PROXY +bool ZWaveProxyFrame::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 1: { + const std::string &data_str = value.as_string(); + this->data_len = data_str.size(); + if (this->data_len > 257) { + this->data_len = 257; + } + memcpy(this->data, data_str.data(), this->data_len); + break; + } + default: + return false; + } + return true; +} +void ZWaveProxyFrame::encode(ProtoWriteBuffer buffer) const { buffer.encode_bytes(1, this->data, this->data_len); } +void ZWaveProxyFrame::calculate_size(ProtoSize &size) const { size.add_length(1, this->data_len); } +bool ZWaveProxyRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: + this->type = static_cast(value.as_uint32()); + break; + default: + return false; + } + return true; +} +#endif } // namespace esphome::api diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 421789a7709..d52cb9eab34 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -276,6 +276,12 @@ enum UpdateCommand : uint32_t { UPDATE_COMMAND_CHECK = 2, }; #endif +#ifdef USE_ZWAVE_PROXY +enum ZWaveProxyRequestType : uint32_t { + ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE = 0, + ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE = 1, +}; +#endif } // namespace enums @@ -361,12 +367,12 @@ class HelloResponse final : public ProtoMessage { protected: }; #ifdef USE_API_PASSWORD -class ConnectRequest final : public ProtoDecodableMessage { +class AuthenticationRequest final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 3; static constexpr uint8_t ESTIMATED_SIZE = 9; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "connect_request"; } + const char *message_name() const override { return "authentication_request"; } #endif std::string password{}; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -376,12 +382,12 @@ class ConnectRequest final : public ProtoDecodableMessage { protected: bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; }; -class ConnectResponse final : public ProtoMessage { +class AuthenticationResponse final : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 4; static constexpr uint8_t ESTIMATED_SIZE = 2; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "connect_response"; } + const char *message_name() const override { return "authentication_response"; } #endif bool invalid_password{false}; void encode(ProtoWriteBuffer buffer) const override; @@ -492,7 +498,7 @@ class DeviceInfo final : public ProtoMessage { class DeviceInfoResponse final : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 10; - static constexpr uint8_t ESTIMATED_SIZE = 247; + static constexpr uint8_t ESTIMATED_SIZE = 252; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "device_info_response"; } #endif @@ -552,6 +558,9 @@ class DeviceInfoResponse final : public ProtoMessage { #endif #ifdef USE_AREAS AreaInfo area{}; +#endif +#ifdef USE_ZWAVE_PROXY + uint32_t zwave_proxy_feature_flags{0}; #endif void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; @@ -2913,5 +2922,40 @@ class UpdateCommandRequest final : public CommandProtoMessage { bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; #endif +#ifdef USE_ZWAVE_PROXY +class ZWaveProxyFrame final : public ProtoDecodableMessage { + public: + static constexpr uint8_t MESSAGE_TYPE = 128; + static constexpr uint8_t ESTIMATED_SIZE = 33; +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *message_name() const override { return "z_wave_proxy_frame"; } +#endif + uint8_t data[257]{}; + uint16_t data_len{0}; + void encode(ProtoWriteBuffer buffer) const override; + void calculate_size(ProtoSize &size) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; +}; +class ZWaveProxyRequest final : public ProtoDecodableMessage { + public: + static constexpr uint8_t MESSAGE_TYPE = 129; + static constexpr uint8_t ESTIMATED_SIZE = 2; +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *message_name() const override { return "z_wave_proxy_request"; } +#endif + enums::ZWaveProxyRequestType type{}; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +#endif } // namespace esphome::api diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index 53af7eefefc..d24f9b3fdc5 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -655,6 +655,18 @@ template<> const char *proto_enum_to_string(enums::UpdateC } } #endif +#ifdef USE_ZWAVE_PROXY +template<> const char *proto_enum_to_string(enums::ZWaveProxyRequestType value) { + switch (value) { + case enums::ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE: + return "ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE"; + case enums::ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE: + return "ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE"; + default: + return "UNKNOWN"; + } +} +#endif void HelloRequest::dump_to(std::string &out) const { MessageDumpHelper helper(out, "HelloRequest"); @@ -670,8 +682,11 @@ void HelloResponse::dump_to(std::string &out) const { dump_field(out, "name", this->name_ref_); } #ifdef USE_API_PASSWORD -void ConnectRequest::dump_to(std::string &out) const { dump_field(out, "password", this->password); } -void ConnectResponse::dump_to(std::string &out) const { dump_field(out, "invalid_password", this->invalid_password); } +void AuthenticationRequest::dump_to(std::string &out) const { dump_field(out, "password", this->password); } +void AuthenticationResponse::dump_to(std::string &out) const { + MessageDumpHelper helper(out, "AuthenticationResponse"); + dump_field(out, "invalid_password", this->invalid_password); +} #endif void DisconnectRequest::dump_to(std::string &out) const { out.append("DisconnectRequest {}"); } void DisconnectResponse::dump_to(std::string &out) const { out.append("DisconnectResponse {}"); } @@ -751,6 +766,9 @@ void DeviceInfoResponse::dump_to(std::string &out) const { this->area.dump_to(out); out.append("\n"); #endif +#ifdef USE_ZWAVE_PROXY + dump_field(out, "zwave_proxy_feature_flags", this->zwave_proxy_feature_flags); +#endif } void ListEntitiesRequest::dump_to(std::string &out) const { out.append("ListEntitiesRequest {}"); } void ListEntitiesDoneResponse::dump_to(std::string &out) const { out.append("ListEntitiesDoneResponse {}"); } @@ -2104,6 +2122,18 @@ void UpdateCommandRequest::dump_to(std::string &out) const { #endif } #endif +#ifdef USE_ZWAVE_PROXY +void ZWaveProxyFrame::dump_to(std::string &out) const { + MessageDumpHelper helper(out, "ZWaveProxyFrame"); + out.append(" data: "); + out.append(format_hex_pretty(this->data, this->data_len)); + out.append("\n"); +} +void ZWaveProxyRequest::dump_to(std::string &out) const { + MessageDumpHelper helper(out, "ZWaveProxyRequest"); + dump_field(out, "type", static_cast(this->type)); +} +#endif } // namespace esphome::api diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 1bcbfe1ba2f..4afc66dc446 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -25,13 +25,13 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, break; } #ifdef USE_API_PASSWORD - case ConnectRequest::MESSAGE_TYPE: { - ConnectRequest msg; + case AuthenticationRequest::MESSAGE_TYPE: { + AuthenticationRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_connect_request: %s", msg.dump().c_str()); + ESP_LOGVV(TAG, "on_authentication_request: %s", msg.dump().c_str()); #endif - this->on_connect_request(msg); + this->on_authentication_request(msg); break; } #endif @@ -588,6 +588,28 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, this->on_bluetooth_scanner_set_mode_request(msg); break; } +#endif +#ifdef USE_ZWAVE_PROXY + case ZWaveProxyFrame::MESSAGE_TYPE: { + ZWaveProxyFrame msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_z_wave_proxy_frame: %s", msg.dump().c_str()); +#endif + this->on_z_wave_proxy_frame(msg); + break; + } +#endif +#ifdef USE_ZWAVE_PROXY + case ZWaveProxyRequest::MESSAGE_TYPE: { + ZWaveProxyRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_z_wave_proxy_request: %s", msg.dump().c_str()); +#endif + this->on_z_wave_proxy_request(msg); + break; + } #endif default: break; @@ -600,8 +622,8 @@ void APIServerConnection::on_hello_request(const HelloRequest &msg) { } } #ifdef USE_API_PASSWORD -void APIServerConnection::on_connect_request(const ConnectRequest &msg) { - if (!this->send_connect_response(msg)) { +void APIServerConnection::on_authentication_request(const AuthenticationRequest &msg) { + if (!this->send_authenticate_response(msg)) { this->on_fatal_error(); } } @@ -899,5 +921,19 @@ void APIServerConnection::on_alarm_control_panel_command_request(const AlarmCont } } #endif +#ifdef USE_ZWAVE_PROXY +void APIServerConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) { + if (this->check_authenticated_()) { + this->zwave_proxy_frame(msg); + } +} +#endif +#ifdef USE_ZWAVE_PROXY +void APIServerConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) { + if (this->check_authenticated_()) { + this->zwave_proxy_request(msg); + } +} +#endif } // namespace esphome::api diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index cc7604c0837..9379dfee7de 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -27,7 +27,7 @@ class APIServerConnectionBase : public ProtoService { virtual void on_hello_request(const HelloRequest &value){}; #ifdef USE_API_PASSWORD - virtual void on_connect_request(const ConnectRequest &value){}; + virtual void on_authentication_request(const AuthenticationRequest &value){}; #endif virtual void on_disconnect_request(const DisconnectRequest &value){}; @@ -207,6 +207,12 @@ class APIServerConnectionBase : public ProtoService { #ifdef USE_UPDATE virtual void on_update_command_request(const UpdateCommandRequest &value){}; +#endif +#ifdef USE_ZWAVE_PROXY + virtual void on_z_wave_proxy_frame(const ZWaveProxyFrame &value){}; +#endif +#ifdef USE_ZWAVE_PROXY + virtual void on_z_wave_proxy_request(const ZWaveProxyRequest &value){}; #endif protected: void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; @@ -216,7 +222,7 @@ class APIServerConnection : public APIServerConnectionBase { public: virtual bool send_hello_response(const HelloRequest &msg) = 0; #ifdef USE_API_PASSWORD - virtual bool send_connect_response(const ConnectRequest &msg) = 0; + virtual bool send_authenticate_response(const AuthenticationRequest &msg) = 0; #endif virtual bool send_disconnect_response(const DisconnectRequest &msg) = 0; virtual bool send_ping_response(const PingRequest &msg) = 0; @@ -335,11 +341,17 @@ class APIServerConnection : public APIServerConnectionBase { #endif #ifdef USE_ALARM_CONTROL_PANEL virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0; +#endif +#ifdef USE_ZWAVE_PROXY + virtual void zwave_proxy_frame(const ZWaveProxyFrame &msg) = 0; +#endif +#ifdef USE_ZWAVE_PROXY + virtual void zwave_proxy_request(const ZWaveProxyRequest &msg) = 0; #endif protected: void on_hello_request(const HelloRequest &msg) override; #ifdef USE_API_PASSWORD - void on_connect_request(const ConnectRequest &msg) override; + void on_authentication_request(const AuthenticationRequest &msg) override; #endif void on_disconnect_request(const DisconnectRequest &msg) override; void on_ping_request(const PingRequest &msg) override; @@ -459,6 +471,12 @@ class APIServerConnection : public APIServerConnectionBase { #ifdef USE_ALARM_CONTROL_PANEL void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override; #endif +#ifdef USE_ZWAVE_PROXY + void on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) override; +#endif +#ifdef USE_ZWAVE_PROXY + void on_z_wave_proxy_request(const ZWaveProxyRequest &msg) override; +#endif }; } // namespace esphome::api diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py index 39cafc7cb4e..9f2af0a230e 100644 --- a/esphome/components/captive_portal/__init__.py +++ b/esphome/components/captive_portal/__init__.py @@ -10,7 +10,8 @@ from esphome.const import ( PLATFORM_LN882X, PLATFORM_RTL87XX, ) -from esphome.core import CORE, CoroPriority, coroutine_with_priority +from esphome.core import CORE, coroutine_with_priority +from esphome.coroutine import CoroPriority AUTO_LOAD = ["web_server_base", "ota.web_server"] DEPENDENCIES = ["wifi"] @@ -40,7 +41,7 @@ CONFIG_SCHEMA = cv.All( ) -@coroutine_with_priority(CoroPriority.COMMUNICATION) +@coroutine_with_priority(CoroPriority.CAPTIVE_PORTAL) async def to_code(config): paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID]) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 50a47765bf7..1a2cbd9dafa 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -36,7 +36,6 @@ from esphome.const import ( __version__, ) from esphome.core import CORE, HexInt, TimePeriod -from esphome.cpp_generator import RawExpression import esphome.final_validate as fv from esphome.helpers import copy_file_if_changed, mkdir_p, write_file_if_changed from esphome.types import ConfigType @@ -157,8 +156,6 @@ def set_core_data(config): conf = config[CONF_FRAMEWORK] if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF: CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "esp-idf" - CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] = {} - CORE.data[KEY_ESP32][KEY_COMPONENTS] = {} elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO: CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino" if variant not in ARDUINO_ALLOWED_VARIANTS: @@ -166,6 +163,8 @@ def set_core_data(config): f"ESPHome does not support using the Arduino framework for the {variant}. Please use the ESP-IDF framework instead.", path=[CONF_FRAMEWORK, CONF_TYPE], ) + CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] = {} + CORE.data[KEY_ESP32][KEY_COMPONENTS] = {} CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse( config[CONF_FRAMEWORK][CONF_VERSION] ) @@ -236,8 +235,6 @@ SdkconfigValueType = bool | int | HexInt | str | RawSdkconfigValue def add_idf_sdkconfig_option(name: str, value: SdkconfigValueType): """Set an esp-idf sdkconfig value.""" - if not CORE.using_esp_idf: - raise ValueError("Not an esp-idf project") CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS][name] = value @@ -252,8 +249,6 @@ def add_idf_component( submodules: list[str] | None = None, ): """Add an esp-idf component to the project.""" - if not CORE.using_esp_idf: - raise ValueError("Not an esp-idf project") if not repo and not ref and not path: raise ValueError("Requires at least one of repo, ref or path") if refresh or submodules or components: @@ -367,47 +362,49 @@ SUPPORTED_PIOARDUINO_ESP_IDF_5X = [ ] -def _arduino_check_versions(value): +def _check_versions(value): value = value.copy() - lookups = { - "dev": (cv.Version(3, 2, 1), "https://github.com/espressif/arduino-esp32.git"), - "latest": (cv.Version(3, 2, 1), None), - "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), - } + if value[CONF_TYPE] == FRAMEWORK_ARDUINO: + lookups = { + "dev": ( + cv.Version(3, 2, 1), + "https://github.com/espressif/arduino-esp32.git", + ), + "latest": (cv.Version(3, 2, 1), None), + "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), + } - if value[CONF_VERSION] in lookups: - if CONF_SOURCE in value: - raise cv.Invalid( - "Framework version needs to be explicitly specified when custom source is used." - ) + if value[CONF_VERSION] in lookups: + if CONF_SOURCE in value: + raise cv.Invalid( + "Framework version needs to be explicitly specified when custom source is used." + ) - version, source = lookups[value[CONF_VERSION]] - else: - version = cv.Version.parse(cv.version_number(value[CONF_VERSION])) - source = value.get(CONF_SOURCE, None) + version, source = lookups[value[CONF_VERSION]] + else: + version = cv.Version.parse(cv.version_number(value[CONF_VERSION])) + source = value.get(CONF_SOURCE, None) - value[CONF_VERSION] = str(version) - value[CONF_SOURCE] = source or _format_framework_arduino_version(version) + value[CONF_VERSION] = str(version) + value[CONF_SOURCE] = source or _format_framework_arduino_version(version) - value[CONF_PLATFORM_VERSION] = value.get( - CONF_PLATFORM_VERSION, _parse_platform_version(str(ARDUINO_PLATFORM_VERSION)) - ) - - if value[CONF_SOURCE].startswith("http"): - # prefix is necessary or platformio will complain with a cryptic error - value[CONF_SOURCE] = f"framework-arduinoespressif32@{value[CONF_SOURCE]}" - - if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: - _LOGGER.warning( - "The selected Arduino framework version is not the recommended one. " - "If there are connectivity or build issues please remove the manual version." + value[CONF_PLATFORM_VERSION] = value.get( + CONF_PLATFORM_VERSION, + _parse_platform_version(str(ARDUINO_PLATFORM_VERSION)), ) - return value + if value[CONF_SOURCE].startswith("http"): + # prefix is necessary or platformio will complain with a cryptic error + value[CONF_SOURCE] = f"framework-arduinoespressif32@{value[CONF_SOURCE]}" + if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: + _LOGGER.warning( + "The selected Arduino framework version is not the recommended one. " + "If there are connectivity or build issues please remove the manual version." + ) + + return value -def _esp_idf_check_versions(value): - value = value.copy() lookups = { "dev": (cv.Version(5, 4, 2), "https://github.com/espressif/esp-idf.git"), "latest": (cv.Version(5, 2, 2), None), @@ -589,24 +586,6 @@ def final_validate(config): return config -ARDUINO_FRAMEWORK_SCHEMA = cv.All( - cv.Schema( - { - cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, - cv.Optional(CONF_SOURCE): cv.string_strict, - cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version, - cv.Optional(CONF_ADVANCED, default={}): cv.Schema( - { - cv.Optional( - CONF_IGNORE_EFUSE_CUSTOM_MAC, default=False - ): cv.boolean, - } - ), - } - ), - _arduino_check_versions, -) - CONF_SDKCONFIG_OPTIONS = "sdkconfig_options" CONF_ENABLE_LWIP_DHCP_SERVER = "enable_lwip_dhcp_server" CONF_ENABLE_LWIP_MDNS_QUERIES = "enable_lwip_mdns_queries" @@ -625,9 +604,14 @@ def _validate_idf_component(config: ConfigType) -> ConfigType: return config -ESP_IDF_FRAMEWORK_SCHEMA = cv.All( +FRAMEWORK_ESP_IDF = "esp-idf" +FRAMEWORK_ARDUINO = "arduino" +FRAMEWORK_SCHEMA = cv.All( cv.Schema( { + cv.Optional(CONF_TYPE, default=FRAMEWORK_ARDUINO): cv.one_of( + FRAMEWORK_ESP_IDF, FRAMEWORK_ARDUINO + ), cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, cv.Optional(CONF_RELEASE): cv.string_strict, cv.Optional(CONF_SOURCE): cv.string_strict, @@ -691,7 +675,7 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All( ), } ), - _esp_idf_check_versions, + _check_versions, ) @@ -758,32 +742,18 @@ def _set_default_framework(config): config = config.copy() variant = config[CONF_VARIANT] + config[CONF_FRAMEWORK] = FRAMEWORK_SCHEMA({}) if variant in ARDUINO_ALLOWED_VARIANTS: - config[CONF_FRAMEWORK] = ARDUINO_FRAMEWORK_SCHEMA({}) config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ARDUINO - # Show the migration message _show_framework_migration_message( config.get(CONF_NAME, "This device"), variant ) else: - config[CONF_FRAMEWORK] = ESP_IDF_FRAMEWORK_SCHEMA({}) config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ESP_IDF return config -FRAMEWORK_ESP_IDF = "esp-idf" -FRAMEWORK_ARDUINO = "arduino" -FRAMEWORK_SCHEMA = cv.typed_schema( - { - FRAMEWORK_ESP_IDF: ESP_IDF_FRAMEWORK_SCHEMA, - FRAMEWORK_ARDUINO: ARDUINO_FRAMEWORK_SCHEMA, - }, - lower=True, - space="-", -) - - FLASH_SIZES = [ "2MB", "4MB", @@ -851,139 +821,145 @@ async def to_code(config): os.path.join(os.path.dirname(__file__), "post_build.py.script"), ) - freq = config[CONF_CPU_FREQUENCY][:-3] if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF: cg.add_platformio_option("framework", "espidf") cg.add_build_flag("-DUSE_ESP_IDF") cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF") - cg.add_build_flag("-Wno-nonnull-compare") - - cg.add_platformio_option("platform_packages", [conf[CONF_SOURCE]]) - - add_idf_sdkconfig_option(f"CONFIG_IDF_TARGET_{variant}", True) - add_idf_sdkconfig_option( - f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True - ) - add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False) - add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True) - add_idf_sdkconfig_option( - "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME", "partitions.csv" - ) - - # Increase freertos tick speed from 100Hz to 1kHz so that delay() resolution is 1ms - add_idf_sdkconfig_option("CONFIG_FREERTOS_HZ", 1000) - - # Setup watchdog - add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT", True) - add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_PANIC", True) - add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0", False) - add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1", False) - - # Disable dynamic log level control to save memory - add_idf_sdkconfig_option("CONFIG_LOG_DYNAMIC_LEVEL_CONTROL", False) - - # Set default CPU frequency - add_idf_sdkconfig_option(f"CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_{freq}", True) - - # Apply LWIP optimization settings - advanced = conf[CONF_ADVANCED] - # DHCP server: only disable if explicitly set to false - # WiFi component handles its own optimization when AP mode is not used - if ( - CONF_ENABLE_LWIP_DHCP_SERVER in advanced - and not advanced[CONF_ENABLE_LWIP_DHCP_SERVER] - ): - add_idf_sdkconfig_option("CONFIG_LWIP_DHCPS", False) - if not advanced.get(CONF_ENABLE_LWIP_MDNS_QUERIES, True): - add_idf_sdkconfig_option("CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES", False) - if not advanced.get(CONF_ENABLE_LWIP_BRIDGE_INTERFACE, False): - add_idf_sdkconfig_option("CONFIG_LWIP_BRIDGEIF_MAX_PORTS", 0) - if advanced.get(CONF_EXECUTE_FROM_PSRAM, False): - add_idf_sdkconfig_option("CONFIG_SPIRAM_FETCH_INSTRUCTIONS", True) - add_idf_sdkconfig_option("CONFIG_SPIRAM_RODATA", True) - - # Apply LWIP core locking for better socket performance - # This is already enabled by default in Arduino framework, where it provides - # significant performance benefits. Our benchmarks show socket operations are - # 24-200% faster with core locking enabled: - # - select() on 4 sockets: ~190μs (Arduino/core locking) vs ~235μs (ESP-IDF default) - # - Up to 200% slower under load when all operations queue through tcpip_thread - # Enabling this makes ESP-IDF socket performance match Arduino framework. - if advanced.get(CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING, True): - add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_CORE_LOCKING", True) - if advanced.get(CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY, True): - add_idf_sdkconfig_option("CONFIG_LWIP_CHECK_THREAD_SAFETY", True) - - cg.add_platformio_option("board_build.partitions", "partitions.csv") - if CONF_PARTITIONS in config: - add_extra_build_file( - "partitions.csv", CORE.relative_config_path(config[CONF_PARTITIONS]) - ) - - if assertion_level := advanced.get(CONF_ASSERTION_LEVEL): - for key, flag in ASSERTION_LEVELS.items(): - add_idf_sdkconfig_option(flag, assertion_level == key) - - add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False) - compiler_optimization = advanced.get(CONF_COMPILER_OPTIMIZATION) - for key, flag in COMPILER_OPTIMIZATIONS.items(): - add_idf_sdkconfig_option(flag, compiler_optimization == key) - - add_idf_sdkconfig_option( - "CONFIG_LWIP_ESP_LWIP_ASSERT", - conf[CONF_ADVANCED][CONF_ENABLE_LWIP_ASSERT], - ) - - if advanced.get(CONF_IGNORE_EFUSE_MAC_CRC): - add_idf_sdkconfig_option("CONFIG_ESP_MAC_IGNORE_MAC_CRC_ERROR", True) - add_idf_sdkconfig_option( - "CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE", False - ) - if advanced.get(CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES): - _LOGGER.warning( - "Using experimental features in ESP-IDF may result in unexpected failures." - ) - add_idf_sdkconfig_option("CONFIG_IDF_EXPERIMENTAL_FEATURES", True) - - cg.add_define( - "USE_ESP_IDF_VERSION_CODE", - cg.RawExpression( - f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})" - ), - ) - - add_idf_sdkconfig_option( - f"CONFIG_LOG_DEFAULT_LEVEL_{conf[CONF_LOG_LEVEL]}", True - ) - - for name, value in conf[CONF_SDKCONFIG_OPTIONS].items(): - add_idf_sdkconfig_option(name, RawSdkconfigValue(value)) - - for component in conf[CONF_COMPONENTS]: - add_idf_component( - name=component[CONF_NAME], - repo=component.get(CONF_SOURCE), - ref=component.get(CONF_REF), - path=component.get(CONF_PATH), - ) - elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO: - cg.add_platformio_option("framework", "arduino") + else: + cg.add_platformio_option("framework", "arduino, espidf") cg.add_build_flag("-DUSE_ARDUINO") cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO") - cg.add_platformio_option("platform_packages", [conf[CONF_SOURCE]]) - - if CONF_PARTITIONS in config: - cg.add_platformio_option("board_build.partitions", config[CONF_PARTITIONS]) - else: - cg.add_platformio_option("board_build.partitions", "partitions.csv") - + cg.add_platformio_option( + "board_build.embed_txtfiles", + [ + "managed_components/espressif__esp_insights/server_certs/https_server.crt", + "managed_components/espressif__esp_rainmaker/server_certs/rmaker_mqtt_server.crt", + "managed_components/espressif__esp_rainmaker/server_certs/rmaker_claim_service_server.crt", + "managed_components/espressif__esp_rainmaker/server_certs/rmaker_ota_server.crt", + ], + ) cg.add_define( "USE_ARDUINO_VERSION_CODE", cg.RawExpression( f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})" ), ) - cg.add(RawExpression(f"setCpuFrequencyMhz({freq})")) + add_idf_sdkconfig_option("CONFIG_AUTOSTART_ARDUINO", True) + add_idf_sdkconfig_option("CONFIG_MBEDTLS_PSK_MODES", True) + add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True) + + cg.add_build_flag("-Wno-nonnull-compare") + + cg.add_platformio_option("platform_packages", [conf[CONF_SOURCE]]) + + add_idf_sdkconfig_option(f"CONFIG_IDF_TARGET_{variant}", True) + add_idf_sdkconfig_option( + f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True + ) + add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False) + add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True) + add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM_FILENAME", "partitions.csv") + + # Increase freertos tick speed from 100Hz to 1kHz so that delay() resolution is 1ms + add_idf_sdkconfig_option("CONFIG_FREERTOS_HZ", 1000) + + # Setup watchdog + add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT", True) + add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_PANIC", True) + add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0", False) + add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1", False) + + # Disable dynamic log level control to save memory + add_idf_sdkconfig_option("CONFIG_LOG_DYNAMIC_LEVEL_CONTROL", False) + + # Set default CPU frequency + add_idf_sdkconfig_option( + f"CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_{config[CONF_CPU_FREQUENCY][:-3]}", True + ) + + # Apply LWIP optimization settings + advanced = conf[CONF_ADVANCED] + # DHCP server: only disable if explicitly set to false + # WiFi component handles its own optimization when AP mode is not used + # When using Arduino with Ethernet, DHCP server functions must be available + # for the Network library to compile, even if not actively used + if ( + CONF_ENABLE_LWIP_DHCP_SERVER in advanced + and not advanced[CONF_ENABLE_LWIP_DHCP_SERVER] + and not ( + conf[CONF_TYPE] == FRAMEWORK_ARDUINO + and "ethernet" in CORE.loaded_integrations + ) + ): + add_idf_sdkconfig_option("CONFIG_LWIP_DHCPS", False) + if not advanced.get(CONF_ENABLE_LWIP_MDNS_QUERIES, True): + add_idf_sdkconfig_option("CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES", False) + if not advanced.get(CONF_ENABLE_LWIP_BRIDGE_INTERFACE, False): + add_idf_sdkconfig_option("CONFIG_LWIP_BRIDGEIF_MAX_PORTS", 0) + if advanced.get(CONF_EXECUTE_FROM_PSRAM, False): + add_idf_sdkconfig_option("CONFIG_SPIRAM_FETCH_INSTRUCTIONS", True) + add_idf_sdkconfig_option("CONFIG_SPIRAM_RODATA", True) + + # Apply LWIP core locking for better socket performance + # This is already enabled by default in Arduino framework, where it provides + # significant performance benefits. Our benchmarks show socket operations are + # 24-200% faster with core locking enabled: + # - select() on 4 sockets: ~190μs (Arduino/core locking) vs ~235μs (ESP-IDF default) + # - Up to 200% slower under load when all operations queue through tcpip_thread + # Enabling this makes ESP-IDF socket performance match Arduino framework. + if advanced.get(CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING, True): + add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_CORE_LOCKING", True) + if advanced.get(CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY, True): + add_idf_sdkconfig_option("CONFIG_LWIP_CHECK_THREAD_SAFETY", True) + + cg.add_platformio_option("board_build.partitions", "partitions.csv") + if CONF_PARTITIONS in config: + add_extra_build_file( + "partitions.csv", CORE.relative_config_path(config[CONF_PARTITIONS]) + ) + + if assertion_level := advanced.get(CONF_ASSERTION_LEVEL): + for key, flag in ASSERTION_LEVELS.items(): + add_idf_sdkconfig_option(flag, assertion_level == key) + + add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False) + compiler_optimization = advanced.get(CONF_COMPILER_OPTIMIZATION) + for key, flag in COMPILER_OPTIMIZATIONS.items(): + add_idf_sdkconfig_option(flag, compiler_optimization == key) + + add_idf_sdkconfig_option( + "CONFIG_LWIP_ESP_LWIP_ASSERT", + conf[CONF_ADVANCED][CONF_ENABLE_LWIP_ASSERT], + ) + + if advanced.get(CONF_IGNORE_EFUSE_MAC_CRC): + add_idf_sdkconfig_option("CONFIG_ESP_MAC_IGNORE_MAC_CRC_ERROR", True) + add_idf_sdkconfig_option("CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE", False) + if advanced.get(CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES): + _LOGGER.warning( + "Using experimental features in ESP-IDF may result in unexpected failures." + ) + add_idf_sdkconfig_option("CONFIG_IDF_EXPERIMENTAL_FEATURES", True) + + cg.add_define( + "USE_ESP_IDF_VERSION_CODE", + cg.RawExpression( + f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})" + ), + ) + + add_idf_sdkconfig_option(f"CONFIG_LOG_DEFAULT_LEVEL_{conf[CONF_LOG_LEVEL]}", True) + + for name, value in conf[CONF_SDKCONFIG_OPTIONS].items(): + add_idf_sdkconfig_option(name, RawSdkconfigValue(value)) + + for component in conf[CONF_COMPONENTS]: + add_idf_component( + name=component[CONF_NAME], + repo=component.get(CONF_SOURCE), + ref=component.get(CONF_REF), + path=component.get(CONF_PATH), + ) APP_PARTITION_SIZES = { @@ -1057,6 +1033,7 @@ def _write_sdkconfig(): ) + "\n" ) + if write_file_if_changed(internal_path, contents): # internal changed, update real one write_file_if_changed(sdk_path, contents) @@ -1088,34 +1065,32 @@ def _write_idf_component_yml(): # Called by writer.py def copy_files(): - if ( - CORE.using_arduino - and "partitions.csv" not in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES] - ): - write_file_if_changed( - CORE.relative_build_path("partitions.csv"), - get_arduino_partition_csv( - CORE.platformio_options.get("board_upload.flash_size") - ), - ) - if CORE.using_esp_idf: - _write_sdkconfig() - _write_idf_component_yml() - if "partitions.csv" not in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES]: + _write_sdkconfig() + _write_idf_component_yml() + + if "partitions.csv" not in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES]: + if CORE.using_arduino: + write_file_if_changed( + CORE.relative_build_path("partitions.csv"), + get_arduino_partition_csv( + CORE.platformio_options.get("board_upload.flash_size") + ), + ) + else: write_file_if_changed( CORE.relative_build_path("partitions.csv"), get_idf_partition_csv( CORE.platformio_options.get("board_upload.flash_size") ), ) - # IDF build scripts look for version string to put in the build. - # However, if the build path does not have an initialized git repo, - # and no version.txt file exists, the CMake script fails for some setups. - # Fix by manually pasting a version.txt file, containing the ESPHome version - write_file_if_changed( - CORE.relative_build_path("version.txt"), - __version__, - ) + # IDF build scripts look for version string to put in the build. + # However, if the build path does not have an initialized git repo, + # and no version.txt file exists, the CMake script fails for some setups. + # Fix by manually pasting a version.txt file, containing the ESPHome version + write_file_if_changed( + CORE.relative_build_path("version.txt"), + __version__, + ) for file in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES].values(): if file[KEY_PATH].startswith("http"): diff --git a/esphome/components/esp32/preferences.cpp b/esphome/components/esp32/preferences.cpp index c5b07b497c1..7bdbb265ca5 100644 --- a/esphome/components/esp32/preferences.cpp +++ b/esphome/components/esp32/preferences.cpp @@ -17,7 +17,14 @@ static const char *const TAG = "esp32.preferences"; struct NVSData { std::string key; - std::vector data; + std::unique_ptr data; + size_t len; + + void set_data(const uint8_t *src, size_t size) { + data = std::make_unique(size); + memcpy(data.get(), src, size); + len = size; + } }; static std::vector s_pending_save; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) @@ -30,26 +37,26 @@ class ESP32PreferenceBackend : public ESPPreferenceBackend { // try find in pending saves and update that for (auto &obj : s_pending_save) { if (obj.key == key) { - obj.data.assign(data, data + len); + obj.set_data(data, len); return true; } } NVSData save{}; save.key = key; - save.data.assign(data, data + len); - s_pending_save.emplace_back(save); - ESP_LOGVV(TAG, "s_pending_save: key: %s, len: %d", key.c_str(), len); + save.set_data(data, len); + s_pending_save.emplace_back(std::move(save)); + ESP_LOGVV(TAG, "s_pending_save: key: %s, len: %zu", key.c_str(), len); return true; } bool load(uint8_t *data, size_t len) override { // try find in pending saves and load from that for (auto &obj : s_pending_save) { if (obj.key == key) { - if (obj.data.size() != len) { + if (obj.len != len) { // size mismatch return false; } - memcpy(data, obj.data.data(), len); + memcpy(data, obj.data.get(), len); return true; } } @@ -61,7 +68,7 @@ class ESP32PreferenceBackend : public ESPPreferenceBackend { return false; } if (actual_len != len) { - ESP_LOGVV(TAG, "NVS length does not match (%u!=%u)", actual_len, len); + ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len); return false; } err = nvs_get_blob(nvs_handle, key.c_str(), data, &len); @@ -69,7 +76,7 @@ class ESP32PreferenceBackend : public ESPPreferenceBackend { ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key.c_str(), esp_err_to_name(err)); return false; } else { - ESP_LOGVV(TAG, "nvs_get_blob: key: %s, len: %d", key.c_str(), len); + ESP_LOGVV(TAG, "nvs_get_blob: key: %s, len: %zu", key.c_str(), len); } return true; } @@ -112,7 +119,7 @@ class ESP32Preferences : public ESPPreferences { if (s_pending_save.empty()) return true; - ESP_LOGV(TAG, "Saving %d items...", s_pending_save.size()); + ESP_LOGV(TAG, "Saving %zu items...", s_pending_save.size()); // goal try write all pending saves even if one fails int cached = 0, written = 0, failed = 0; esp_err_t last_err = ESP_OK; @@ -123,11 +130,10 @@ class ESP32Preferences : public ESPPreferences { const auto &save = s_pending_save[i]; ESP_LOGVV(TAG, "Checking if NVS data %s has changed", save.key.c_str()); if (is_changed(nvs_handle, save)) { - esp_err_t err = nvs_set_blob(nvs_handle, save.key.c_str(), save.data.data(), save.data.size()); - ESP_LOGV(TAG, "sync: key: %s, len: %d", save.key.c_str(), save.data.size()); + esp_err_t err = nvs_set_blob(nvs_handle, save.key.c_str(), save.data.get(), save.len); + ESP_LOGV(TAG, "sync: key: %s, len: %zu", save.key.c_str(), save.len); if (err != 0) { - ESP_LOGV(TAG, "nvs_set_blob('%s', len=%u) failed: %s", save.key.c_str(), save.data.size(), - esp_err_to_name(err)); + ESP_LOGV(TAG, "nvs_set_blob('%s', len=%zu) failed: %s", save.key.c_str(), save.len, esp_err_to_name(err)); failed++; last_err = err; last_key = save.key; @@ -135,7 +141,7 @@ class ESP32Preferences : public ESPPreferences { } written++; } else { - ESP_LOGV(TAG, "NVS data not changed skipping %s len=%u", save.key.c_str(), save.data.size()); + ESP_LOGV(TAG, "NVS data not changed skipping %s len=%zu", save.key.c_str(), save.len); cached++; } s_pending_save.erase(s_pending_save.begin() + i); @@ -164,7 +170,7 @@ class ESP32Preferences : public ESPPreferences { return true; } // Check size first before allocating memory - if (actual_len != to_save.data.size()) { + if (actual_len != to_save.len) { return true; } auto stored_data = std::make_unique(actual_len); @@ -173,7 +179,7 @@ class ESP32Preferences : public ESPPreferences { ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", to_save.key.c_str(), esp_err_to_name(err)); return true; } - return memcmp(to_save.data.data(), stored_data.get(), to_save.data.size()) != 0; + return memcmp(to_save.data.get(), stored_data.get(), to_save.len) != 0; } bool reset() override { diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index d2eaa3ce6f7..8886dc415b7 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -12,7 +12,7 @@ from esphome.const import ( CONF_NAME, CONF_NAME_ADD_MAC_SUFFIX, ) -from esphome.core import CORE, TimePeriod +from esphome.core import TimePeriod import esphome.final_validate as fv DEPENDENCIES = ["esp32"] @@ -261,43 +261,40 @@ async def to_code(config): cg.add(var.set_name(name)) await cg.register_component(var, config) - if CORE.using_esp_idf: - add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) - add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True) + add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) + add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True) - # Register the core BLE loggers that are always needed - register_bt_logger(BTLoggers.GAP, BTLoggers.BTM, BTLoggers.HCI) + # Register the core BLE loggers that are always needed + register_bt_logger(BTLoggers.GAP, BTLoggers.BTM, BTLoggers.HCI) - # Apply logger settings if log disabling is enabled - if config.get(CONF_DISABLE_BT_LOGS, False): - # Disable all Bluetooth loggers that are not required - for logger in BTLoggers: - if logger not in _required_loggers: - add_idf_sdkconfig_option(f"{logger.value}_NONE", True) + # Apply logger settings if log disabling is enabled + if config.get(CONF_DISABLE_BT_LOGS, False): + # Disable all Bluetooth loggers that are not required + for logger in BTLoggers: + if logger not in _required_loggers: + add_idf_sdkconfig_option(f"{logger.value}_NONE", True) - # Set BLE connection establishment timeout to match aioesphomeapi/bleak-retry-connector - # Default is 20 seconds instead of ESP-IDF's 30 seconds. Because there is no way to - # cancel a BLE connection in progress, when aioesphomeapi times out at 20 seconds, - # the connection slot remains occupied for the remaining time, preventing new connection - # attempts and wasting valuable connection slots. - if CONF_CONNECTION_TIMEOUT in config: - timeout_seconds = int(config[CONF_CONNECTION_TIMEOUT].total_seconds) - add_idf_sdkconfig_option( - "CONFIG_BT_BLE_ESTAB_LINK_CONN_TOUT", timeout_seconds - ) - # Increase GATT client connection retry count for problematic devices - # Default in ESP-IDF is 3, we increase to 10 for better reliability with - # low-power/timing-sensitive devices - add_idf_sdkconfig_option("CONFIG_BT_GATTC_CONNECT_RETRY_COUNT", 10) + # Set BLE connection establishment timeout to match aioesphomeapi/bleak-retry-connector + # Default is 20 seconds instead of ESP-IDF's 30 seconds. Because there is no way to + # cancel a BLE connection in progress, when aioesphomeapi times out at 20 seconds, + # the connection slot remains occupied for the remaining time, preventing new connection + # attempts and wasting valuable connection slots. + if CONF_CONNECTION_TIMEOUT in config: + timeout_seconds = int(config[CONF_CONNECTION_TIMEOUT].total_seconds) + add_idf_sdkconfig_option("CONFIG_BT_BLE_ESTAB_LINK_CONN_TOUT", timeout_seconds) + # Increase GATT client connection retry count for problematic devices + # Default in ESP-IDF is 3, we increase to 10 for better reliability with + # low-power/timing-sensitive devices + add_idf_sdkconfig_option("CONFIG_BT_GATTC_CONNECT_RETRY_COUNT", 10) - # Set the maximum number of notification registrations - # This controls how many BLE characteristics can have notifications enabled - # across all connections for a single GATT client interface - # https://github.com/esphome/issues/issues/6808 - if CONF_MAX_NOTIFICATIONS in config: - add_idf_sdkconfig_option( - "CONFIG_BT_GATTC_NOTIF_REG_MAX", config[CONF_MAX_NOTIFICATIONS] - ) + # Set the maximum number of notification registrations + # This controls how many BLE characteristics can have notifications enabled + # across all connections for a single GATT client interface + # https://github.com/esphome/issues/issues/6808 + if CONF_MAX_NOTIFICATIONS in config: + add_idf_sdkconfig_option( + "CONFIG_BT_GATTC_NOTIF_REG_MAX", config[CONF_MAX_NOTIFICATIONS] + ) cg.add_define("USE_ESP32_BLE") diff --git a/esphome/components/esp32_ble_beacon/__init__.py b/esphome/components/esp32_ble_beacon/__init__.py index 8fc4fe941de..794f5637a41 100644 --- a/esphome/components/esp32_ble_beacon/__init__.py +++ b/esphome/components/esp32_ble_beacon/__init__.py @@ -4,7 +4,7 @@ from esphome.components.esp32 import add_idf_sdkconfig_option from esphome.components.esp32_ble import CONF_BLE_ID import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_TX_POWER, CONF_TYPE, CONF_UUID -from esphome.core import CORE, TimePeriod +from esphome.core import TimePeriod AUTO_LOAD = ["esp32_ble"] DEPENDENCIES = ["esp32"] @@ -86,6 +86,5 @@ async def to_code(config): cg.add_define("USE_ESP32_BLE_ADVERTISING") - if CORE.using_esp_idf: - add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) - add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True) + add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) + add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True) diff --git a/esphome/components/esp32_ble_server/__init__.py b/esphome/components/esp32_ble_server/__init__.py index 8ddb15a7f84..a8bb99b745c 100644 --- a/esphome/components/esp32_ble_server/__init__.py +++ b/esphome/components/esp32_ble_server/__init__.py @@ -573,8 +573,7 @@ async def to_code(config): ) cg.add_define("USE_ESP32_BLE_SERVER") cg.add_define("USE_ESP32_BLE_ADVERTISING") - if CORE.using_esp_idf: - add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) + add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) @automation.register_action( diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index 558143b0075..8655d5a02ab 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -342,19 +342,18 @@ async def to_code(config): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) - if CORE.using_esp_idf: - add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) - if config.get(CONF_SOFTWARE_COEXISTENCE): - add_idf_sdkconfig_option("CONFIG_SW_COEXIST_ENABLE", True) - # https://github.com/espressif/esp-idf/issues/4101 - # https://github.com/espressif/esp-idf/issues/2503 - # Match arduino CONFIG_BTU_TASK_STACK_SIZE - # https://github.com/espressif/arduino-esp32/blob/fd72cf46ad6fc1a6de99c1d83ba8eba17d80a4ee/tools/sdk/esp32/sdkconfig#L1866 - add_idf_sdkconfig_option("CONFIG_BT_BTU_TASK_STACK_SIZE", 8192) - add_idf_sdkconfig_option("CONFIG_BT_ACL_CONNECTIONS", 9) - add_idf_sdkconfig_option( - "CONFIG_BTDM_CTRL_BLE_MAX_CONN", config[CONF_MAX_CONNECTIONS] - ) + add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) + if config.get(CONF_SOFTWARE_COEXISTENCE): + add_idf_sdkconfig_option("CONFIG_SW_COEXIST_ENABLE", True) + # https://github.com/espressif/esp-idf/issues/4101 + # https://github.com/espressif/esp-idf/issues/2503 + # Match arduino CONFIG_BTU_TASK_STACK_SIZE + # https://github.com/espressif/arduino-esp32/blob/fd72cf46ad6fc1a6de99c1d83ba8eba17d80a4ee/tools/sdk/esp32/sdkconfig#L1866 + add_idf_sdkconfig_option("CONFIG_BT_BTU_TASK_STACK_SIZE", 8192) + add_idf_sdkconfig_option("CONFIG_BT_ACL_CONNECTIONS", 9) + add_idf_sdkconfig_option( + "CONFIG_BTDM_CTRL_BLE_MAX_CONN", config[CONF_MAX_CONNECTIONS] + ) cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts cg.add_define("USE_ESP32_BLE_CLIENT") diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index 6206fe46826..d8ba098645e 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -21,7 +21,6 @@ from esphome.const import ( CONF_TRIGGER_ID, CONF_VSYNC_PIN, ) -from esphome.core import CORE from esphome.core.entity_helpers import setup_entity import esphome.final_validate as fv @@ -344,8 +343,7 @@ async def to_code(config): cg.add_define("USE_CAMERA") - if CORE.using_esp_idf: - add_idf_component(name="espressif/esp32-camera", ref="2.1.1") + add_idf_component(name="espressif/esp32-camera", ref="2.1.1") for conf in config.get(CONF_ON_STREAM_START, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) diff --git a/esphome/components/esphome/ota/__init__.py b/esphome/components/esphome/ota/__init__.py index 7b579501ed2..93216f94255 100644 --- a/esphome/components/esphome/ota/__init__.py +++ b/esphome/components/esphome/ota/__init__.py @@ -16,7 +16,8 @@ from esphome.const import ( CONF_SAFE_MODE, CONF_VERSION, ) -from esphome.core import CoroPriority, coroutine_with_priority +from esphome.core import coroutine_with_priority +from esphome.coroutine import CoroPriority import esphome.final_validate as fv _LOGGER = logging.getLogger(__name__) @@ -121,7 +122,7 @@ CONFIG_SCHEMA = ( FINAL_VALIDATE_SCHEMA = ota_esphome_final_validate -@coroutine_with_priority(CoroPriority.COMMUNICATION) +@coroutine_with_priority(CoroPriority.OTA_UPDATES) async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) cg.add(var.set_port(config[CONF_PORT])) diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index 151da7d0e5d..ef8e82a8435 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -117,19 +117,15 @@ ManualIP = ethernet_ns.struct("ManualIP") def _is_framework_spi_polling_mode_supported(): # SPI Ethernet without IRQ feature is added in - # esp-idf >= (5.3+ ,5.2.1+, 5.1.4) and arduino-esp32 >= 3.0.0 + # esp-idf >= (5.3+ ,5.2.1+, 5.1.4) + # Note: Arduino now uses ESP-IDF as a component, so we only check IDF version framework_version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] - if CORE.using_esp_idf: - if framework_version >= cv.Version(5, 3, 0): - return True - if cv.Version(5, 3, 0) > framework_version >= cv.Version(5, 2, 1): - return True - if cv.Version(5, 2, 0) > framework_version >= cv.Version(5, 1, 4): # noqa: SIM103 - return True - return False - if CORE.using_arduino: - return framework_version >= cv.Version(3, 0, 0) - # fail safe: Unknown framework + if framework_version >= cv.Version(5, 3, 0): + return True + if cv.Version(5, 3, 0) > framework_version >= cv.Version(5, 2, 1): + return True + if cv.Version(5, 2, 0) > framework_version >= cv.Version(5, 1, 4): # noqa: SIM103 + return True return False @@ -322,11 +318,8 @@ async def to_code(config): cg.add(var.set_clock_speed(config[CONF_CLOCK_SPEED])) cg.add_define("USE_ETHERNET_SPI") - if CORE.using_esp_idf: - add_idf_sdkconfig_option("CONFIG_ETH_USE_SPI_ETHERNET", True) - add_idf_sdkconfig_option( - f"CONFIG_ETH_SPI_ETHERNET_{config[CONF_TYPE]}", True - ) + add_idf_sdkconfig_option("CONFIG_ETH_USE_SPI_ETHERNET", True) + add_idf_sdkconfig_option(f"CONFIG_ETH_SPI_ETHERNET_{config[CONF_TYPE]}", True) elif config[CONF_TYPE] == "OPENETH": cg.add_define("USE_ETHERNET_OPENETH") add_idf_sdkconfig_option("CONFIG_ETH_USE_OPENETH", True) @@ -359,10 +352,9 @@ async def to_code(config): cg.add_define("USE_ETHERNET") # Disable WiFi when using Ethernet to save memory - if CORE.using_esp_idf: - add_idf_sdkconfig_option("CONFIG_ESP_WIFI_ENABLED", False) - # Also disable WiFi/BT coexistence since WiFi is disabled - add_idf_sdkconfig_option("CONFIG_SW_COEXIST_ENABLE", False) + add_idf_sdkconfig_option("CONFIG_ESP_WIFI_ENABLED", False) + # Also disable WiFi/BT coexistence since WiFi is disabled + add_idf_sdkconfig_option("CONFIG_SW_COEXIST_ENABLE", False) if CORE.using_arduino: cg.add_library("WiFi", None) diff --git a/esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp b/esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp index 45544c185ba..7a355961947 100644 --- a/esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp +++ b/esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp @@ -6,6 +6,7 @@ namespace gpio { static const char *const TAG = "gpio.binary_sensor"; +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG static const LogString *interrupt_type_to_string(gpio::InterruptType type) { switch (type) { case gpio::INTERRUPT_RISING_EDGE: @@ -22,6 +23,7 @@ static const LogString *interrupt_type_to_string(gpio::InterruptType type) { static const LogString *gpio_mode_to_string(bool use_interrupt) { return use_interrupt ? LOG_STR("interrupt") : LOG_STR("polling"); } +#endif void IRAM_ATTR GPIOBinarySensorStore::gpio_intr(GPIOBinarySensorStore *arg) { bool new_state = arg->isr_pin_.digital_read(); diff --git a/esphome/components/http_request/ota/__init__.py b/esphome/components/http_request/ota/__init__.py index fd542e594ae..d2c574d8c61 100644 --- a/esphome/components/http_request/ota/__init__.py +++ b/esphome/components/http_request/ota/__init__.py @@ -3,7 +3,8 @@ import esphome.codegen as cg from esphome.components.ota import BASE_OTA_SCHEMA, OTAComponent, ota_to_code import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_PASSWORD, CONF_URL, CONF_USERNAME -from esphome.core import CoroPriority, coroutine_with_priority +from esphome.core import coroutine_with_priority +from esphome.coroutine import CoroPriority from .. import CONF_HTTP_REQUEST_ID, HttpRequestComponent, http_request_ns @@ -40,7 +41,7 @@ CONFIG_SCHEMA = cv.All( ) -@coroutine_with_priority(CoroPriority.COMMUNICATION) +@coroutine_with_priority(CoroPriority.OTA_UPDATES) async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await ota_to_code(var, config) diff --git a/esphome/components/i2s_audio/__init__.py b/esphome/components/i2s_audio/__init__.py index cff91a546fd..8ceff26d847 100644 --- a/esphome/components/i2s_audio/__init__.py +++ b/esphome/components/i2s_audio/__init__.py @@ -262,8 +262,7 @@ async def to_code(config): cg.add_define("USE_I2S_LEGACY") # Helps avoid callbacks being skipped due to processor load - if CORE.using_esp_idf: - add_idf_sdkconfig_option("CONFIG_I2S_ISR_IRAM_SAFE", True) + add_idf_sdkconfig_option("CONFIG_I2S_ISR_IRAM_SAFE", True) cg.add(var.set_lrclk_pin(config[CONF_I2S_LRCLK_PIN])) if CONF_I2S_BCLK_PIN in config: diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index ae4927828b8..528a155a7ff 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -15,11 +15,10 @@ static const char *const TAG = "improv_serial"; void ImprovSerialComponent::setup() { global_improv_serial_component = this; -#ifdef USE_ARDUINO - this->hw_serial_ = logger::global_logger->get_hw_serial(); -#endif -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 this->uart_num_ = logger::global_logger->get_uart_num(); +#elif defined(USE_ARDUINO) + this->hw_serial_ = logger::global_logger->get_hw_serial(); #endif if (wifi::global_wifi_component->has_sta()) { @@ -34,13 +33,7 @@ void ImprovSerialComponent::dump_config() { ESP_LOGCONFIG(TAG, "Improv Serial:") optional ImprovSerialComponent::read_byte_() { optional byte; uint8_t data = 0; -#ifdef USE_ARDUINO - if (this->hw_serial_->available()) { - this->hw_serial_->readBytes(&data, 1); - byte = data; - } -#endif -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 switch (logger::global_logger->get_uart()) { case logger::UART_SELECTION_UART0: case logger::UART_SELECTION_UART1: @@ -76,16 +69,18 @@ optional ImprovSerialComponent::read_byte_() { default: break; } +#elif defined(USE_ARDUINO) + if (this->hw_serial_->available()) { + this->hw_serial_->readBytes(&data, 1); + byte = data; + } #endif return byte; } void ImprovSerialComponent::write_data_(std::vector &data) { data.push_back('\n'); -#ifdef USE_ARDUINO - this->hw_serial_->write(data.data(), data.size()); -#endif -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 switch (logger::global_logger->get_uart()) { case logger::UART_SELECTION_UART0: case logger::UART_SELECTION_UART1: @@ -112,6 +107,8 @@ void ImprovSerialComponent::write_data_(std::vector &data) { default: break; } +#elif defined(USE_ARDUINO) + this->hw_serial_->write(data.data(), data.size()); #endif } diff --git a/esphome/components/improv_serial/improv_serial_component.h b/esphome/components/improv_serial/improv_serial_component.h index 5d2534c2fcb..c3c9aee24e1 100644 --- a/esphome/components/improv_serial/improv_serial_component.h +++ b/esphome/components/improv_serial/improv_serial_component.h @@ -9,10 +9,7 @@ #include #include -#ifdef USE_ARDUINO -#include -#endif -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \ defined(USE_ESP32_VARIANT_ESP32H2) @@ -22,6 +19,8 @@ #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #include #endif +#elif defined(USE_ARDUINO) +#include #endif namespace esphome { @@ -60,11 +59,10 @@ class ImprovSerialComponent : public Component, public improv_base::ImprovBase { optional read_byte_(); void write_data_(std::vector &data); -#ifdef USE_ARDUINO - Stream *hw_serial_{nullptr}; -#endif -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 uart_port_t uart_num_; +#elif defined(USE_ARDUINO) + Stream *hw_serial_{nullptr}; #endif std::vector rx_buffer_; diff --git a/esphome/components/libretiny/preferences.cpp b/esphome/components/libretiny/preferences.cpp index fc535c99b47..871b186d8eb 100644 --- a/esphome/components/libretiny/preferences.cpp +++ b/esphome/components/libretiny/preferences.cpp @@ -15,7 +15,14 @@ static const char *const TAG = "lt.preferences"; struct NVSData { std::string key; - std::vector data; + std::unique_ptr data; + size_t len; + + void set_data(const uint8_t *src, size_t size) { + data = std::make_unique(size); + memcpy(data.get(), src, size); + len = size; + } }; static std::vector s_pending_save; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) @@ -30,15 +37,15 @@ class LibreTinyPreferenceBackend : public ESPPreferenceBackend { // try find in pending saves and update that for (auto &obj : s_pending_save) { if (obj.key == key) { - obj.data.assign(data, data + len); + obj.set_data(data, len); return true; } } NVSData save{}; save.key = key; - save.data.assign(data, data + len); - s_pending_save.emplace_back(save); - ESP_LOGVV(TAG, "s_pending_save: key: %s, len: %d", key.c_str(), len); + save.set_data(data, len); + s_pending_save.emplace_back(std::move(save)); + ESP_LOGVV(TAG, "s_pending_save: key: %s, len: %zu", key.c_str(), len); return true; } @@ -46,11 +53,11 @@ class LibreTinyPreferenceBackend : public ESPPreferenceBackend { // try find in pending saves and load from that for (auto &obj : s_pending_save) { if (obj.key == key) { - if (obj.data.size() != len) { + if (obj.len != len) { // size mismatch return false; } - memcpy(data, obj.data.data(), len); + memcpy(data, obj.data.get(), len); return true; } } @@ -58,10 +65,10 @@ class LibreTinyPreferenceBackend : public ESPPreferenceBackend { fdb_blob_make(blob, data, len); size_t actual_len = fdb_kv_get_blob(db, key.c_str(), blob); if (actual_len != len) { - ESP_LOGVV(TAG, "NVS length does not match (%u!=%u)", actual_len, len); + ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len); return false; } else { - ESP_LOGVV(TAG, "fdb_kv_get_blob: key: %s, len: %d", key.c_str(), len); + ESP_LOGVV(TAG, "fdb_kv_get_blob: key: %s, len: %zu", key.c_str(), len); } return true; } @@ -101,7 +108,7 @@ class LibreTinyPreferences : public ESPPreferences { if (s_pending_save.empty()) return true; - ESP_LOGV(TAG, "Saving %d items...", s_pending_save.size()); + ESP_LOGV(TAG, "Saving %zu items...", s_pending_save.size()); // goal try write all pending saves even if one fails int cached = 0, written = 0, failed = 0; fdb_err_t last_err = FDB_NO_ERR; @@ -112,11 +119,11 @@ class LibreTinyPreferences : public ESPPreferences { const auto &save = s_pending_save[i]; ESP_LOGVV(TAG, "Checking if FDB data %s has changed", save.key.c_str()); if (is_changed(&db, save)) { - ESP_LOGV(TAG, "sync: key: %s, len: %d", save.key.c_str(), save.data.size()); - fdb_blob_make(&blob, save.data.data(), save.data.size()); + ESP_LOGV(TAG, "sync: key: %s, len: %zu", save.key.c_str(), save.len); + fdb_blob_make(&blob, save.data.get(), save.len); fdb_err_t err = fdb_kv_set_blob(&db, save.key.c_str(), &blob); if (err != FDB_NO_ERR) { - ESP_LOGV(TAG, "fdb_kv_set_blob('%s', len=%u) failed: %d", save.key.c_str(), save.data.size(), err); + ESP_LOGV(TAG, "fdb_kv_set_blob('%s', len=%zu) failed: %d", save.key.c_str(), save.len, err); failed++; last_err = err; last_key = save.key; @@ -124,7 +131,7 @@ class LibreTinyPreferences : public ESPPreferences { } written++; } else { - ESP_LOGD(TAG, "FDB data not changed; skipping %s len=%u", save.key.c_str(), save.data.size()); + ESP_LOGD(TAG, "FDB data not changed; skipping %s len=%zu", save.key.c_str(), save.len); cached++; } s_pending_save.erase(s_pending_save.begin() + i); @@ -147,7 +154,7 @@ class LibreTinyPreferences : public ESPPreferences { } // Check size first - if different, data has changed - if (kv.value_len != to_save.data.size()) { + if (kv.value_len != to_save.len) { return true; } @@ -161,7 +168,7 @@ class LibreTinyPreferences : public ESPPreferences { } // Compare the actual data - return memcmp(to_save.data.data(), stored_data.get(), kv.value_len) != 0; + return memcmp(to_save.data.get(), stored_data.get(), kv.value_len) != 0; } bool reset() override { diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 2865355278c..7d1a591f0ca 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -117,8 +117,6 @@ UART_SELECTION_LIBRETINY = { COMPONENT_RTL87XX: [DEFAULT, UART0, UART1, UART2], } -ESP_ARDUINO_UNSUPPORTED_USB_UARTS = [USB_SERIAL_JTAG] - UART_SELECTION_RP2040 = [USB_CDC, UART0, UART1] UART_SELECTION_NRF52 = [USB_CDC, UART0] @@ -153,13 +151,7 @@ is_log_level = cv.one_of(*LOG_LEVELS, upper=True) def uart_selection(value): if CORE.is_esp32: - if CORE.using_arduino and value.upper() in ESP_ARDUINO_UNSUPPORTED_USB_UARTS: - raise cv.Invalid(f"Arduino framework does not support {value}.") variant = get_esp32_variant() - if CORE.using_esp_idf and variant == VARIANT_ESP32C3 and value == USB_CDC: - raise cv.Invalid( - f"{value} is not supported for variant {variant} when using ESP-IDF." - ) if variant in UART_SELECTION_ESP32: return cv.one_of(*UART_SELECTION_ESP32[variant], upper=True)(value) if CORE.is_esp8266: @@ -226,14 +218,11 @@ CONFIG_SCHEMA = cv.All( esp8266=UART0, esp32=UART0, esp32_s2=USB_CDC, - esp32_s3_arduino=USB_CDC, - esp32_s3_idf=USB_SERIAL_JTAG, - esp32_c3_arduino=USB_CDC, - esp32_c3_idf=USB_SERIAL_JTAG, - esp32_c5_idf=USB_SERIAL_JTAG, - esp32_c6_arduino=USB_CDC, - esp32_c6_idf=USB_SERIAL_JTAG, - esp32_p4_idf=USB_SERIAL_JTAG, + esp32_s3=USB_SERIAL_JTAG, + esp32_c3=USB_SERIAL_JTAG, + esp32_c5=USB_SERIAL_JTAG, + esp32_c6=USB_SERIAL_JTAG, + esp32_p4=USB_SERIAL_JTAG, rp2040=USB_CDC, bk72xx=DEFAULT, ln882x=DEFAULT, @@ -346,15 +335,7 @@ async def to_code(config): if config.get(CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH): cg.add_build_flag("-DUSE_STORE_LOG_STR_IN_FLASH") - if CORE.using_arduino and config[CONF_HARDWARE_UART] == USB_CDC: - cg.add_build_flag("-DARDUINO_USB_CDC_ON_BOOT=1") - if CORE.is_esp32 and get_esp32_variant() in ( - VARIANT_ESP32C3, - VARIANT_ESP32C6, - ): - cg.add_build_flag("-DARDUINO_USB_MODE=1") - - if CORE.using_esp_idf: + if CORE.is_esp32: if config[CONF_HARDWARE_UART] == USB_CDC: add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_CDC", True) elif config[CONF_HARDWARE_UART] == USB_SERIAL_JTAG: diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 5f0e78fc0dc..4a69bd98531 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -173,24 +173,8 @@ void Logger::init_log_buffer(size_t total_buffer_size) { } #endif -#ifndef USE_ZEPHYR -#if defined(USE_LOGGER_USB_CDC) || defined(USE_ESP32) -void Logger::loop() { -#if defined(USE_LOGGER_USB_CDC) && defined(USE_ARDUINO) - if (this->uart_ == UART_SELECTION_USB_CDC) { - static bool opened = false; - if (opened == Serial) { - return; - } - if (false == opened) { - App.schedule_dump_config(); - } - opened = !opened; - } -#endif - this->process_messages_(); -} -#endif +#ifdef USE_ESPHOME_TASK_LOG_BUFFER +void Logger::loop() { this->process_messages_(); } #endif void Logger::process_messages_() { diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index a4cf5e30040..b5fb15d3472 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -16,18 +16,18 @@ #endif #ifdef USE_ARDUINO -#if defined(USE_ESP8266) || defined(USE_ESP32) +#if defined(USE_ESP8266) #include -#endif // USE_ESP8266 || USE_ESP32 +#endif // USE_ESP8266 #ifdef USE_RP2040 #include #include #endif // USE_RP2040 #endif // USE_ARDUINO -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include -#endif // USE_ESP_IDF +#endif // USE_ESP32 #ifdef USE_ZEPHYR #include @@ -110,19 +110,17 @@ class Logger : public Component { #ifdef USE_ESPHOME_TASK_LOG_BUFFER void init_log_buffer(size_t total_buffer_size); #endif -#if defined(USE_LOGGER_USB_CDC) || defined(USE_ESP32) || defined(USE_ZEPHYR) +#if defined(USE_ESPHOME_TASK_LOG_BUFFER) || (defined(USE_ZEPHYR) && defined(USE_LOGGER_USB_CDC)) void loop() override; #endif /// Manually set the baud rate for serial, set to 0 to disable. void set_baud_rate(uint32_t baud_rate); uint32_t get_baud_rate() const { return baud_rate_; } -#ifdef USE_ARDUINO +#if defined(USE_ARDUINO) && !defined(USE_ESP32) Stream *get_hw_serial() const { return hw_serial_; } #endif -#ifdef USE_ESP_IDF - uart_port_t get_uart_num() const { return uart_num_; } -#endif #ifdef USE_ESP32 + uart_port_t get_uart_num() const { return uart_num_; } void create_pthread_key() { pthread_key_create(&log_recursion_key_, nullptr); } #endif #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) @@ -232,7 +230,7 @@ class Logger : public Component { // Group 4-byte aligned members first uint32_t baud_rate_; char *tx_buffer_{nullptr}; -#ifdef USE_ARDUINO +#if defined(USE_ARDUINO) && !defined(USE_ESP32) Stream *hw_serial_{nullptr}; #endif #if defined(USE_ZEPHYR) @@ -246,9 +244,7 @@ class Logger : public Component { // - Main task uses a dedicated member variable for efficiency // - Other tasks use pthread TLS with a dynamically created key via pthread_key_create pthread_key_t log_recursion_key_; // 4 bytes -#endif -#ifdef USE_ESP_IDF - uart_port_t uart_num_; // 4 bytes (enum defaults to int size) + uart_port_t uart_num_; // 4 bytes (enum defaults to int size) #endif // Large objects (internally aligned) @@ -380,15 +376,7 @@ class Logger : public Component { // will be processed on the next main loop iteration since: // - disable_loop() takes effect immediately // - enable_loop_soon_any_context() sets a pending flag that's checked at loop start -#if defined(USE_LOGGER_USB_CDC) && defined(USE_ARDUINO) - // Only disable if not using USB CDC (which needs loop for connection detection) - if (this->uart_ != UART_SELECTION_USB_CDC) { - this->disable_loop(); - } -#else - // No USB CDC support, always safe to disable this->disable_loop(); -#endif } #endif }; diff --git a/esphome/components/logger/logger_esp32.cpp b/esphome/components/logger/logger_esp32.cpp index 6cb57c15407..7fc79e6f541 100644 --- a/esphome/components/logger/logger_esp32.cpp +++ b/esphome/components/logger/logger_esp32.cpp @@ -1,11 +1,8 @@ #ifdef USE_ESP32 #include "logger.h" -#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF) #include -#endif // USE_ESP32_FRAMEWORK_ARDUINO || USE_ESP_IDF -#ifdef USE_ESP_IDF #include #ifdef USE_LOGGER_USB_SERIAL_JTAG @@ -25,16 +22,12 @@ #include #include -#endif // USE_ESP_IDF - #include "esphome/core/log.h" namespace esphome::logger { static const char *const TAG = "logger"; -#ifdef USE_ESP_IDF - #ifdef USE_LOGGER_USB_SERIAL_JTAG static void init_usb_serial_jtag_() { setvbuf(stdin, NULL, _IONBF, 0); // Disable buffering on stdin @@ -89,42 +82,8 @@ void init_uart(uart_port_t uart_num, uint32_t baud_rate, int tx_buffer_size) { uart_driver_install(uart_num, uart_buffer_size, uart_buffer_size, 10, nullptr, 0); } -#endif // USE_ESP_IDF - void Logger::pre_setup() { if (this->baud_rate_ > 0) { -#ifdef USE_ARDUINO - switch (this->uart_) { - case UART_SELECTION_UART0: -#if ARDUINO_USB_CDC_ON_BOOT - this->hw_serial_ = &Serial0; - Serial0.begin(this->baud_rate_); -#else - this->hw_serial_ = &Serial; - Serial.begin(this->baud_rate_); -#endif - break; - case UART_SELECTION_UART1: - this->hw_serial_ = &Serial1; - Serial1.begin(this->baud_rate_); - break; -#ifdef USE_ESP32_VARIANT_ESP32 - case UART_SELECTION_UART2: - this->hw_serial_ = &Serial2; - Serial2.begin(this->baud_rate_); - break; -#endif - -#ifdef USE_LOGGER_USB_CDC - case UART_SELECTION_USB_CDC: - this->hw_serial_ = &Serial; - Serial.begin(this->baud_rate_); - break; -#endif - } -#endif // USE_ARDUINO - -#ifdef USE_ESP_IDF this->uart_num_ = UART_NUM_0; switch (this->uart_) { case UART_SELECTION_UART0: @@ -151,21 +110,17 @@ void Logger::pre_setup() { break; #endif } -#endif // USE_ESP_IDF } global_logger = this; -#if defined(USE_ESP_IDF) || defined(USE_ESP32_FRAMEWORK_ARDUINO) esp_log_set_vprintf(esp_idf_log_vprintf_); if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE) { esp_log_level_set("*", ESP_LOG_VERBOSE); } -#endif // USE_ESP_IDF || USE_ESP32_FRAMEWORK_ARDUINO ESP_LOGI(TAG, "Log initialized"); } -#ifdef USE_ESP_IDF void HOT Logger::write_msg_(const char *msg) { if ( #if defined(USE_LOGGER_USB_CDC) && !defined(USE_LOGGER_USB_SERIAL_JTAG) @@ -186,9 +141,6 @@ void HOT Logger::write_msg_(const char *msg) { uart_write_bytes(this->uart_num_, "\n", 1); } } -#else -void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); } -#endif const LogString *Logger::get_uart_selection_() { switch (this->uart_) { diff --git a/esphome/components/logger/logger_zephyr.cpp b/esphome/components/logger/logger_zephyr.cpp index 817ca168f81..fb0c7dcca37 100644 --- a/esphome/components/logger/logger_zephyr.cpp +++ b/esphome/components/logger/logger_zephyr.cpp @@ -12,8 +12,8 @@ namespace esphome::logger { static const char *const TAG = "logger"; -void Logger::loop() { #ifdef USE_LOGGER_USB_CDC +void Logger::loop() { if (this->uart_ != UART_SELECTION_USB_CDC || nullptr == this->uart_dev_) { return; } @@ -30,9 +30,8 @@ void Logger::loop() { App.schedule_dump_config(); } opened = !opened; -#endif - this->process_messages_(); } +#endif void Logger::pre_setup() { if (this->baud_rate_ > 0) { diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py index a21ef9d97ba..a84fe5a2491 100644 --- a/esphome/components/mdns/__init__.py +++ b/esphome/components/mdns/__init__.py @@ -11,7 +11,8 @@ from esphome.const import ( CONF_SERVICES, PlatformFramework, ) -from esphome.core import CORE, CoroPriority, coroutine_with_priority +from esphome.core import CORE, coroutine_with_priority +from esphome.coroutine import CoroPriority CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network"] @@ -72,7 +73,7 @@ def mdns_service( ) -@coroutine_with_priority(CoroPriority.COMMUNICATION) +@coroutine_with_priority(CoroPriority.NETWORK_SERVICES) async def to_code(config): if config[CONF_DISABLED] is True: return diff --git a/esphome/components/network/__init__.py b/esphome/components/network/__init__.py index 9679961b155..1a74350c4c6 100644 --- a/esphome/components/network/__init__.py +++ b/esphome/components/network/__init__.py @@ -47,9 +47,13 @@ async def to_code(config): cg.add_define( "USE_NETWORK_MIN_IPV6_ADDR_COUNT", config[CONF_MIN_IPV6_ADDR_COUNT] ) - if CORE.using_esp_idf: - add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", enable_ipv6) - add_idf_sdkconfig_option("CONFIG_LWIP_IPV6_AUTOCONFIG", enable_ipv6) + if CORE.is_esp32: + if CORE.using_esp_idf: + add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", enable_ipv6) + add_idf_sdkconfig_option("CONFIG_LWIP_IPV6_AUTOCONFIG", enable_ipv6) + else: + add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", True) + add_idf_sdkconfig_option("CONFIG_LWIP_IPV6_AUTOCONFIG", True) elif enable_ipv6: cg.add_build_flag("-DCONFIG_LWIP_IPV6") cg.add_build_flag("-DCONFIG_LWIP_IPV6_AUTOCONFIG") diff --git a/esphome/components/nextion/display.py b/esphome/components/nextion/display.py index 4254ae45fe4..ed6cd930277 100644 --- a/esphome/components/nextion/display.py +++ b/esphome/components/nextion/display.py @@ -153,10 +153,10 @@ async def to_code(config): if CONF_TFT_URL in config: cg.add_define("USE_NEXTION_TFT_UPLOAD") cg.add(var.set_tft_url(config[CONF_TFT_URL])) - if CORE.is_esp32 and CORE.using_arduino: - cg.add_library("NetworkClientSecure", None) - cg.add_library("HTTPClient", None) - elif CORE.is_esp32 and CORE.using_esp_idf: + if CORE.is_esp32: + if CORE.using_arduino: + cg.add_library("NetworkClientSecure", None) + cg.add_library("HTTPClient", None) esp32.add_idf_sdkconfig_option("CONFIG_ESP_TLS_INSECURE", True) esp32.add_idf_sdkconfig_option( "CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY", True diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index cf814fb1eee..eec39668db6 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -10,7 +10,8 @@ from esphome.const import ( CONF_TRIGGER_ID, PlatformFramework, ) -from esphome.core import CORE, CoroPriority, coroutine_with_priority +from esphome.core import CORE, coroutine_with_priority +from esphome.coroutine import CoroPriority CODEOWNERS = ["@esphome/core"] AUTO_LOAD = ["md5", "safe_mode"] @@ -82,7 +83,7 @@ BASE_OTA_SCHEMA = cv.Schema( ) -@coroutine_with_priority(CoroPriority.COMMUNICATION) +@coroutine_with_priority(CoroPriority.OTA_UPDATES) async def to_code(config): cg.add_define("USE_OTA") diff --git a/esphome/components/packet_transport/__init__.py b/esphome/components/packet_transport/__init__.py index bfb2bbc4f82..43da7740fec 100644 --- a/esphome/components/packet_transport/__init__.py +++ b/esphome/components/packet_transport/__init__.py @@ -121,15 +121,11 @@ def transport_schema(cls): return TRANSPORT_SCHEMA.extend({cv.GenerateID(): cv.declare_id(cls)}) -# Build a list of sensors for this platform -CORE.data[DOMAIN] = {CONF_SENSORS: []} - - def get_sensors(transport_id): """Return the list of sensors for this platform.""" return ( sensor - for sensor in CORE.data[DOMAIN][CONF_SENSORS] + for sensor in CORE.data.setdefault(DOMAIN, {}).setdefault(CONF_SENSORS, []) if sensor[CONF_TRANSPORT_ID] == transport_id ) @@ -137,7 +133,8 @@ def get_sensors(transport_id): def validate_packet_transport_sensor(config): if CONF_NAME in config and CONF_INTERNAL not in config: raise cv.Invalid("Must provide internal: config when using name:") - CORE.data[DOMAIN][CONF_SENSORS].append(config) + conf_sensors = CORE.data.setdefault(DOMAIN, {}).setdefault(CONF_SENSORS, []) + conf_sensors.append(config) return config diff --git a/esphome/components/psram/__init__.py b/esphome/components/psram/__init__.py index b5c87ae5a89..d559b2436b1 100644 --- a/esphome/components/psram/__init__.py +++ b/esphome/components/psram/__init__.py @@ -121,33 +121,30 @@ async def to_code(config): if config[CONF_MODE] == TYPE_OCTAL: cg.add_platformio_option("board_build.arduino.memory_type", "qio_opi") - if CORE.using_esp_idf: - add_idf_sdkconfig_option( - f"CONFIG_{get_esp32_variant().upper()}_SPIRAM_SUPPORT", True - ) - add_idf_sdkconfig_option("CONFIG_SOC_SPIRAM_SUPPORTED", True) - add_idf_sdkconfig_option("CONFIG_SPIRAM", True) - add_idf_sdkconfig_option("CONFIG_SPIRAM_USE", True) - add_idf_sdkconfig_option("CONFIG_SPIRAM_USE_CAPS_ALLOC", True) - add_idf_sdkconfig_option("CONFIG_SPIRAM_IGNORE_NOTFOUND", True) + add_idf_sdkconfig_option( + f"CONFIG_{get_esp32_variant().upper()}_SPIRAM_SUPPORT", True + ) + add_idf_sdkconfig_option("CONFIG_SOC_SPIRAM_SUPPORTED", True) + add_idf_sdkconfig_option("CONFIG_SPIRAM", True) + add_idf_sdkconfig_option("CONFIG_SPIRAM_USE", True) + add_idf_sdkconfig_option("CONFIG_SPIRAM_USE_CAPS_ALLOC", True) + add_idf_sdkconfig_option("CONFIG_SPIRAM_IGNORE_NOTFOUND", True) - add_idf_sdkconfig_option( - f"CONFIG_SPIRAM_MODE_{SDK_MODES[config[CONF_MODE]]}", True - ) + add_idf_sdkconfig_option(f"CONFIG_SPIRAM_MODE_{SDK_MODES[config[CONF_MODE]]}", True) - # Remove MHz suffix, convert to int - speed = int(config[CONF_SPEED][:-3]) - add_idf_sdkconfig_option(f"CONFIG_SPIRAM_SPEED_{speed}M", True) - add_idf_sdkconfig_option("CONFIG_SPIRAM_SPEED", speed) - if config[CONF_MODE] == TYPE_OCTAL and speed == 120: - add_idf_sdkconfig_option("CONFIG_ESPTOOLPY_FLASHFREQ_120M", True) - add_idf_sdkconfig_option("CONFIG_BOOTLOADER_FLASH_DC_AWARE", True) - if CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version(5, 4, 0): - add_idf_sdkconfig_option( - "CONFIG_SPIRAM_TIMING_TUNING_POINT_VIA_TEMPERATURE_SENSOR", True - ) - if config[CONF_ENABLE_ECC]: - add_idf_sdkconfig_option("CONFIG_SPIRAM_ECC_ENABLE", True) + # Remove MHz suffix, convert to int + speed = int(config[CONF_SPEED][:-3]) + add_idf_sdkconfig_option(f"CONFIG_SPIRAM_SPEED_{speed}M", True) + add_idf_sdkconfig_option("CONFIG_SPIRAM_SPEED", speed) + if config[CONF_MODE] == TYPE_OCTAL and speed == 120: + add_idf_sdkconfig_option("CONFIG_ESPTOOLPY_FLASHFREQ_120M", True) + add_idf_sdkconfig_option("CONFIG_BOOTLOADER_FLASH_DC_AWARE", True) + if CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version(5, 4, 0): + add_idf_sdkconfig_option( + "CONFIG_SPIRAM_TIMING_TUNING_POINT_VIA_TEMPERATURE_SENSOR", True + ) + if config[CONF_ENABLE_ECC]: + add_idf_sdkconfig_option("CONFIG_SPIRAM_ECC_ENABLE", True) cg.add_define("USE_PSRAM") diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index 7d4c6360fea..4c8c991eb67 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -16,7 +16,6 @@ from esphome.const import ( CONF_DUMMY_RECEIVER_ID, CONF_ID, CONF_INVERT, - CONF_INVERTED, CONF_LAMBDA, CONF_NUMBER, CONF_PORT, @@ -39,9 +38,6 @@ uart_ns = cg.esphome_ns.namespace("uart") UARTComponent = uart_ns.class_("UARTComponent") IDFUARTComponent = uart_ns.class_("IDFUARTComponent", UARTComponent, cg.Component) -ESP32ArduinoUARTComponent = uart_ns.class_( - "ESP32ArduinoUARTComponent", UARTComponent, cg.Component -) ESP8266UartComponent = uart_ns.class_( "ESP8266UartComponent", UARTComponent, cg.Component ) @@ -53,7 +49,6 @@ HostUartComponent = uart_ns.class_("HostUartComponent", UARTComponent, cg.Compon NATIVE_UART_CLASSES = ( str(IDFUARTComponent), - str(ESP32ArduinoUARTComponent), str(ESP8266UartComponent), str(RP2040UartComponent), str(LibreTinyUARTComponent), @@ -119,20 +114,6 @@ def validate_rx_pin(value): return value -def validate_invert_esp32(config): - if ( - CORE.is_esp32 - and CORE.using_arduino - and CONF_TX_PIN in config - and CONF_RX_PIN in config - and config[CONF_TX_PIN][CONF_INVERTED] != config[CONF_RX_PIN][CONF_INVERTED] - ): - raise cv.Invalid( - "Different invert values for TX and RX pin are not supported for ESP32 when using Arduino." - ) - return config - - def validate_host_config(config): if CORE.is_host: if CONF_TX_PIN in config or CONF_RX_PIN in config: @@ -151,10 +132,7 @@ def _uart_declare_type(value): if CORE.is_esp8266: return cv.declare_id(ESP8266UartComponent)(value) if CORE.is_esp32: - if CORE.using_arduino: - return cv.declare_id(ESP32ArduinoUARTComponent)(value) - if CORE.using_esp_idf: - return cv.declare_id(IDFUARTComponent)(value) + return cv.declare_id(IDFUARTComponent)(value) if CORE.is_rp2040: return cv.declare_id(RP2040UartComponent)(value) if CORE.is_libretiny: @@ -255,7 +233,6 @@ CONFIG_SCHEMA = cv.All( } ).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN, CONF_PORT), - validate_invert_esp32, validate_host_config, ) @@ -444,8 +421,10 @@ async def uart_write_to_code(config, action_id, template_arg, args): FILTER_SOURCE_FILES = filter_source_files_from_platform( { - "uart_component_esp32_arduino.cpp": {PlatformFramework.ESP32_ARDUINO}, - "uart_component_esp_idf.cpp": {PlatformFramework.ESP32_IDF}, + "uart_component_esp_idf.cpp": { + PlatformFramework.ESP32_IDF, + PlatformFramework.ESP32_ARDUINO, + }, "uart_component_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO}, "uart_component_host.cpp": {PlatformFramework.HOST_NATIVE}, "uart_component_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO}, diff --git a/esphome/components/uart/uart_component_esp32_arduino.cpp b/esphome/components/uart/uart_component_esp32_arduino.cpp deleted file mode 100644 index 4a1c326789d..00000000000 --- a/esphome/components/uart/uart_component_esp32_arduino.cpp +++ /dev/null @@ -1,214 +0,0 @@ -#ifdef USE_ESP32_FRAMEWORK_ARDUINO -#include "uart_component_esp32_arduino.h" -#include "esphome/core/application.h" -#include "esphome/core/defines.h" -#include "esphome/core/helpers.h" -#include "esphome/core/log.h" - -#ifdef USE_LOGGER -#include "esphome/components/logger/logger.h" -#endif - -namespace esphome { -namespace uart { -static const char *const TAG = "uart.arduino_esp32"; - -static const uint32_t UART_PARITY_EVEN = 0 << 0; -static const uint32_t UART_PARITY_ODD = 1 << 0; -static const uint32_t UART_PARITY_ENABLE = 1 << 1; -static const uint32_t UART_NB_BIT_5 = 0 << 2; -static const uint32_t UART_NB_BIT_6 = 1 << 2; -static const uint32_t UART_NB_BIT_7 = 2 << 2; -static const uint32_t UART_NB_BIT_8 = 3 << 2; -static const uint32_t UART_NB_STOP_BIT_1 = 1 << 4; -static const uint32_t UART_NB_STOP_BIT_2 = 3 << 4; -static const uint32_t UART_TICK_APB_CLOCK = 1 << 27; - -uint32_t ESP32ArduinoUARTComponent::get_config() { - uint32_t config = 0; - - /* - * All bits numbers below come from - * framework-arduinoespressif32/cores/esp32/esp32-hal-uart.h - * And more specifically conf0 union in uart_dev_t. - * - * Below is bit used from conf0 union. - * : - * parity:0 0:even 1:odd - * parity_en:1 Set this bit to enable uart parity check. - * bit_num:2-4 0:5bits 1:6bits 2:7bits 3:8bits - * stop_bit_num:4-6 stop bit. 1:1bit 2:1.5bits 3:2bits - * tick_ref_always_on:27 select the clock.1:apb clock:ref_tick - */ - - if (this->parity_ == UART_CONFIG_PARITY_EVEN) { - config |= UART_PARITY_EVEN | UART_PARITY_ENABLE; - } else if (this->parity_ == UART_CONFIG_PARITY_ODD) { - config |= UART_PARITY_ODD | UART_PARITY_ENABLE; - } - - switch (this->data_bits_) { - case 5: - config |= UART_NB_BIT_5; - break; - case 6: - config |= UART_NB_BIT_6; - break; - case 7: - config |= UART_NB_BIT_7; - break; - case 8: - config |= UART_NB_BIT_8; - break; - } - - if (this->stop_bits_ == 1) { - config |= UART_NB_STOP_BIT_1; - } else { - config |= UART_NB_STOP_BIT_2; - } - - config |= UART_TICK_APB_CLOCK; - - return config; -} - -void ESP32ArduinoUARTComponent::setup() { - // Use Arduino HardwareSerial UARTs if all used pins match the ones - // preconfigured by the platform. For example if RX disabled but TX pin - // is 1 we still want to use Serial. - bool is_default_tx, is_default_rx; -#ifdef CONFIG_IDF_TARGET_ESP32C3 - is_default_tx = tx_pin_ == nullptr || tx_pin_->get_pin() == 21; - is_default_rx = rx_pin_ == nullptr || rx_pin_->get_pin() == 20; -#else - is_default_tx = tx_pin_ == nullptr || tx_pin_->get_pin() == 1; - is_default_rx = rx_pin_ == nullptr || rx_pin_->get_pin() == 3; -#endif - static uint8_t next_uart_num = 0; - if (is_default_tx && is_default_rx && next_uart_num == 0) { -#if ARDUINO_USB_CDC_ON_BOOT - this->hw_serial_ = &Serial0; -#else - this->hw_serial_ = &Serial; -#endif - next_uart_num++; - } else { -#ifdef USE_LOGGER - bool logger_uses_hardware_uart = true; - -#ifdef USE_LOGGER_USB_CDC - if (logger::global_logger->get_uart() == logger::UART_SELECTION_USB_CDC) { - // this is not a hardware UART, ignore it - logger_uses_hardware_uart = false; - } -#endif // USE_LOGGER_USB_CDC - -#ifdef USE_LOGGER_USB_SERIAL_JTAG - if (logger::global_logger->get_uart() == logger::UART_SELECTION_USB_SERIAL_JTAG) { - // this is not a hardware UART, ignore it - logger_uses_hardware_uart = false; - } -#endif // USE_LOGGER_USB_SERIAL_JTAG - - if (logger_uses_hardware_uart && logger::global_logger->get_baud_rate() > 0 && - logger::global_logger->get_uart() == next_uart_num) { - next_uart_num++; - } -#endif // USE_LOGGER - - if (next_uart_num >= SOC_UART_NUM) { - ESP_LOGW(TAG, "Maximum number of UART components created already."); - this->mark_failed(); - return; - } - - this->number_ = next_uart_num; - this->hw_serial_ = new HardwareSerial(next_uart_num++); // NOLINT(cppcoreguidelines-owning-memory) - } - - this->load_settings(false); -} - -void ESP32ArduinoUARTComponent::load_settings(bool dump_config) { - int8_t tx = this->tx_pin_ != nullptr ? this->tx_pin_->get_pin() : -1; - int8_t rx = this->rx_pin_ != nullptr ? this->rx_pin_->get_pin() : -1; - bool invert = false; - if (tx_pin_ != nullptr && tx_pin_->is_inverted()) - invert = true; - if (rx_pin_ != nullptr && rx_pin_->is_inverted()) - invert = true; - this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); - this->hw_serial_->begin(this->baud_rate_, get_config(), rx, tx, invert); - if (dump_config) { - ESP_LOGCONFIG(TAG, "UART %u was reloaded.", this->number_); - this->dump_config(); - } -} - -void ESP32ArduinoUARTComponent::dump_config() { - ESP_LOGCONFIG(TAG, "UART Bus %d:", this->number_); - LOG_PIN(" TX Pin: ", tx_pin_); - LOG_PIN(" RX Pin: ", rx_pin_); - if (this->rx_pin_ != nullptr) { - ESP_LOGCONFIG(TAG, " RX Buffer Size: %u", this->rx_buffer_size_); - } - ESP_LOGCONFIG(TAG, - " Baud Rate: %u baud\n" - " Data Bits: %u\n" - " Parity: %s\n" - " Stop bits: %u", - this->baud_rate_, this->data_bits_, LOG_STR_ARG(parity_to_str(this->parity_)), this->stop_bits_); - this->check_logger_conflict(); -} - -void ESP32ArduinoUARTComponent::write_array(const uint8_t *data, size_t len) { - this->hw_serial_->write(data, len); -#ifdef USE_UART_DEBUGGER - for (size_t i = 0; i < len; i++) { - this->debug_callback_.call(UART_DIRECTION_TX, data[i]); - } -#endif -} - -bool ESP32ArduinoUARTComponent::peek_byte(uint8_t *data) { - if (!this->check_read_timeout_()) - return false; - *data = this->hw_serial_->peek(); - return true; -} - -bool ESP32ArduinoUARTComponent::read_array(uint8_t *data, size_t len) { - if (!this->check_read_timeout_(len)) - return false; - this->hw_serial_->readBytes(data, len); -#ifdef USE_UART_DEBUGGER - for (size_t i = 0; i < len; i++) { - this->debug_callback_.call(UART_DIRECTION_RX, data[i]); - } -#endif - return true; -} - -int ESP32ArduinoUARTComponent::available() { return this->hw_serial_->available(); } -void ESP32ArduinoUARTComponent::flush() { - ESP_LOGVV(TAG, " Flushing"); - this->hw_serial_->flush(); -} - -void ESP32ArduinoUARTComponent::check_logger_conflict() { -#ifdef USE_LOGGER - if (this->hw_serial_ == nullptr || logger::global_logger->get_baud_rate() == 0) { - return; - } - - if (this->hw_serial_ == logger::global_logger->get_hw_serial()) { - ESP_LOGW(TAG, " You're using the same serial port for logging and the UART component. Please " - "disable logging over the serial port by setting logger->baud_rate to 0."); - } -#endif -} - -} // namespace uart -} // namespace esphome -#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/uart/uart_component_esp32_arduino.h b/esphome/components/uart/uart_component_esp32_arduino.h deleted file mode 100644 index de17d9718b8..00000000000 --- a/esphome/components/uart/uart_component_esp32_arduino.h +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once - -#ifdef USE_ESP32_FRAMEWORK_ARDUINO - -#include -#include -#include -#include "esphome/core/component.h" -#include "esphome/core/hal.h" -#include "esphome/core/log.h" -#include "uart_component.h" - -namespace esphome { -namespace uart { - -class ESP32ArduinoUARTComponent : public UARTComponent, public Component { - public: - void setup() override; - void dump_config() override; - float get_setup_priority() const override { return setup_priority::BUS; } - - void write_array(const uint8_t *data, size_t len) override; - - bool peek_byte(uint8_t *data) override; - bool read_array(uint8_t *data, size_t len) override; - - int available() override; - void flush() override; - - uint32_t get_config(); - - HardwareSerial *get_hw_serial() { return this->hw_serial_; } - uint8_t get_hw_serial_number() { return this->number_; } - - /** - * Load the UART with the current settings. - * @param dump_config (Optional, default `true`): True for displaying new settings or - * false to change it quitely - * - * Example: - * ```cpp - * id(uart1).load_settings(); - * ``` - * - * This will load the current UART interface with the latest settings (baud_rate, parity, etc). - */ - void load_settings(bool dump_config) override; - void load_settings() override { this->load_settings(true); } - - protected: - void check_logger_conflict() override; - - HardwareSerial *hw_serial_{nullptr}; - uint8_t number_{0}; -}; - -} // namespace uart -} // namespace esphome - -#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp index 6bb4b168191..6517b4b5038 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -1,4 +1,4 @@ -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "uart_component_esp_idf.h" #include diff --git a/esphome/components/uart/uart_component_esp_idf.h b/esphome/components/uart/uart_component_esp_idf.h index 215641ebe23..606e17e5b68 100644 --- a/esphome/components/uart/uart_component_esp_idf.h +++ b/esphome/components/uart/uart_component_esp_idf.h @@ -1,6 +1,6 @@ #pragma once -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include #include "esphome/core/component.h" @@ -55,4 +55,4 @@ class IDFUARTComponent : public UARTComponent, public Component { } // namespace uart } // namespace esphome -#endif // USE_ESP_IDF +#endif // USE_ESP32 diff --git a/esphome/components/web_server/ota/__init__.py b/esphome/components/web_server/ota/__init__.py index 3ec7e65e1d3..22e56639e19 100644 --- a/esphome/components/web_server/ota/__init__.py +++ b/esphome/components/web_server/ota/__init__.py @@ -3,7 +3,8 @@ from esphome.components.esp32 import add_idf_component from esphome.components.ota import BASE_OTA_SCHEMA, OTAComponent, ota_to_code import esphome.config_validation as cv from esphome.const import CONF_ID -from esphome.core import CORE, CoroPriority, coroutine_with_priority +from esphome.core import CORE, coroutine_with_priority +from esphome.coroutine import CoroPriority CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network", "web_server_base"] @@ -22,7 +23,7 @@ CONFIG_SCHEMA = ( ) -@coroutine_with_priority(CoroPriority.COMMUNICATION) +@coroutine_with_priority(CoroPriority.WEB_SERVER_OTA) async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await ota_to_code(var, config) diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index 8add2f051f2..2aff4050362 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -1,7 +1,8 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import CONF_ID -from esphome.core import CORE, CoroPriority, coroutine_with_priority +from esphome.core import CORE, coroutine_with_priority +from esphome.coroutine import CoroPriority CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network"] @@ -26,7 +27,7 @@ CONFIG_SCHEMA = cv.Schema( ) -@coroutine_with_priority(CoroPriority.COMMUNICATION) +@coroutine_with_priority(CoroPriority.WEB_SERVER_BASE) async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index c63a12f879c..ef74c149242 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -402,7 +402,7 @@ async def to_code(config): add_idf_sdkconfig_option("CONFIG_LWIP_DHCPS", False) # Disable Enterprise WiFi support if no EAP is configured - if CORE.is_esp32 and CORE.using_esp_idf and not has_eap: + if CORE.is_esp32 and not has_eap: add_idf_sdkconfig_option("CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT", False) cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) diff --git a/esphome/components/wireguard/__init__.py b/esphome/components/wireguard/__init__.py index 8eff8e7b2a8..50c79802153 100644 --- a/esphome/components/wireguard/__init__.py +++ b/esphome/components/wireguard/__init__.py @@ -118,7 +118,7 @@ async def to_code(config): # Workaround for crash on IDF 5+ # See https://github.com/trombik/esp_wireguard/issues/33#issuecomment-1568503651 - if CORE.using_esp_idf: + if CORE.is_esp32: add_idf_sdkconfig_option("CONFIG_LWIP_PPP_SUPPORT", True) # This flag is added here because the esp_wireguard library statically diff --git a/esphome/components/zwave_proxy/__init__.py b/esphome/components/zwave_proxy/__init__.py new file mode 100644 index 00000000000..d88f9f7041e --- /dev/null +++ b/esphome/components/zwave_proxy/__init__.py @@ -0,0 +1,43 @@ +import esphome.codegen as cg +from esphome.components import uart +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_POWER_SAVE_MODE, CONF_WIFI +import esphome.final_validate as fv + +CODEOWNERS = ["@kbx81"] +DEPENDENCIES = ["api", "uart"] + +zwave_proxy_ns = cg.esphome_ns.namespace("zwave_proxy") +ZWaveProxy = zwave_proxy_ns.class_("ZWaveProxy", cg.Component, uart.UARTDevice) + + +def final_validate(config): + full_config = fv.full_config.get() + if (wifi_conf := full_config.get(CONF_WIFI)) and ( + wifi_conf.get(CONF_POWER_SAVE_MODE).lower() != "none" + ): + raise cv.Invalid( + f"{CONF_WIFI} {CONF_POWER_SAVE_MODE} must be set to 'none' when using Z-Wave proxy" + ) + + return config + + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(ZWaveProxy), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(uart.UART_DEVICE_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = final_validate + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) + cg.add_define("USE_ZWAVE_PROXY") diff --git a/esphome/components/zwave_proxy/zwave_proxy.cpp b/esphome/components/zwave_proxy/zwave_proxy.cpp new file mode 100644 index 00000000000..e39f857743a --- /dev/null +++ b/esphome/components/zwave_proxy/zwave_proxy.cpp @@ -0,0 +1,224 @@ +#include "zwave_proxy.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "esphome/core/util.h" + +namespace esphome { +namespace zwave_proxy { + +static const char *const TAG = "zwave_proxy"; + +ZWaveProxy::ZWaveProxy() { global_zwave_proxy = this; } + +void ZWaveProxy::loop() { + if (this->response_handler_()) { + ESP_LOGV(TAG, "Handled late response"); + } + if (this->api_connection_ != nullptr && (!this->api_connection_->is_connection_setup() || !api_is_connected())) { + ESP_LOGW(TAG, "Subscriber disconnected"); + this->api_connection_ = nullptr; // Unsubscribe if disconnected + } + + while (this->available()) { + uint8_t byte; + if (!this->read_byte(&byte)) { + this->status_set_warning("UART read failed"); + return; + } + if (this->parse_byte_(byte)) { + ESP_LOGV(TAG, "Sending to client: %s", YESNO(this->api_connection_ != nullptr)); + if (this->api_connection_ != nullptr) { + // minimize copying to reduce CPU overhead + if (this->in_bootloader_) { + this->outgoing_proto_msg_.data_len = this->buffer_index_; + } else { + // If this is a data frame, use frame length indicator + 2 (for SoF + checksum), else assume 1 for ACK/NAK/CAN + this->outgoing_proto_msg_.data_len = this->buffer_[0] == ZWAVE_FRAME_TYPE_START ? this->buffer_[1] + 2 : 1; + } + std::memcpy(this->outgoing_proto_msg_.data, this->buffer_, this->outgoing_proto_msg_.data_len); + this->api_connection_->send_message(this->outgoing_proto_msg_, api::ZWaveProxyFrame::MESSAGE_TYPE); + } + } + } + this->status_clear_warning(); +} + +void ZWaveProxy::dump_config() { ESP_LOGCONFIG(TAG, "Z-Wave Proxy"); } + +void ZWaveProxy::zwave_proxy_request(api::APIConnection *api_connection, api::enums::ZWaveProxyRequestType type) { + switch (type) { + case api::enums::ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE: + if (this->api_connection_ != nullptr) { + ESP_LOGE(TAG, "Only one API subscription is allowed at a time"); + return; + } + this->api_connection_ = api_connection; + ESP_LOGV(TAG, "API connection is now subscribed"); + break; + case api::enums::ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE: + if (this->api_connection_ != api_connection) { + ESP_LOGV(TAG, "API connection is not subscribed"); + return; + } + this->api_connection_ = nullptr; + break; + default: + ESP_LOGW(TAG, "Unknown request type: %d", type); + break; + } +} + +void ZWaveProxy::send_frame(const uint8_t *data, size_t length) { + if (length == 1 && data[0] == this->last_response_) { + ESP_LOGV(TAG, "Skipping sending duplicate response: 0x%02X", data[0]); + return; + } + ESP_LOGVV(TAG, "Sending: %s", format_hex_pretty(data, length).c_str()); + this->write_array(data, length); +} + +bool ZWaveProxy::parse_byte_(uint8_t byte) { + bool frame_completed = false; + // Basic parsing logic for received frames + switch (this->parsing_state_) { + case ZWAVE_PARSING_STATE_WAIT_START: + this->parse_start_(byte); + break; + case ZWAVE_PARSING_STATE_WAIT_LENGTH: + if (!byte) { + ESP_LOGW(TAG, "Invalid LENGTH: %u", byte); + this->parsing_state_ = ZWAVE_PARSING_STATE_SEND_NAK; + return false; + } + ESP_LOGVV(TAG, "Received LENGTH: %u", byte); + this->end_frame_after_ = this->buffer_index_ + byte; + ESP_LOGVV(TAG, "Calculated EOF: %u", this->end_frame_after_); + this->buffer_[this->buffer_index_++] = byte; + this->checksum_ ^= byte; + this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_TYPE; + break; + case ZWAVE_PARSING_STATE_WAIT_TYPE: + this->buffer_[this->buffer_index_++] = byte; + ESP_LOGVV(TAG, "Received TYPE: 0x%02X", byte); + this->checksum_ ^= byte; + this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_COMMAND_ID; + break; + case ZWAVE_PARSING_STATE_WAIT_COMMAND_ID: + this->buffer_[this->buffer_index_++] = byte; + ESP_LOGVV(TAG, "Received COMMAND ID: 0x%02X", byte); + this->checksum_ ^= byte; + this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_PAYLOAD; + break; + case ZWAVE_PARSING_STATE_WAIT_PAYLOAD: + this->buffer_[this->buffer_index_++] = byte; + this->checksum_ ^= byte; + ESP_LOGVV(TAG, "Received PAYLOAD: 0x%02X", byte); + if (this->buffer_index_ >= this->end_frame_after_) { + this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_CHECKSUM; + } + break; + case ZWAVE_PARSING_STATE_WAIT_CHECKSUM: + this->buffer_[this->buffer_index_++] = byte; + ESP_LOGVV(TAG, "Received CHECKSUM: 0x%02X", byte); + ESP_LOGV(TAG, "Calculated CHECKSUM: 0x%02X", this->checksum_); + if (this->checksum_ != byte) { + ESP_LOGW(TAG, "Bad checksum: expected 0x%02X, got 0x%02X", this->checksum_, byte); + this->parsing_state_ = ZWAVE_PARSING_STATE_SEND_NAK; + } else { + this->parsing_state_ = ZWAVE_PARSING_STATE_SEND_ACK; + ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(this->buffer_, this->buffer_index_).c_str()); + frame_completed = true; + } + this->response_handler_(); + break; + case ZWAVE_PARSING_STATE_READ_BL_MENU: + this->buffer_[this->buffer_index_++] = byte; + if (!byte) { + this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_START; + frame_completed = true; + } + break; + case ZWAVE_PARSING_STATE_SEND_ACK: + case ZWAVE_PARSING_STATE_SEND_NAK: + break; // Should not happen, handled in loop() + default: + ESP_LOGW(TAG, "Bad parsing state; resetting"); + this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_START; + break; + } + return frame_completed; +} + +void ZWaveProxy::parse_start_(uint8_t byte) { + this->buffer_index_ = 0; + this->checksum_ = 0xFF; + this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_START; + switch (byte) { + case ZWAVE_FRAME_TYPE_START: + ESP_LOGVV(TAG, "Received START"); + if (this->in_bootloader_) { + ESP_LOGD(TAG, "Exited bootloader mode"); + this->in_bootloader_ = false; + } + this->buffer_[this->buffer_index_++] = byte; + this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_LENGTH; + return; + case ZWAVE_FRAME_TYPE_BL_MENU: + ESP_LOGVV(TAG, "Received BL_MENU"); + if (!this->in_bootloader_) { + ESP_LOGD(TAG, "Entered bootloader mode"); + this->in_bootloader_ = true; + } + this->buffer_[this->buffer_index_++] = byte; + this->parsing_state_ = ZWAVE_PARSING_STATE_READ_BL_MENU; + return; + case ZWAVE_FRAME_TYPE_BL_BEGIN_UPLOAD: + ESP_LOGVV(TAG, "Received BL_BEGIN_UPLOAD"); + break; + case ZWAVE_FRAME_TYPE_ACK: + ESP_LOGVV(TAG, "Received ACK"); + break; + case ZWAVE_FRAME_TYPE_NAK: + ESP_LOGW(TAG, "Received NAK"); + break; + case ZWAVE_FRAME_TYPE_CAN: + ESP_LOGW(TAG, "Received CAN"); + break; + default: + ESP_LOGW(TAG, "Unrecognized START: 0x%02X", byte); + return; + } + // Forward response (ACK/NAK/CAN) back to client for processing + if (this->api_connection_ != nullptr) { + this->outgoing_proto_msg_.data[0] = byte; + this->outgoing_proto_msg_.data_len = 1; + this->api_connection_->send_message(this->outgoing_proto_msg_, api::ZWaveProxyFrame::MESSAGE_TYPE); + } +} + +bool ZWaveProxy::response_handler_() { + switch (this->parsing_state_) { + case ZWAVE_PARSING_STATE_SEND_ACK: + this->last_response_ = ZWAVE_FRAME_TYPE_ACK; + break; + case ZWAVE_PARSING_STATE_SEND_CAN: + this->last_response_ = ZWAVE_FRAME_TYPE_CAN; + break; + case ZWAVE_PARSING_STATE_SEND_NAK: + this->last_response_ = ZWAVE_FRAME_TYPE_NAK; + break; + default: + return false; // No response handled + } + + ESP_LOGVV(TAG, "Sending %s (0x%02X)", this->last_response_ == ZWAVE_FRAME_TYPE_ACK ? "ACK" : "NAK/CAN", + this->last_response_); + this->write_byte(this->last_response_); + this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_START; + return true; +} + +ZWaveProxy *global_zwave_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +} // namespace zwave_proxy +} // namespace esphome diff --git a/esphome/components/zwave_proxy/zwave_proxy.h b/esphome/components/zwave_proxy/zwave_proxy.h new file mode 100644 index 00000000000..a0f25849e40 --- /dev/null +++ b/esphome/components/zwave_proxy/zwave_proxy.h @@ -0,0 +1,73 @@ +#pragma once + +#include "esphome/components/api/api_connection.h" +#include "esphome/components/api/api_pb2.h" +#include "esphome/core/component.h" +#include "esphome/components/uart/uart.h" + +namespace esphome { +namespace zwave_proxy { + +enum ZWaveResponseTypes : uint8_t { + ZWAVE_FRAME_TYPE_ACK = 0x06, + ZWAVE_FRAME_TYPE_CAN = 0x18, + ZWAVE_FRAME_TYPE_NAK = 0x15, + ZWAVE_FRAME_TYPE_START = 0x01, + ZWAVE_FRAME_TYPE_BL_MENU = 0x0D, + ZWAVE_FRAME_TYPE_BL_BEGIN_UPLOAD = 0x43, +}; + +enum ZWaveParsingState : uint8_t { + ZWAVE_PARSING_STATE_WAIT_START, + ZWAVE_PARSING_STATE_WAIT_LENGTH, + ZWAVE_PARSING_STATE_WAIT_TYPE, + ZWAVE_PARSING_STATE_WAIT_COMMAND_ID, + ZWAVE_PARSING_STATE_WAIT_PAYLOAD, + ZWAVE_PARSING_STATE_WAIT_CHECKSUM, + ZWAVE_PARSING_STATE_SEND_ACK, + ZWAVE_PARSING_STATE_SEND_CAN, + ZWAVE_PARSING_STATE_SEND_NAK, + ZWAVE_PARSING_STATE_READ_BL_MENU, +}; + +enum ZWaveProxyFeature : uint32_t { + FEATURE_ZWAVE_PROXY_ENABLED = 1 << 0, +}; + +class ZWaveProxy : public uart::UARTDevice, public Component { + public: + ZWaveProxy(); + + void loop() override; + void dump_config() override; + + void zwave_proxy_request(api::APIConnection *api_connection, api::enums::ZWaveProxyRequestType type); + api::APIConnection *get_api_connection() { return this->api_connection_; } + + uint32_t get_feature_flags() const { return ZWaveProxyFeature::FEATURE_ZWAVE_PROXY_ENABLED; } + + void send_frame(const uint8_t *data, size_t length); + + protected: + bool parse_byte_(uint8_t byte); // Returns true if frame parsing was completed (a frame is ready in the buffer) + void parse_start_(uint8_t byte); + bool response_handler_(); + + api::APIConnection *api_connection_{nullptr}; // Current subscribed client + + uint8_t buffer_[sizeof(api::ZWaveProxyFrame::data)]; // Fixed buffer for incoming data + uint8_t buffer_index_{0}; // Index for populating the data buffer + uint8_t checksum_{0}; // Checksum of the frame being parsed + uint8_t end_frame_after_{0}; // Payload reception ends after this index + uint8_t last_response_{0}; // Last response type sent + ZWaveParsingState parsing_state_{ZWAVE_PARSING_STATE_WAIT_START}; + bool in_bootloader_{false}; // True if the device is detected to be in bootloader mode + + // Pre-allocated message - always ready to send + api::ZWaveProxyFrame outgoing_proto_msg_; +}; + +extern ZWaveProxy *global_zwave_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +} // namespace zwave_proxy +} // namespace esphome diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 6e8d5ed74c5..bdc59598f6a 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -100,6 +100,7 @@ #define USE_UART_DEBUGGER #define USE_UPDATE #define USE_VALVE +#define USE_ZWAVE_PROXY // Feature flags which do not work for zephyr #ifndef USE_ZEPHYR diff --git a/esphome/core/string_ref.cpp b/esphome/core/string_ref.cpp deleted file mode 100644 index ce1e33cbb74..00000000000 --- a/esphome/core/string_ref.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "string_ref.h" - -namespace esphome { - -#ifdef USE_JSON - -// NOLINTNEXTLINE(readability-identifier-naming) -void convertToJson(const StringRef &src, JsonVariant dst) { dst.set(src.c_str()); } - -#endif // USE_JSON - -} // namespace esphome diff --git a/esphome/core/string_ref.h b/esphome/core/string_ref.h index c4320107e3b..efaa17181d7 100644 --- a/esphome/core/string_ref.h +++ b/esphome/core/string_ref.h @@ -130,7 +130,7 @@ inline std::string operator+(const StringRef &lhs, const char *rhs) { #ifdef USE_JSON // NOLINTNEXTLINE(readability-identifier-naming) -void convertToJson(const StringRef &src, JsonVariant dst); +inline void convertToJson(const StringRef &src, JsonVariant dst) { dst.set(src.c_str()); } #endif // USE_JSON } // namespace esphome diff --git a/esphome/coroutine.py b/esphome/coroutine.py index 741a0c7c0c0..0331c602c54 100644 --- a/esphome/coroutine.py +++ b/esphome/coroutine.py @@ -90,11 +90,30 @@ class CoroPriority(enum.IntEnum): # Examples: status_led (80) STATUS = 80 + # Web server infrastructure + # Examples: web_server_base (65) + WEB_SERVER_BASE = 65 + + # Network portal services + # Examples: captive_portal (64) + CAPTIVE_PORTAL = 64 + # Communication protocols and services - # Examples: web_server_base (65), captive_portal (64), wifi (60), ethernet (60), - # mdns (55), ota_updates (54), web_server_ota (52) + # Examples: wifi (60), ethernet (60) COMMUNICATION = 60 + # Network discovery and management services + # Examples: mdns (55) + NETWORK_SERVICES = 55 + + # OTA update services + # Examples: ota_updates (54) + OTA_UPDATES = 54 + + # Web-based OTA services + # Examples: web_server_ota (52) + WEB_SERVER_OTA = 52 + # Application-level services # Examples: safe_mode (50) APPLICATION = 50 diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py index 267277ebe17..d8c86118844 100644 --- a/esphome/platformio_api.py +++ b/esphome/platformio_api.py @@ -70,6 +70,8 @@ FILTER_PLATFORMIO_LINES = [ r" - tool-esptool.* \(.*\)", r" - toolchain-.* \(.*\)", r"Creating BIN file .*", + r"Warning! Could not find file \".*.crt\"", + r"Warning! Arduino framework as an ESP-IDF component doesn't handle the `variant` field! The default `esp32` variant will be used.", ] diff --git a/pyproject.toml b/pyproject.toml index 4943c48eb00..b7b4a48d7eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "esphome" -license = {text = "MIT"} +license = "MIT" description = "ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems." readme = "README.md" authors = [ @@ -15,7 +15,6 @@ classifiers = [ "Environment :: Console", "Intended Audience :: Developers", "Intended Audience :: End Users/Desktop", - "License :: OSI Approved :: MIT License", "Programming Language :: C++", "Programming Language :: Python :: 3", "Topic :: Home Automation", diff --git a/requirements.txt b/requirements.txt index 296485bdae2..e29a926a7cd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==5.0.2 click==8.1.7 esphome-dashboard==20250904.0 -aioesphomeapi==40.2.1 +aioesphomeapi==41.1.0 zeroconf==0.147.2 puremagic==1.30 ruamel.yaml==0.18.15 # dashboard_import diff --git a/tests/components/mdns/test-comprehensive.esp8266-ard.yaml b/tests/components/mdns/test-comprehensive.esp8266-ard.yaml new file mode 100644 index 00000000000..02767833a39 --- /dev/null +++ b/tests/components/mdns/test-comprehensive.esp8266-ard.yaml @@ -0,0 +1,42 @@ +# Comprehensive ESP8266 test for mdns with multiple network components +# Tests the complete priority chain: +# wifi (60) -> mdns (55) -> ota (54) -> web_server_ota (52) + +esphome: + name: mdns-comprehensive-test + +esp8266: + board: esp01_1m + +logger: + level: DEBUG + +wifi: + ssid: MySSID + password: password1 + +# web_server_base should run at priority 65 (before wifi) +web_server: + port: 80 + +# mdns should run at priority 55 (after wifi at 60) +mdns: + services: + - service: _http + protocol: _tcp + port: 80 + +# OTA should run at priority 54 (after mdns) +ota: + - platform: esphome + password: "otapassword" + +# Test status LED at priority 80 +status_led: + pin: + number: GPIO2 + inverted: true + +# Include API at priority 40 +api: + password: "apipassword" diff --git a/tests/components/zwave_proxy/common.yaml b/tests/components/zwave_proxy/common.yaml new file mode 100644 index 00000000000..08092ebe55f --- /dev/null +++ b/tests/components/zwave_proxy/common.yaml @@ -0,0 +1,15 @@ +wifi: + ssid: MySSID + password: password1 + power_save_mode: none + +uart: + - id: uart_zwave_proxy + tx_pin: ${tx_pin} + rx_pin: ${rx_pin} + baud_rate: 115200 + +api: + +zwave_proxy: + id: zw_proxy diff --git a/tests/components/zwave_proxy/test.esp32-ard.yaml b/tests/components/zwave_proxy/test.esp32-ard.yaml new file mode 100644 index 00000000000..f486544afa4 --- /dev/null +++ b/tests/components/zwave_proxy/test.esp32-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO17 + rx_pin: GPIO16 + +<<: !include common.yaml diff --git a/tests/components/zwave_proxy/test.esp32-c3-ard.yaml b/tests/components/zwave_proxy/test.esp32-c3-ard.yaml new file mode 100644 index 00000000000..b516342f3bc --- /dev/null +++ b/tests/components/zwave_proxy/test.esp32-c3-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/zwave_proxy/test.esp32-c3-idf.yaml b/tests/components/zwave_proxy/test.esp32-c3-idf.yaml new file mode 100644 index 00000000000..b516342f3bc --- /dev/null +++ b/tests/components/zwave_proxy/test.esp32-c3-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/zwave_proxy/test.esp32-idf.yaml b/tests/components/zwave_proxy/test.esp32-idf.yaml new file mode 100644 index 00000000000..f486544afa4 --- /dev/null +++ b/tests/components/zwave_proxy/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO17 + rx_pin: GPIO16 + +<<: !include common.yaml diff --git a/tests/components/zwave_proxy/test.esp8266-ard.yaml b/tests/components/zwave_proxy/test.esp8266-ard.yaml new file mode 100644 index 00000000000..b516342f3bc --- /dev/null +++ b/tests/components/zwave_proxy/test.esp8266-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/zwave_proxy/test.rp2040-ard.yaml b/tests/components/zwave_proxy/test.rp2040-ard.yaml new file mode 100644 index 00000000000..b516342f3bc --- /dev/null +++ b/tests/components/zwave_proxy/test.rp2040-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/unit_tests/test_coroutine.py b/tests/unit_tests/test_coroutine.py index 138b08edb57..e12c273294b 100644 --- a/tests/unit_tests/test_coroutine.py +++ b/tests/unit_tests/test_coroutine.py @@ -13,7 +13,12 @@ def test_coro_priority_enum_values() -> None: assert CoroPriority.CORE == 100 assert CoroPriority.DIAGNOSTICS == 90 assert CoroPriority.STATUS == 80 + assert CoroPriority.WEB_SERVER_BASE == 65 + assert CoroPriority.CAPTIVE_PORTAL == 64 assert CoroPriority.COMMUNICATION == 60 + assert CoroPriority.NETWORK_SERVICES == 55 + assert CoroPriority.OTA_UPDATES == 54 + assert CoroPriority.WEB_SERVER_OTA == 52 assert CoroPriority.APPLICATION == 50 assert CoroPriority.WEB == 40 assert CoroPriority.AUTOMATION == 30 @@ -70,7 +75,12 @@ def test_float_and_enum_are_interchangeable() -> None: (CoroPriority.CORE, 100.0), (CoroPriority.DIAGNOSTICS, 90.0), (CoroPriority.STATUS, 80.0), + (CoroPriority.WEB_SERVER_BASE, 65.0), + (CoroPriority.CAPTIVE_PORTAL, 64.0), (CoroPriority.COMMUNICATION, 60.0), + (CoroPriority.NETWORK_SERVICES, 55.0), + (CoroPriority.OTA_UPDATES, 54.0), + (CoroPriority.WEB_SERVER_OTA, 52.0), (CoroPriority.APPLICATION, 50.0), (CoroPriority.WEB, 40.0), (CoroPriority.AUTOMATION, 30.0), @@ -164,8 +174,13 @@ def test_enum_priority_comparison() -> None: assert CoroPriority.NETWORK_TRANSPORT > CoroPriority.CORE assert CoroPriority.CORE > CoroPriority.DIAGNOSTICS assert CoroPriority.DIAGNOSTICS > CoroPriority.STATUS - assert CoroPriority.STATUS > CoroPriority.COMMUNICATION - assert CoroPriority.COMMUNICATION > CoroPriority.APPLICATION + assert CoroPriority.STATUS > CoroPriority.WEB_SERVER_BASE + assert CoroPriority.WEB_SERVER_BASE > CoroPriority.CAPTIVE_PORTAL + assert CoroPriority.CAPTIVE_PORTAL > CoroPriority.COMMUNICATION + assert CoroPriority.COMMUNICATION > CoroPriority.NETWORK_SERVICES + assert CoroPriority.NETWORK_SERVICES > CoroPriority.OTA_UPDATES + assert CoroPriority.OTA_UPDATES > CoroPriority.WEB_SERVER_OTA + assert CoroPriority.WEB_SERVER_OTA > CoroPriority.APPLICATION assert CoroPriority.APPLICATION > CoroPriority.WEB assert CoroPriority.WEB > CoroPriority.AUTOMATION assert CoroPriority.AUTOMATION > CoroPriority.BUS diff --git a/tests/unit_tests/test_main.py b/tests/unit_tests/test_main.py index bfebb44545c..fff0b2cd48b 100644 --- a/tests/unit_tests/test_main.py +++ b/tests/unit_tests/test_main.py @@ -1226,6 +1226,18 @@ def test_has_mqtt_logging_no_log_topic() -> None: setup_core(config={}) assert has_mqtt_logging() is False + # Setup MQTT config with CONF_LOG_TOPIC but no CONF_LEVEL (regression test for #10771) + # This simulates the default configuration created by validate_config in the MQTT component + setup_core( + config={ + CONF_MQTT: { + CONF_BROKER: "mqtt.local", + CONF_LOG_TOPIC: {CONF_TOPIC: "esphome/debug"}, + } + } + ) + assert has_mqtt_logging() is True + def test_has_mqtt() -> None: """Test has_mqtt function."""