diff --git a/.clang-tidy.hash b/.clang-tidy.hash index a14b44ef96..59caddf59b 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -94557f94be073390342833aff12ef8676a8b597db5fa770a5a1232e9425cb48f +97fb425f1d681a5994ed1cc6187910f5d2c37ee577b6dc07eb3f4d8862a011de diff --git a/CODEOWNERS b/CODEOWNERS index 00db5a3c79..a2267621e7 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -135,7 +135,7 @@ esphome/components/display_menu_base/* @numo68 esphome/components/dps310/* @kbx81 esphome/components/ds1307/* @badbadc0ffee esphome/components/ds2484/* @mrk-its -esphome/components/dsmr/* @glmnet @zuidwijk +esphome/components/dsmr/* @glmnet @PolarGoose @zuidwijk esphome/components/duty_time/* @dudanov esphome/components/ee895/* @Stock-M esphome/components/ektf2232/touchscreen/* @jesserockz diff --git a/esphome/components/async_tcp/__init__.py b/esphome/components/async_tcp/__init__.py index f2d8895b39..4b6c6a275c 100644 --- a/esphome/components/async_tcp/__init__.py +++ b/esphome/components/async_tcp/__init__.py @@ -1,37 +1,50 @@ -# Dummy integration to allow relying on AsyncTCP +# Async TCP client support for all platforms import esphome.codegen as cg import esphome.config_validation as cv -from esphome.const import ( - PLATFORM_BK72XX, - PLATFORM_ESP32, - PLATFORM_ESP8266, - PLATFORM_LN882X, - PLATFORM_RTL87XX, -) from esphome.core import CORE, CoroPriority, coroutine_with_priority CODEOWNERS = ["@esphome/core"] +DEPENDENCIES = ["network"] -CONFIG_SCHEMA = cv.All( - cv.Schema({}), - cv.only_with_arduino, - cv.only_on( - [ - PLATFORM_ESP32, - PLATFORM_ESP8266, - PLATFORM_BK72XX, - PLATFORM_LN882X, - PLATFORM_RTL87XX, - ] - ), -) + +def AUTO_LOAD() -> list[str]: + # Socket component needed for platforms using socket-based implementation + # ESP32, ESP8266, RP2040, and LibreTiny use AsyncTCP libraries, others use sockets + if ( + not CORE.is_esp32 + and not CORE.is_esp8266 + and not CORE.is_rp2040 + and not CORE.is_libretiny + ): + return ["socket"] + return [] + + +# Support all platforms - Arduino/ESP-IDF get libraries, other platforms use socket implementation +CONFIG_SCHEMA = cv.Schema({}) @coroutine_with_priority(CoroPriority.NETWORK_TRANSPORT) async def to_code(config): - if CORE.is_esp32 or CORE.is_libretiny: + if CORE.using_esp_idf: + # ESP-IDF needs the IDF component + from esphome.components.esp32 import add_idf_component + + add_idf_component(name="esp32async/asynctcp", ref="3.4.91") + elif CORE.is_esp32 or CORE.is_libretiny: # https://github.com/ESP32Async/AsyncTCP cg.add_library("ESP32Async/AsyncTCP", "3.4.5") elif CORE.is_esp8266: # https://github.com/ESP32Async/ESPAsyncTCP cg.add_library("ESP32Async/ESPAsyncTCP", "2.0.0") + elif CORE.is_rp2040: + # https://github.com/khoih-prog/AsyncTCP_RP2040W + cg.add_library("khoih-prog/AsyncTCP_RP2040W", "1.2.0") + # Other platforms (host, etc) use socket-based implementation + + +def FILTER_SOURCE_FILES() -> list[str]: + # Exclude socket implementation for platforms that use AsyncTCP libraries + if CORE.is_esp32 or CORE.is_esp8266 or CORE.is_rp2040 or CORE.is_libretiny: + return ["async_tcp_socket.cpp"] + return [] diff --git a/esphome/components/async_tcp/async_tcp.h b/esphome/components/async_tcp/async_tcp.h new file mode 100644 index 0000000000..362f603451 --- /dev/null +++ b/esphome/components/async_tcp/async_tcp.h @@ -0,0 +1,17 @@ +#pragma once +#include "esphome/core/defines.h" + +#if (defined(USE_ESP32) || defined(USE_LIBRETINY)) && !defined(CLANG_TIDY) +// Use AsyncTCP library for ESP32 (Arduino or ESP-IDF) and LibreTiny +// But not for clang-tidy as the header file isn't present in that case +#include +#elif defined(USE_ESP8266) +// Use ESPAsyncTCP library for ESP8266 (always Arduino) +#include +#elif defined(USE_RP2040) +// Use AsyncTCP_RP2040W library for RP2040 +#include +#else +// Use socket-based implementation for other platforms and clang-tidy +#include "async_tcp_socket.h" +#endif diff --git a/esphome/components/async_tcp/async_tcp_socket.cpp b/esphome/components/async_tcp/async_tcp_socket.cpp new file mode 100644 index 0000000000..6c13f346e9 --- /dev/null +++ b/esphome/components/async_tcp/async_tcp_socket.cpp @@ -0,0 +1,161 @@ +#include "async_tcp_socket.h" + +#if defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS) + +#include "esphome/components/network/util.h" +#include "esphome/core/log.h" +#include +#include + +namespace esphome::async_tcp { + +static const char *const TAG = "async_tcp"; + +// Read buffer size matches TCP MSS (1500 MTU - 40 bytes IP/TCP headers). +// This implementation only runs on ESP-IDF and host which have ample stack. +static constexpr size_t READ_BUFFER_SIZE = 1460; + +bool AsyncClient::connect(const char *host, uint16_t port) { + if (connected_ || connecting_) { + ESP_LOGW(TAG, "Already connected/connecting"); + return false; + } + + // Resolve address + struct sockaddr_storage addr; + socklen_t addrlen = esphome::socket::set_sockaddr((struct sockaddr *) &addr, sizeof(addr), host, port); + if (addrlen == 0) { + ESP_LOGE(TAG, "Invalid address: %s", host); + if (error_cb_) + error_cb_(error_arg_, this, -1); + return false; + } + + // Create socket with loop monitoring + int family = ((struct sockaddr *) &addr)->sa_family; + socket_ = esphome::socket::socket_loop_monitored(family, SOCK_STREAM, IPPROTO_TCP); + if (!socket_) { + ESP_LOGE(TAG, "Failed to create socket"); + if (error_cb_) + error_cb_(error_arg_, this, -1); + return false; + } + + socket_->setblocking(false); + + int err = socket_->connect((struct sockaddr *) &addr, addrlen); + if (err == 0) { + // Connection succeeded immediately (rare, but possible for localhost) + connected_ = true; + if (connect_cb_) + connect_cb_(connect_arg_, this); + return true; + } + if (errno != EINPROGRESS) { + ESP_LOGE(TAG, "Connect failed: %d", errno); + close(); + if (error_cb_) + error_cb_(error_arg_, this, errno); + return false; + } + + connecting_ = true; + return true; +} + +void AsyncClient::close() { + socket_.reset(); + bool was_connected = connected_; + connected_ = false; + connecting_ = false; + if (was_connected && disconnect_cb_) + disconnect_cb_(disconnect_arg_, this); +} + +size_t AsyncClient::write(const char *data, size_t len) { + if (!socket_ || !connected_) + return 0; + + ssize_t sent = socket_->write(data, len); + if (sent < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + ESP_LOGE(TAG, "Write error: %d", errno); + close(); + if (error_cb_) + error_cb_(error_arg_, this, errno); + } + return 0; + } + return sent; +} + +void AsyncClient::loop() { + if (!socket_) + return; + + if (connecting_) { + // For connecting, we need to check writability, not readability + // The Application's select() only monitors read FDs, so we do our own check here + // For ESP platforms lwip_select() might be faster, but this code isn't used + // on those platforms anyway. If it was, we'd fix the Application select() + // to report writability instead of doing it this way. + int fd = socket_->get_fd(); + if (fd < 0) { + ESP_LOGW(TAG, "Invalid socket fd"); + close(); + return; + } + + fd_set writefds; + FD_ZERO(&writefds); + FD_SET(fd, &writefds); + + struct timeval tv = {0, 0}; + int ret = select(fd + 1, nullptr, &writefds, nullptr, &tv); + + if (ret > 0 && FD_ISSET(fd, &writefds)) { + int error = 0; + socklen_t len = sizeof(error); + if (socket_->getsockopt(SOL_SOCKET, SO_ERROR, &error, &len) == 0 && error == 0) { + connecting_ = false; + connected_ = true; + if (connect_cb_) + connect_cb_(connect_arg_, this); + } else { + ESP_LOGW(TAG, "Connection failed: %d", error); + close(); + if (error_cb_) + error_cb_(error_arg_, this, error); + } + } else if (ret < 0) { + ESP_LOGE(TAG, "Select error: %d", errno); + close(); + if (error_cb_) + error_cb_(error_arg_, this, errno); + } + } else if (connected_) { + // For connected sockets, use the Application's select() results + if (!socket_->ready()) + return; + + uint8_t buf[READ_BUFFER_SIZE]; + ssize_t len = socket_->read(buf, READ_BUFFER_SIZE); + + if (len == 0) { + ESP_LOGI(TAG, "Connection closed by peer"); + close(); + } else if (len > 0) { + if (data_cb_) + data_cb_(data_arg_, this, buf, len); + } else if (errno != EAGAIN && errno != EWOULDBLOCK) { + ESP_LOGW(TAG, "Read error: %d", errno); + close(); + if (error_cb_) + error_cb_(error_arg_, this, errno); + } + } +} + +} // namespace esphome::async_tcp + +#endif // defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS) diff --git a/esphome/components/async_tcp/async_tcp_socket.h b/esphome/components/async_tcp/async_tcp_socket.h new file mode 100644 index 0000000000..ca3bf19d67 --- /dev/null +++ b/esphome/components/async_tcp/async_tcp_socket.h @@ -0,0 +1,73 @@ +#pragma once + +#include "esphome/core/defines.h" + +#if defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS) + +#include "esphome/components/socket/socket.h" +#include +#include +#include +#include + +namespace esphome::async_tcp { + +/// AsyncClient API for platforms using sockets (ESP-IDF, host, etc.) +/// NOTE: This class is NOT thread-safe. All methods must be called from the main loop. +class AsyncClient { + public: + using AcConnectHandler = std::function; + using AcDataHandler = std::function; + using AcErrorHandler = std::function; + + AsyncClient() = default; + ~AsyncClient() = default; + + [[nodiscard]] bool connect(const char *host, uint16_t port); + void close(); + [[nodiscard]] bool connected() const { return connected_; } + size_t write(const char *data, size_t len); + + void onConnect(AcConnectHandler cb, void *arg = nullptr) { // NOLINT(readability-identifier-naming) + connect_cb_ = std::move(cb); + connect_arg_ = arg; + } + void onDisconnect(AcConnectHandler cb, void *arg = nullptr) { // NOLINT(readability-identifier-naming) + disconnect_cb_ = std::move(cb); + disconnect_arg_ = arg; + } + /// Set data callback. NOTE: data pointer is only valid during callback execution. + void onData(AcDataHandler cb, void *arg = nullptr) { // NOLINT(readability-identifier-naming) + data_cb_ = std::move(cb); + data_arg_ = arg; + } + void onError(AcErrorHandler cb, void *arg = nullptr) { // NOLINT(readability-identifier-naming) + error_cb_ = std::move(cb); + error_arg_ = arg; + } + + // Must be called from loop() + void loop(); + + private: + std::unique_ptr socket_; + + AcConnectHandler connect_cb_{nullptr}; + void *connect_arg_{nullptr}; + AcConnectHandler disconnect_cb_{nullptr}; + void *disconnect_arg_{nullptr}; + AcDataHandler data_cb_{nullptr}; + void *data_arg_{nullptr}; + AcErrorHandler error_cb_{nullptr}; + void *error_arg_{nullptr}; + + bool connected_{false}; + bool connecting_{false}; +}; + +} // namespace esphome::async_tcp + +// Expose AsyncClient in global namespace to match library behavior +using esphome::async_tcp::AsyncClient; // NOLINT(google-global-names-in-headers) +#define ESPHOME_ASYNC_TCP_SOCKET_IMPL +#endif // defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS) diff --git a/esphome/components/dsmr/__init__.py b/esphome/components/dsmr/__init__.py index 017a11673f..0ba68daf5d 100644 --- a/esphome/components/dsmr/__init__.py +++ b/esphome/components/dsmr/__init__.py @@ -4,7 +4,7 @@ from esphome.components import uart import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_RECEIVE_TIMEOUT, CONF_UART_ID -CODEOWNERS = ["@glmnet", "@zuidwijk"] +CODEOWNERS = ["@glmnet", "@zuidwijk", "@PolarGoose"] MULTI_CONF = True @@ -61,7 +61,6 @@ CONFIG_SCHEMA = cv.All( ): cv.positive_time_period_milliseconds, } ).extend(uart.UART_DEVICE_SCHEMA), - cv.only_with_arduino, ) @@ -83,7 +82,7 @@ async def to_code(config): cg.add_build_flag("-DDSMR_WATER_MBUS_ID=" + str(config[CONF_WATER_MBUS_ID])) # DSMR Parser - cg.add_library("glmnet/Dsmr", "0.8") + cg.add_library("esphome/dsmr_parser", "1.0.0") # Crypto - cg.add_library("rweather/Crypto", "0.4.0") + cg.add_library("polargoose/Crypto-no-arduino", "0.4.0") diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index d99cf5e7a9..41fc2f0d85 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -1,5 +1,3 @@ -#ifdef USE_ARDUINO - #include "dsmr.h" #include "esphome/core/log.h" @@ -7,8 +5,7 @@ #include #include -namespace esphome { -namespace dsmr { +namespace esphome::dsmr { static const char *const TAG = "dsmr"; @@ -257,9 +254,9 @@ bool Dsmr::parse_telegram() { ESP_LOGV(TAG, "Trying to parse telegram"); this->stop_requesting_data_(); - ::dsmr::ParseResult res = - ::dsmr::P1Parser::parse(&data, this->telegram_, this->bytes_read_, false, - this->crc_check_); // Parse telegram according to data definition. Ignore unknown values. + const auto &res = dsmr_parser::P1Parser::parse( + data, this->telegram_, this->bytes_read_, false, + this->crc_check_); // Parse telegram according to data definition. Ignore unknown values. if (res.err) { // Parsing error, show it auto err_str = res.fullError(this->telegram_, this->telegram_ + this->bytes_read_); @@ -329,7 +326,4 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) { } } -} // namespace dsmr -} // namespace esphome - -#endif // USE_ARDUINO +} // namespace esphome::dsmr diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h index 7304737b50..56ba75b5fa 100644 --- a/esphome/components/dsmr/dsmr.h +++ b/esphome/components/dsmr/dsmr.h @@ -1,24 +1,17 @@ #pragma once -#ifdef USE_ARDUINO - #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/text_sensor/text_sensor.h" #include "esphome/components/uart/uart.h" #include "esphome/core/log.h" -#include "esphome/core/defines.h" - -// don't include because it puts everything in global namespace -#include -#include - +#include +#include #include -namespace esphome { -namespace dsmr { +namespace esphome::dsmr { -using namespace ::dsmr::fields; +using namespace dsmr_parser::fields; // DSMR_**_LIST generated by ESPHome and written in esphome/core/defines @@ -44,8 +37,8 @@ using namespace ::dsmr::fields; #define DSMR_DATA_SENSOR(s) s #define DSMR_COMMA , -using MyData = ::dsmr::ParsedData; +using MyData = dsmr_parser::ParsedData; class Dsmr : public Component, public uart::UARTDevice { public: @@ -140,7 +133,4 @@ class Dsmr : public Component, public uart::UARTDevice { std::vector decryption_key_{}; bool crc_check_; }; -} // namespace dsmr -} // namespace esphome - -#endif // USE_ARDUINO +} // namespace esphome::dsmr diff --git a/esphome/components/dsmr/sensor.py b/esphome/components/dsmr/sensor.py index 0696fccdf7..7d69f79530 100644 --- a/esphome/components/dsmr/sensor.py +++ b/esphome/components/dsmr/sensor.py @@ -3,27 +3,34 @@ from esphome.components import sensor import esphome.config_validation as cv from esphome.const import ( CONF_ID, + DEVICE_CLASS_APPARENT_POWER, DEVICE_CLASS_CURRENT, + DEVICE_CLASS_DURATION, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_FREQUENCY, DEVICE_CLASS_GAS, DEVICE_CLASS_POWER, + DEVICE_CLASS_REACTIVE_POWER, DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_WATER, STATE_CLASS_MEASUREMENT, STATE_CLASS_TOTAL_INCREASING, UNIT_AMPERE, UNIT_CUBIC_METER, + UNIT_HERTZ, + UNIT_KILOVOLT_AMPS, UNIT_KILOVOLT_AMPS_REACTIVE, UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, UNIT_KILOWATT, UNIT_KILOWATT_HOURS, + UNIT_SECOND, UNIT_VOLT, ) from . import CONF_DSMR_ID, Dsmr AUTO_LOAD = ["dsmr"] - +UNIT_GIGA_JOULE = "GJ" CONFIG_SCHEMA = cv.Schema( { @@ -46,6 +53,18 @@ CONFIG_SCHEMA = cv.Schema( device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_TOTAL_INCREASING, ), + cv.Optional("energy_delivered_tariff3"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional("energy_delivered_tariff4"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), cv.Optional("energy_returned_lux"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT_HOURS, accuracy_decimals=3, @@ -64,14 +83,82 @@ CONFIG_SCHEMA = cv.Schema( device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_TOTAL_INCREASING, ), + cv.Optional("energy_returned_tariff3"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional("energy_returned_tariff4"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional("energy_delivered_tariff1_ch"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional("energy_delivered_tariff2_ch"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional("energy_returned_tariff1_ch"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional("energy_returned_tariff2_ch"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), cv.Optional("total_imported_energy"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=3, ), + cv.Optional("reactive_energy_delivered_tariff1"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, + ), + cv.Optional("reactive_energy_delivered_tariff2"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, + ), + cv.Optional("reactive_energy_delivered_tariff3"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, + ), + cv.Optional("reactive_energy_delivered_tariff4"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, + ), cv.Optional("total_exported_energy"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=3, ), + cv.Optional("reactive_energy_returned_tariff1"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, + ), + cv.Optional("reactive_energy_returned_tariff2"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, + ), + cv.Optional("reactive_energy_returned_tariff3"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, + ), + cv.Optional("reactive_energy_returned_tariff4"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, + ), cv.Optional("power_delivered"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT, accuracy_decimals=3, @@ -84,61 +171,195 @@ CONFIG_SCHEMA = cv.Schema( device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional("power_delivered_ch"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("power_returned_ch"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), cv.Optional("reactive_power_delivered"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_threshold"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_switch_position"): sensor.sensor_schema( accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_failures"): sensor.sensor_schema( accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_long_failures"): sensor.sensor_schema( accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_sags_l1"): sensor.sensor_schema( accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_sags_l2"): sensor.sensor_schema( accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_sags_l3"): sensor.sensor_schema( accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_swells_l1"): sensor.sensor_schema( accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_swells_l2"): sensor.sensor_schema( accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_swells_l3"): sensor.sensor_schema( accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_sag_time_l1"): sensor.sensor_schema( + unit_of_measurement=UNIT_SECOND, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DURATION, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_sag_time_l2"): sensor.sensor_schema( + unit_of_measurement=UNIT_SECOND, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DURATION, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_sag_time_l3"): sensor.sensor_schema( + unit_of_measurement=UNIT_SECOND, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DURATION, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_sag_l1"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_sag_l2"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_sag_l3"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_swell_time_l1"): sensor.sensor_schema( + unit_of_measurement=UNIT_SECOND, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DURATION, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_swell_time_l2"): sensor.sensor_schema( + unit_of_measurement=UNIT_SECOND, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DURATION, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_swell_time_l3"): sensor.sensor_schema( + unit_of_measurement=UNIT_SECOND, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DURATION, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_swell_l1"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_swell_l2"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_swell_l3"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("current_l1"): sensor.sensor_schema( unit_of_measurement=UNIT_AMPERE, - accuracy_decimals=1, + accuracy_decimals=3, device_class=DEVICE_CLASS_CURRENT, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("current_l2"): sensor.sensor_schema( unit_of_measurement=UNIT_AMPERE, - accuracy_decimals=1, + accuracy_decimals=3, device_class=DEVICE_CLASS_CURRENT, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("current_l3"): sensor.sensor_schema( unit_of_measurement=UNIT_AMPERE, - accuracy_decimals=1, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("current"): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("current_n"): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("current_sum"): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("current_fuse_l1"): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("current_fuse_l2"): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("current_fuse_l3"): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=3, device_class=DEVICE_CLASS_CURRENT, state_class=STATE_CLASS_MEASUREMENT, ), @@ -181,51 +402,93 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional("reactive_power_delivered_l1"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered_l2"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered_l3"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l1"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l2"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l3"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("voltage_l1"): sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, - accuracy_decimals=1, + accuracy_decimals=3, device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("voltage_l2"): sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, - accuracy_decimals=1, + accuracy_decimals=3, device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("voltage_l3"): sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, - accuracy_decimals=1, + accuracy_decimals=3, device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional("voltage_avg_l1"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_avg_l2"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_avg_l3"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("frequency"): sensor.sensor_schema( + unit_of_measurement=UNIT_HERTZ, + accuracy_decimals=3, + device_class=DEVICE_CLASS_FREQUENCY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("abs_power"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), cv.Optional("gas_delivered"): sensor.sensor_schema( unit_of_measurement=UNIT_CUBIC_METER, accuracy_decimals=3, @@ -244,6 +507,109 @@ CONFIG_SCHEMA = cv.Schema( device_class=DEVICE_CLASS_WATER, state_class=STATE_CLASS_TOTAL_INCREASING, ), + cv.Optional("thermal_delivered"): sensor.sensor_schema( + unit_of_measurement=UNIT_GIGA_JOULE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional("sub_delivered"): sensor.sensor_schema( + unit_of_measurement=UNIT_CUBIC_METER, + accuracy_decimals=3, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional("gas_device_type"): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("gas_valve_position"): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("thermal_device_type"): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("thermal_valve_position"): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("water_device_type"): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("water_valve_position"): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("sub_device_type"): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("sub_valve_position"): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("apparent_delivery_power"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("apparent_delivery_power_l1"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("apparent_delivery_power_l2"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("apparent_delivery_power_l3"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("apparent_return_power"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("apparent_return_power_l1"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("apparent_return_power_l2"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("apparent_return_power_l3"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("active_demand_power"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("active_demand_abs"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), cv.Optional( "active_energy_import_current_average_demand" ): sensor.sensor_schema( @@ -252,6 +618,90 @@ CONFIG_SCHEMA = cv.Schema( device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional( + "active_energy_export_current_average_demand" + ): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + "reactive_energy_import_current_average_demand" + ): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + "reactive_energy_export_current_average_demand" + ): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + "apparent_energy_import_current_average_demand" + ): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + "apparent_energy_export_current_average_demand" + ): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("active_energy_import_last_completed_demand"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("active_energy_export_last_completed_demand"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + "reactive_energy_import_last_completed_demand" + ): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + "reactive_energy_export_last_completed_demand" + ): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + "apparent_energy_import_last_completed_demand" + ): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + "apparent_energy_export_last_completed_demand" + ): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), cv.Optional( "active_energy_import_maximum_demand_running_month" ): sensor.sensor_schema( @@ -268,6 +718,14 @@ CONFIG_SCHEMA = cv.Schema( device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional("fw_core_version"): sensor.sensor_schema( + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("fw_module_version"): sensor.sensor_schema( + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, + ), } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/dsmr/text_sensor.py b/esphome/components/dsmr/text_sensor.py index 3223d943be..4c7455a38f 100644 --- a/esphome/components/dsmr/text_sensor.py +++ b/esphome/components/dsmr/text_sensor.py @@ -18,11 +18,15 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional("electricity_failure_log"): text_sensor.text_sensor_schema(), cv.Optional("message_short"): text_sensor.text_sensor_schema(), cv.Optional("message_long"): text_sensor.text_sensor_schema(), + cv.Optional("equipment_id"): text_sensor.text_sensor_schema(), cv.Optional("gas_equipment_id"): text_sensor.text_sensor_schema(), + cv.Optional("gas_equipment_id_be"): text_sensor.text_sensor_schema(), cv.Optional("thermal_equipment_id"): text_sensor.text_sensor_schema(), cv.Optional("water_equipment_id"): text_sensor.text_sensor_schema(), cv.Optional("sub_equipment_id"): text_sensor.text_sensor_schema(), cv.Optional("gas_delivered_text"): text_sensor.text_sensor_schema(), + cv.Optional("fw_core_checksum"): text_sensor.text_sensor_schema(), + cv.Optional("fw_module_checksum"): text_sensor.text_sensor_schema(), cv.Optional("telegram"): text_sensor.text_sensor_schema().extend( {cv.Optional(CONF_INTERNAL, default=True): cv.boolean} ), diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index 16d7089f02..ba25c69fae 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -387,14 +387,14 @@ bool ESPHomeOTAComponent::readall_(uint8_t *buf, size_t len) { while (len - at > 0) { uint32_t now = millis(); if (now - start > OTA_SOCKET_TIMEOUT_DATA) { - ESP_LOGW(TAG, "Timeout reading %d bytes", len); + ESP_LOGW(TAG, "Timeout reading %zu bytes", len); return false; } ssize_t read = this->client_->read(buf + at, len - at); if (read == -1) { if (!this->would_block_(errno)) { - ESP_LOGW(TAG, "Read err %d bytes, errno %d", len, errno); + ESP_LOGW(TAG, "Read err %zu bytes, errno %d", len, errno); return false; } } else if (read == 0) { @@ -414,14 +414,14 @@ bool ESPHomeOTAComponent::writeall_(const uint8_t *buf, size_t len) { while (len - at > 0) { uint32_t now = millis(); if (now - start > OTA_SOCKET_TIMEOUT_DATA) { - ESP_LOGW(TAG, "Timeout writing %d bytes", len); + ESP_LOGW(TAG, "Timeout writing %zu bytes", len); return false; } ssize_t written = this->client_->write(buf + at, len - at); if (written == -1) { if (!this->would_block_(errno)) { - ESP_LOGW(TAG, "Write err %d bytes, errno %d", len, errno); + ESP_LOGW(TAG, "Write err %zu bytes, errno %d", len, errno); return false; } } else { diff --git a/esphome/components/image/__init__.py b/esphome/components/image/__init__.py index bf25a7cd92..a7b788bf91 100644 --- a/esphome/components/image/__init__.py +++ b/esphome/components/image/__init__.py @@ -374,23 +374,6 @@ def is_svg_file(file): return " 500 or height > 500): _LOGGER.warning( diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index a514a7482f..ee54d5f8d3 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -1,3 +1,5 @@ +import logging + from esphome import automation import esphome.codegen as cg from esphome.config_helpers import filter_source_files_from_platform @@ -27,6 +29,8 @@ CONF_ON_PROGRESS = "on_progress" CONF_ON_STATE_CHANGE = "on_state_change" +_LOGGER = logging.getLogger(__name__) + ota_ns = cg.esphome_ns.namespace("ota") OTAComponent = ota_ns.class_("OTAComponent", cg.Component) OTAState = ota_ns.enum("OTAState") @@ -45,6 +49,10 @@ def _ota_final_validate(config): raise cv.Invalid( f"At least one platform must be specified for '{CONF_OTA}'; add '{CONF_PLATFORM}: {CONF_ESPHOME}' for original OTA functionality" ) + if CORE.is_host: + _LOGGER.warning( + "OTA not available for platform 'host'. OTA functionality disabled." + ) FINAL_VALIDATE_SCHEMA = _ota_final_validate diff --git a/esphome/components/ota/ota_backend_host.cpp b/esphome/components/ota/ota_backend_host.cpp new file mode 100644 index 0000000000..ddab174bed --- /dev/null +++ b/esphome/components/ota/ota_backend_host.cpp @@ -0,0 +1,24 @@ +#ifdef USE_HOST +#include "ota_backend_host.h" + +#include "esphome/core/defines.h" + +namespace esphome::ota { + +// Stub implementation - OTA is not supported on host platform. +// All methods return error codes to allow compilation of configs with OTA triggers. + +std::unique_ptr make_ota_backend() { return make_unique(); } + +OTAResponseTypes HostOTABackend::begin(size_t image_size) { return OTA_RESPONSE_ERROR_UPDATE_PREPARE; } + +void HostOTABackend::set_update_md5(const char *expected_md5) {} + +OTAResponseTypes HostOTABackend::write(uint8_t *data, size_t len) { return OTA_RESPONSE_ERROR_WRITING_FLASH; } + +OTAResponseTypes HostOTABackend::end() { return OTA_RESPONSE_ERROR_UPDATE_END; } + +void HostOTABackend::abort() {} + +} // namespace esphome::ota +#endif diff --git a/esphome/components/ota/ota_backend_host.h b/esphome/components/ota/ota_backend_host.h new file mode 100644 index 0000000000..ae7d0cb0b3 --- /dev/null +++ b/esphome/components/ota/ota_backend_host.h @@ -0,0 +1,21 @@ +#pragma once +#ifdef USE_HOST +#include "ota_backend.h" + +namespace esphome::ota { + +/// Stub OTA backend for host platform - allows compilation but does not implement OTA. +/// All operations return error codes immediately. This enables configurations with +/// OTA triggers to compile for host platform during development. +class HostOTABackend : public OTABackend { + public: + OTAResponseTypes begin(size_t image_size) override; + void set_update_md5(const char *md5) override; + OTAResponseTypes write(uint8_t *data, size_t len) override; + OTAResponseTypes end() override; + void abort() override; + bool supports_compression() override { return false; } +}; + +} // namespace esphome::ota +#endif diff --git a/platformio.ini b/platformio.ini index e58989c566..dd9eb566c5 100644 --- a/platformio.ini +++ b/platformio.ini @@ -38,6 +38,8 @@ lib_deps_base = wjtje/qr-code-generator-library@1.7.0 ; qr_code functionpointer/arduino-MLX90393@1.0.2 ; mlx90393 pavlodn/HaierProtocol@0.9.31 ; haier + esphome/dsmr_parser@1.0.0 ; dsmr + polargoose/Crypto-no-arduino@0.4.0 ; dsmr https://github.com/esphome/TinyGPSPlus.git#v1.1.0 ; gps ; This is using the repository until a new release is published to PlatformIO https://github.com/Sensirion/arduino-gas-index-algorithm.git#3.2.1 ; Sensirion Gas Index Algorithm Arduino Library @@ -82,8 +84,6 @@ lib_deps = heman/AsyncMqttClient-esphome@1.0.0 ; mqtt fastled/FastLED@3.9.16 ; fastled_base freekode/TM1651@1.0.1 ; tm1651 - glmnet/Dsmr@0.7 ; dsmr - rweather/Crypto@0.4.0 ; dsmr dudanov/MideaUART@1.1.9 ; midea tonia/HeatpumpIR@1.0.37 ; heatpumpir build_flags = diff --git a/requirements.txt b/requirements.txt index 6631cb55bd..56df559cd7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,13 +19,7 @@ ruamel.yaml==0.19.1 # dashboard_import ruamel.yaml.clib==0.2.15 # dashboard_import esphome-glyphsets==0.2.0 pillow==11.3.0 - -# pycairo fork for Windows -cairosvg @ git+https://github.com/clydebarrow/cairosvg.git@release ; sys_platform == 'win32' - -# Original for everything else -cairosvg==2.8.2 ; sys_platform != 'win32' - +resvg-py==0.2.5 freetype-py==2.5.1 jinja2==3.1.6 bleak==2.1.1 diff --git a/script/ci-custom.py b/script/ci-custom.py index f0676d594b..cf59c3883b 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -580,6 +580,7 @@ def lint_relative_py_import(fname: Path, line, col, content): ], exclude=[ "esphome/components/socket/headers.h", + "esphome/components/async_tcp/async_tcp.h", "esphome/components/esp32/core.cpp", "esphome/components/esp8266/core.cpp", "esphome/components/rp2040/core.cpp", diff --git a/tests/components/dsmr/test.esp32-idf.yaml b/tests/components/dsmr/test.esp32-idf.yaml new file mode 100644 index 0000000000..522f60db49 --- /dev/null +++ b/tests/components/dsmr/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +substitutions: + request_pin: GPIO15 + +packages: + uart: !include ../../test_build_components/common/uart/esp32-idf.yaml + +<<: !include common.yaml diff --git a/tests/components/ota/test.host.yaml b/tests/components/ota/test.host.yaml new file mode 100644 index 0000000000..ae7c4d0add --- /dev/null +++ b/tests/components/ota/test.host.yaml @@ -0,0 +1,4 @@ +<<: !include common.yaml + +#host platform does not support wifi / network is automatically included +wifi: !remove