diff --git a/.clang-tidy.hash b/.clang-tidy.hash index 30cf982649..a6de2366bc 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -6af8b429b94191fe8e239fcb3b73f7982d0266cb5b05ffbc81edaeac1bc8c273 +0440e35cf89a49e8a35fd3690ed453a72b7b6f61b9d346ced6140e1c0d39dff6 diff --git a/.github/workflows/auto-label-pr.yml b/.github/workflows/auto-label-pr.yml index 63f059eb6d..c42b5330d2 100644 --- a/.github/workflows/auto-label-pr.yml +++ b/.github/workflows/auto-label-pr.yml @@ -105,7 +105,9 @@ jobs: // Calculate data from PR files const changedFiles = prFiles.map(file => file.filename); - const totalChanges = prFiles.reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0); + const totalAdditions = prFiles.reduce((sum, file) => sum + (file.additions || 0), 0); + const totalDeletions = prFiles.reduce((sum, file) => sum + (file.deletions || 0), 0); + const totalChanges = totalAdditions + totalDeletions; console.log('Current labels:', currentLabels.join(', ')); console.log('Changed files:', changedFiles.length); @@ -231,16 +233,21 @@ jobs: // Strategy: PR size detection async function detectPRSize() { const labels = new Set(); - const testChanges = prFiles - .filter(file => file.filename.startsWith('tests/')) - .reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0); - - const nonTestChanges = totalChanges - testChanges; if (totalChanges <= SMALL_PR_THRESHOLD) { labels.add('small-pr'); + return labels; } + const testAdditions = prFiles + .filter(file => file.filename.startsWith('tests/')) + .reduce((sum, file) => sum + (file.additions || 0), 0); + const testDeletions = prFiles + .filter(file => file.filename.startsWith('tests/')) + .reduce((sum, file) => sum + (file.deletions || 0), 0); + + const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions); + // Don't add too-big if mega-pr label is already present if (nonTestChanges > TOO_BIG_THRESHOLD && !isMegaPR) { labels.add('too-big'); @@ -375,7 +382,7 @@ jobs: const labels = new Set(); // Check for missing tests - if ((allLabels.has('new-component') || allLabels.has('new-platform')) && !allLabels.has('has-tests')) { + if ((allLabels.has('new-component') || allLabels.has('new-platform') || allLabels.has('new-feature')) && !allLabels.has('has-tests')) { labels.add('needs-tests'); } @@ -412,10 +419,13 @@ jobs: // Too big message if (finalLabels.includes('too-big')) { - const testChanges = prFiles + const testAdditions = prFiles .filter(file => file.filename.startsWith('tests/')) - .reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0); - const nonTestChanges = totalChanges - testChanges; + .reduce((sum, file) => sum + (file.additions || 0), 0); + const testDeletions = prFiles + .filter(file => file.filename.startsWith('tests/')) + .reduce((sum, file) => sum + (file.deletions || 0), 0); + const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions); const tooManyLabels = finalLabels.length > MAX_LABELS; const tooManyChanges = nonTestChanges > TOO_BIG_THRESHOLD; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e50f2aef40..8f429f7b40 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -156,7 +156,7 @@ jobs: . venv/bin/activate pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/ - name: Upload coverage to Codecov - uses: codecov/codecov-action@v5.4.3 + uses: codecov/codecov-action@v5.5.0 with: token: ${{ secrets.CODECOV_TOKEN }} - name: Save Python virtual environment cache diff --git a/esphome/components/ags10/ags10.cpp b/esphome/components/ags10/ags10.cpp index 9a29a979f3..fa7170114c 100644 --- a/esphome/components/ags10/ags10.cpp +++ b/esphome/components/ags10/ags10.cpp @@ -89,7 +89,7 @@ void AGS10Component::dump_config() { bool AGS10Component::new_i2c_address(uint8_t newaddress) { uint8_t rev_newaddress = ~newaddress; std::array data{newaddress, rev_newaddress, newaddress, rev_newaddress, 0}; - data[4] = calc_crc8_(data, 4); + data[4] = crc8(data.data(), 4, 0xFF, 0x31, true); if (!this->write_bytes(REG_ADDRESS, data)) { this->error_code_ = COMMUNICATION_FAILED; this->status_set_warning(); @@ -109,7 +109,7 @@ bool AGS10Component::set_zero_point_with_current_resistance() { return this->set bool AGS10Component::set_zero_point_with(uint16_t value) { std::array data{0x00, 0x0C, (uint8_t) ((value >> 8) & 0xFF), (uint8_t) (value & 0xFF), 0}; - data[4] = calc_crc8_(data, 4); + data[4] = crc8(data.data(), 4, 0xFF, 0x31, true); if (!this->write_bytes(REG_CALIBRATION, data)) { this->error_code_ = COMMUNICATION_FAILED; this->status_set_warning(); @@ -184,7 +184,7 @@ template optional> AGS10Component::read_and_che auto res = *data; auto crc_byte = res[len]; - if (crc_byte != calc_crc8_(res, len)) { + if (crc_byte != crc8(res.data(), len, 0xFF, 0x31, true)) { this->error_code_ = CRC_CHECK_FAILED; ESP_LOGE(TAG, "Reading AGS10 version failed: crc error!"); return optional>(); @@ -192,20 +192,5 @@ template optional> AGS10Component::read_and_che return data; } - -template uint8_t AGS10Component::calc_crc8_(std::array dat, uint8_t num) { - uint8_t i, byte1, crc = 0xFF; - for (byte1 = 0; byte1 < num; byte1++) { - crc ^= (dat[byte1]); - for (i = 0; i < 8; i++) { - if (crc & 0x80) { - crc = (crc << 1) ^ 0x31; - } else { - crc = (crc << 1); - } - } - } - return crc; -} } // namespace ags10 } // namespace esphome diff --git a/esphome/components/ags10/ags10.h b/esphome/components/ags10/ags10.h index 3e184ae176..e0975f14bc 100644 --- a/esphome/components/ags10/ags10.h +++ b/esphome/components/ags10/ags10.h @@ -1,9 +1,9 @@ #pragma once +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" #include "esphome/core/automation.h" #include "esphome/core/component.h" -#include "esphome/components/sensor/sensor.h" -#include "esphome/components/i2c/i2c.h" namespace esphome { namespace ags10 { @@ -99,16 +99,6 @@ class AGS10Component : public PollingComponent, public i2c::I2CDevice { * Read, checks and returns data from the sensor. */ template optional> read_and_check_(uint8_t a_register); - - /** - * Calculates CRC8 value. - * - * CRC8 calculation, initial value: 0xFF, polynomial: 0x31 (x8+ x5+ x4+1) - * - * @param[in] dat the data buffer - * @param num number of bytes in the buffer - */ - template uint8_t calc_crc8_(std::array dat, uint8_t num); }; template class AGS10NewI2cAddressAction : public Action, public Parented { diff --git a/esphome/components/airthings_ble/__init__.py b/esphome/components/airthings_ble/__init__.py index eae400ab39..1545110798 100644 --- a/esphome/components/airthings_ble/__init__.py +++ b/esphome/components/airthings_ble/__init__.py @@ -18,6 +18,6 @@ CONFIG_SCHEMA = cv.Schema( ).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) -def to_code(config): +async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - yield esp32_ble_tracker.register_ble_device(var, config) + await esp32_ble_tracker.register_ble_device(var, config) diff --git a/esphome/components/am2315c/am2315c.cpp b/esphome/components/am2315c/am2315c.cpp index 048c34d749..b20a8c6cbb 100644 --- a/esphome/components/am2315c/am2315c.cpp +++ b/esphome/components/am2315c/am2315c.cpp @@ -29,22 +29,6 @@ namespace am2315c { static const char *const TAG = "am2315c"; -uint8_t AM2315C::crc8_(uint8_t *data, uint8_t len) { - uint8_t crc = 0xFF; - while (len--) { - crc ^= *data++; - for (uint8_t i = 0; i < 8; i++) { - if (crc & 0x80) { - crc <<= 1; - crc ^= 0x31; - } else { - crc <<= 1; - } - } - } - return crc; -} - bool AM2315C::reset_register_(uint8_t reg) { // code based on demo code sent by www.aosong.com // no further documentation. @@ -86,7 +70,7 @@ bool AM2315C::convert_(uint8_t *data, float &humidity, float &temperature) { humidity = raw * 9.5367431640625e-5; raw = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5]; temperature = raw * 1.9073486328125e-4 - 50; - return this->crc8_(data, 6) == data[6]; + return crc8(data, 6, 0xFF, 0x31, true) == data[6]; } void AM2315C::setup() { diff --git a/esphome/components/am2315c/am2315c.h b/esphome/components/am2315c/am2315c.h index 9cec40e4c2..c8d01beeaa 100644 --- a/esphome/components/am2315c/am2315c.h +++ b/esphome/components/am2315c/am2315c.h @@ -21,9 +21,9 @@ // SOFTWARE. #pragma once -#include "esphome/core/component.h" -#include "esphome/components/sensor/sensor.h" #include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" namespace esphome { namespace am2315c { @@ -39,7 +39,6 @@ class AM2315C : public PollingComponent, public i2c::I2CDevice { void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; } protected: - uint8_t crc8_(uint8_t *data, uint8_t len); bool convert_(uint8_t *data, float &humidity, float &temperature); bool reset_register_(uint8_t reg); diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index ba5f994e9a..2672ea1edb 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -321,6 +321,7 @@ HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value( HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA, ) async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args): + cg.add_define("USE_API_HOMEASSISTANT_SERVICES") serv = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, serv, True) cg.add(var.set_service("esphome.tag_scanned")) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 7c4abbe47f..fd4ef021e1 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -468,9 +468,7 @@ uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection * resp.cold_white = values.get_cold_white(); resp.warm_white = values.get_warm_white(); if (light->supports_effects()) { - // get_effect_name() returns temporary std::string - must store it - std::string effect_name = light->get_effect_name(); - resp.set_effect(StringRef(effect_name)); + resp.set_effect(light->get_effect_name_ref()); } return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } @@ -1434,9 +1432,7 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION); resp.set_esphome_version(ESPHOME_VERSION_REF); - // get_compilation_time() returns temporary std::string - must store it - std::string compilation_time = App.get_compilation_time(); - resp.set_compilation_time(StringRef(compilation_time)); + resp.set_compilation_time(App.get_compilation_time_ref()); // Compile-time StringRef constants for manufacturers #if defined(USE_ESP8266) || defined(USE_ESP32) diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index e07ba315f4..6eb964e482 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -306,9 +306,15 @@ class APIConnection final : public APIServerConnection { APIConnection *conn, uint32_t remaining_size, bool is_single) { // Set common fields that are shared by all entity types msg.key = entity->get_object_id_hash(); - // IMPORTANT: get_object_id() may return a temporary std::string - std::string object_id = entity->get_object_id(); - msg.set_object_id(StringRef(object_id)); + // Try to use static reference first to avoid allocation + StringRef static_ref = entity->get_object_id_ref_for_api_(); + if (!static_ref.empty()) { + msg.set_object_id(static_ref); + } else { + // Dynamic case - need to allocate + std::string object_id = entity->get_object_id(); + msg.set_object_id(StringRef(object_id)); + } if (entity->has_own_name()) { msg.set_name(entity->get_name()); diff --git a/esphome/components/binary_sensor/binary_sensor.cpp b/esphome/components/binary_sensor/binary_sensor.cpp index 02b83af552..e652d302b6 100644 --- a/esphome/components/binary_sensor/binary_sensor.cpp +++ b/esphome/components/binary_sensor/binary_sensor.cpp @@ -7,6 +7,19 @@ namespace binary_sensor { static const char *const TAG = "binary_sensor"; +// Function implementation of LOG_BINARY_SENSOR macro to reduce code size +void log_binary_sensor(const char *tag, const char *prefix, const char *type, BinarySensor *obj) { + if (obj == nullptr) { + return; + } + + ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str()); + + if (!obj->get_device_class().empty()) { + ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->get_device_class().c_str()); + } +} + void BinarySensor::publish_state(bool new_state) { if (this->filter_list_ == nullptr) { this->send_state_internal(new_state); diff --git a/esphome/components/binary_sensor/binary_sensor.h b/esphome/components/binary_sensor/binary_sensor.h index d61be7a49b..2bd17d97c9 100644 --- a/esphome/components/binary_sensor/binary_sensor.h +++ b/esphome/components/binary_sensor/binary_sensor.h @@ -10,13 +10,10 @@ namespace esphome { namespace binary_sensor { -#define LOG_BINARY_SENSOR(prefix, type, obj) \ - if ((obj) != nullptr) { \ - ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ - if (!(obj)->get_device_class().empty()) { \ - ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \ - } \ - } +class BinarySensor; +void log_binary_sensor(const char *tag, const char *prefix, const char *type, BinarySensor *obj); + +#define LOG_BINARY_SENSOR(prefix, type, obj) log_binary_sensor(TAG, prefix, LOG_STR_LITERAL(type), obj) #define SUB_BINARY_SENSOR(name) \ protected: \ diff --git a/esphome/components/ble_client/output/__init__.py b/esphome/components/ble_client/output/__init__.py index 729885eb8b..22a6b29442 100644 --- a/esphome/components/ble_client/output/__init__.py +++ b/esphome/components/ble_client/output/__init__.py @@ -27,7 +27,7 @@ CONFIG_SCHEMA = cv.All( ) -def to_code(config): +async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): cg.add( @@ -63,6 +63,6 @@ def to_code(config): ) cg.add(var.set_char_uuid128(uuid128)) cg.add(var.set_require_response(config[CONF_REQUIRE_RESPONSE])) - yield output.register_output(var, config) - yield ble_client.register_ble_node(var, config) - yield cg.register_component(var, config) + await output.register_output(var, config) + await ble_client.register_ble_node(var, config) + await cg.register_component(var, config) diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.h b/esphome/components/bluetooth_proxy/bluetooth_connection.h index a975d25d91..e5d5ff2dd6 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.h +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.h @@ -8,7 +8,7 @@ namespace esphome::bluetooth_proxy { class BluetoothProxy; -class BluetoothConnection : public esp32_ble_client::BLEClientBase { +class BluetoothConnection final : public esp32_ble_client::BLEClientBase { public: void dump_config() override; void loop() override; diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.h b/esphome/components/bluetooth_proxy/bluetooth_proxy.h index bc8d3ed762..c81c8c9532 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.h +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.h @@ -50,7 +50,7 @@ enum BluetoothProxySubscriptionFlag : uint32_t { SUBSCRIPTION_RAW_ADVERTISEMENTS = 1 << 0, }; -class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Component { +class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, public Component { friend class BluetoothConnection; // Allow connection to update connections_free_response_ public: BluetoothProxy(); diff --git a/esphome/components/button/button.cpp b/esphome/components/button/button.cpp index 4c4cb7740c..63d71dcb8a 100644 --- a/esphome/components/button/button.cpp +++ b/esphome/components/button/button.cpp @@ -6,6 +6,19 @@ namespace button { static const char *const TAG = "button"; +// Function implementation of LOG_BUTTON macro to reduce code size +void log_button(const char *tag, const char *prefix, const char *type, Button *obj) { + if (obj == nullptr) { + return; + } + + ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str()); + + if (!obj->get_icon().empty()) { + ESP_LOGCONFIG(tag, "%s Icon: '%s'", prefix, obj->get_icon().c_str()); + } +} + void Button::press() { ESP_LOGD(TAG, "'%s' Pressed.", this->get_name().c_str()); this->press_action(); diff --git a/esphome/components/button/button.h b/esphome/components/button/button.h index 9488eca221..75b76f9dcf 100644 --- a/esphome/components/button/button.h +++ b/esphome/components/button/button.h @@ -7,13 +7,10 @@ namespace esphome { namespace button { -#define LOG_BUTTON(prefix, type, obj) \ - if ((obj) != nullptr) { \ - ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ - if (!(obj)->get_icon().empty()) { \ - ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ - } \ - } +class Button; +void log_button(const char *tag, const char *prefix, const char *type, Button *obj); + +#define LOG_BUTTON(prefix, type, obj) log_button(TAG, prefix, LOG_STR_LITERAL(type), obj) #define SUB_BUTTON(name) \ protected: \ diff --git a/esphome/components/ccs811/ccs811.cpp b/esphome/components/ccs811/ccs811.cpp index cecb92b3df..2617d7577a 100644 --- a/esphome/components/ccs811/ccs811.cpp +++ b/esphome/components/ccs811/ccs811.cpp @@ -153,8 +153,8 @@ void CCS811Component::dump_config() { ESP_LOGCONFIG(TAG, "CCS811"); LOG_I2C_DEVICE(this) LOG_UPDATE_INTERVAL(this) - LOG_SENSOR(" ", "CO2 Sensor", this->co2_) - LOG_SENSOR(" ", "TVOC Sensor", this->tvoc_) + LOG_SENSOR(" ", "CO2 Sensor", this->co2_); + LOG_SENSOR(" ", "TVOC Sensor", this->tvoc_); LOG_TEXT_SENSOR(" ", "Firmware Version Sensor", this->version_) if (this->baseline_) { ESP_LOGCONFIG(TAG, " Baseline: %04X", *this->baseline_); diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index 0e01eb336f..383cfaf8fb 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -228,9 +228,9 @@ async def cover_stop_to_code(config, action_id, template_arg, args): @automation.register_action("cover.toggle", ToggleAction, COVER_ACTION_SCHEMA) -def cover_toggle_to_code(config, action_id, template_arg, args): - paren = yield cg.get_variable(config[CONF_ID]) - yield cg.new_Pvariable(action_id, template_arg, paren) +async def cover_toggle_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(action_id, template_arg, paren) COVER_CONTROL_ACTION_SCHEMA = cv.Schema( diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp index 2d84436d84..e23be2e0c1 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.cpp +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -159,8 +159,7 @@ void BLEClientBase::disconnect() { return; } if (this->state_ == espbt::ClientState::CONNECTING || this->conn_id_ == UNSET_CONN_ID) { - ESP_LOGW(TAG, "[%d] [%s] Disconnecting before connected, disconnect scheduled.", this->connection_index_, - this->address_str_.c_str()); + this->log_warning_("Disconnect before connected, disconnect scheduled."); this->want_disconnect_ = true; return; } @@ -172,13 +171,11 @@ void BLEClientBase::unconditional_disconnect() { ESP_LOGI(TAG, "[%d] [%s] Disconnecting (conn_id: %d).", this->connection_index_, this->address_str_.c_str(), this->conn_id_); if (this->state_ == espbt::ClientState::DISCONNECTING) { - ESP_LOGE(TAG, "[%d] [%s] Tried to disconnect while already disconnecting.", this->connection_index_, - this->address_str_.c_str()); + this->log_error_("Already disconnecting"); return; } if (this->conn_id_ == UNSET_CONN_ID) { - ESP_LOGE(TAG, "[%d] [%s] No connection ID set, cannot disconnect.", this->connection_index_, - this->address_str_.c_str()); + this->log_error_("conn id unset, cannot disconnect"); return; } auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_); @@ -234,6 +231,18 @@ void BLEClientBase::log_connection_params_(const char *param_type) { ESP_LOGD(TAG, "[%d] [%s] %s conn params", this->connection_index_, this->address_str_.c_str(), param_type); } +void BLEClientBase::log_error_(const char *message) { + ESP_LOGE(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_.c_str(), message); +} + +void BLEClientBase::log_error_(const char *message, int code) { + ESP_LOGE(TAG, "[%d] [%s] %s=%d", this->connection_index_, this->address_str_.c_str(), message, code); +} + +void BLEClientBase::log_warning_(const char *message) { + ESP_LOGW(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_.c_str(), message); +} + void BLEClientBase::restore_medium_conn_params_() { // Restore to medium connection parameters after initial connection phase // This balances performance with bandwidth usage for normal operation @@ -264,8 +273,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ this->app_id); this->gattc_if_ = esp_gattc_if; } else { - ESP_LOGE(TAG, "[%d] [%s] gattc app registration failed id=%d code=%d", this->connection_index_, - this->address_str_.c_str(), param->reg.app_id, param->reg.status); + this->log_error_("gattc app registration failed status", param->reg.status); this->status_ = param->reg.status; this->mark_failed(); } @@ -281,8 +289,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ // This should not happen but lets log it in case it does // because it means we have a bad assumption about how the // ESP BT stack works. - ESP_LOGE(TAG, "[%d] [%s] Got ESP_GATTC_OPEN_EVT while in %s state, status=%d", this->connection_index_, - this->address_str_.c_str(), espbt::client_state_to_string(this->state_), param->open.status); + this->log_error_("ESP_GATTC_OPEN_EVT wrong state status", param->open.status); } if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) { this->log_gattc_warning_("Connection open", param->open.status); @@ -307,7 +314,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ this->state_ = espbt::ClientState::ESTABLISHED; break; } - ESP_LOGD(TAG, "[%d] [%s] Searching for services", this->connection_index_, this->address_str_.c_str()); + this->log_event_("Searching for services"); esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr); break; } @@ -332,8 +339,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ // Check if we were disconnected while waiting for service discovery if (param->disconnect.reason == ESP_GATT_CONN_TERMINATE_PEER_USER && this->state_ == espbt::ClientState::CONNECTED) { - ESP_LOGW(TAG, "[%d] [%s] Disconnected by remote during service discovery", this->connection_index_, - this->address_str_.c_str()); + this->log_warning_("Remote closed during discovery"); } else { ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason 0x%02x", this->connection_index_, this->address_str_.c_str(), param->disconnect.reason); @@ -506,16 +512,14 @@ void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_ return; esp_bd_addr_t bd_addr; memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t)); - ESP_LOGI(TAG, "[%d] [%s] auth complete. remote BD_ADDR: %s", this->connection_index_, this->address_str_.c_str(), + ESP_LOGI(TAG, "[%d] [%s] auth complete addr: %s", this->connection_index_, this->address_str_.c_str(), format_hex(bd_addr, 6).c_str()); if (!param->ble_security.auth_cmpl.success) { - ESP_LOGE(TAG, "[%d] [%s] auth fail reason = 0x%x", this->connection_index_, this->address_str_.c_str(), - param->ble_security.auth_cmpl.fail_reason); + this->log_error_("auth fail reason", param->ble_security.auth_cmpl.fail_reason); } else { this->paired_ = true; - ESP_LOGD(TAG, "[%d] [%s] auth success. address type = %d auth mode = %d", this->connection_index_, - this->address_str_.c_str(), param->ble_security.auth_cmpl.addr_type, - param->ble_security.auth_cmpl.auth_mode); + ESP_LOGD(TAG, "[%d] [%s] auth success type = %d mode = %d", this->connection_index_, this->address_str_.c_str(), + param->ble_security.auth_cmpl.addr_type, param->ble_security.auth_cmpl.auth_mode); } break; diff --git a/esphome/components/esp32_ble_client/ble_client_base.h b/esphome/components/esp32_ble_client/ble_client_base.h index 2d00688dbd..1850b2c5b3 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.h +++ b/esphome/components/esp32_ble_client/ble_client_base.h @@ -137,6 +137,10 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { void log_gattc_warning_(const char *operation, esp_gatt_status_t status); void log_gattc_warning_(const char *operation, esp_err_t err); void log_connection_params_(const char *param_type); + // Compact error logging helpers to reduce flash usage + void log_error_(const char *message); + void log_error_(const char *message, int code); + void log_warning_(const char *message); }; } // namespace esphome::esp32_ble_client diff --git a/esphome/components/grove_gas_mc_v2/grove_gas_mc_v2.cpp b/esphome/components/grove_gas_mc_v2/grove_gas_mc_v2.cpp index 4842ee5d06..52ec8433a2 100644 --- a/esphome/components/grove_gas_mc_v2/grove_gas_mc_v2.cpp +++ b/esphome/components/grove_gas_mc_v2/grove_gas_mc_v2.cpp @@ -58,10 +58,10 @@ void GroveGasMultichannelV2Component::dump_config() { ESP_LOGCONFIG(TAG, "Grove Multichannel Gas Sensor V2"); LOG_I2C_DEVICE(this) LOG_UPDATE_INTERVAL(this) - LOG_SENSOR(" ", "Nitrogen Dioxide", this->nitrogen_dioxide_sensor_) - LOG_SENSOR(" ", "Ethanol", this->ethanol_sensor_) - LOG_SENSOR(" ", "Carbon Monoxide", this->carbon_monoxide_sensor_) - LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_) + LOG_SENSOR(" ", "Nitrogen Dioxide", this->nitrogen_dioxide_sensor_); + LOG_SENSOR(" ", "Ethanol", this->ethanol_sensor_); + LOG_SENSOR(" ", "Carbon Monoxide", this->carbon_monoxide_sensor_); + LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_); if (this->is_failed()) { switch (this->error_code_) { diff --git a/esphome/components/hlw8012/hlw8012.cpp b/esphome/components/hlw8012/hlw8012.cpp index a28678e630..f293185cce 100644 --- a/esphome/components/hlw8012/hlw8012.cpp +++ b/esphome/components/hlw8012/hlw8012.cpp @@ -43,10 +43,10 @@ void HLW8012Component::dump_config() { " Voltage Divider: %.1f", this->change_mode_every_, this->current_resistor_ * 1000.0f, this->voltage_divider_); LOG_UPDATE_INTERVAL(this) - LOG_SENSOR(" ", "Voltage", this->voltage_sensor_) - LOG_SENSOR(" ", "Current", this->current_sensor_) - LOG_SENSOR(" ", "Power", this->power_sensor_) - LOG_SENSOR(" ", "Energy", this->energy_sensor_) + LOG_SENSOR(" ", "Voltage", this->voltage_sensor_); + LOG_SENSOR(" ", "Current", this->current_sensor_); + LOG_SENSOR(" ", "Power", this->power_sensor_); + LOG_SENSOR(" ", "Energy", this->energy_sensor_); } float HLW8012Component::get_setup_priority() const { return setup_priority::DATA; } void HLW8012Component::update() { diff --git a/esphome/components/hte501/hte501.cpp b/esphome/components/hte501/hte501.cpp index 75770ceffe..fa81640f50 100644 --- a/esphome/components/hte501/hte501.cpp +++ b/esphome/components/hte501/hte501.cpp @@ -12,7 +12,7 @@ void HTE501Component::setup() { this->write(address, 2, false); uint8_t identification[9]; this->read(identification, 9); - if (identification[8] != calc_crc8_(identification, 0, 7)) { + if (identification[8] != crc8(identification, 8, 0xFF, 0x31, true)) { this->error_code_ = CRC_CHECK_FAILED; this->mark_failed(); return; @@ -46,7 +46,8 @@ void HTE501Component::update() { this->set_timeout(50, [this]() { uint8_t i2c_response[6]; this->read(i2c_response, 6); - if (i2c_response[2] != calc_crc8_(i2c_response, 0, 1) && i2c_response[5] != calc_crc8_(i2c_response, 3, 4)) { + if (i2c_response[2] != crc8(i2c_response, 2, 0xFF, 0x31, true) && + i2c_response[5] != crc8(i2c_response + 3, 2, 0xFF, 0x31, true)) { this->error_code_ = CRC_CHECK_FAILED; this->status_set_warning(); return; @@ -67,24 +68,5 @@ void HTE501Component::update() { this->status_clear_warning(); }); } - -unsigned char HTE501Component::calc_crc8_(const unsigned char buf[], unsigned char from, unsigned char to) { - unsigned char crc_val = 0xFF; - unsigned char i = 0; - unsigned char j = 0; - for (i = from; i <= to; i++) { - int cur_val = buf[i]; - for (j = 0; j < 8; j++) { - if (((crc_val ^ cur_val) & 0x80) != 0) // If MSBs are not equal - { - crc_val = ((crc_val << 1) ^ 0x31); - } else { - crc_val = (crc_val << 1); - } - cur_val = cur_val << 1; - } - } - return crc_val; -} } // namespace hte501 } // namespace esphome diff --git a/esphome/components/hte501/hte501.h b/esphome/components/hte501/hte501.h index 0d2c952e81..a7072d5bdb 100644 --- a/esphome/components/hte501/hte501.h +++ b/esphome/components/hte501/hte501.h @@ -1,8 +1,8 @@ #pragma once -#include "esphome/core/component.h" -#include "esphome/components/sensor/sensor.h" #include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" namespace esphome { namespace hte501 { @@ -19,7 +19,6 @@ class HTE501Component : public PollingComponent, public i2c::I2CDevice { void update() override; protected: - unsigned char calc_crc8_(const unsigned char buf[], unsigned char from, unsigned char to); sensor::Sensor *temperature_sensor_; sensor::Sensor *humidity_sensor_; diff --git a/esphome/components/lc709203f/lc709203f.cpp b/esphome/components/lc709203f/lc709203f.cpp index e5d12a75d4..f711cb4f0e 100644 --- a/esphome/components/lc709203f/lc709203f.cpp +++ b/esphome/components/lc709203f/lc709203f.cpp @@ -1,5 +1,6 @@ -#include "esphome/core/log.h" #include "lc709203f.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" namespace esphome { namespace lc709203f { @@ -189,7 +190,7 @@ uint8_t Lc709203f::get_register_(uint8_t register_to_read, uint16_t *register_va // Error on the i2c bus this->status_set_warning( str_sprintf("Error code %d when reading from register 0x%02X", return_code, register_to_read).c_str()); - } else if (this->crc8_(read_buffer, 5) != read_buffer[5]) { + } else if (crc8(read_buffer, 5, 0x00, 0x07, true) != read_buffer[5]) { // I2C indicated OK, but the CRC of the data does not matcth. this->status_set_warning(str_sprintf("CRC error reading from register 0x%02X", register_to_read).c_str()); } else { @@ -220,7 +221,7 @@ uint8_t Lc709203f::set_register_(uint8_t register_to_set, uint16_t value_to_set) write_buffer[1] = register_to_set; write_buffer[2] = value_to_set & 0xFF; // Low byte write_buffer[3] = (value_to_set >> 8) & 0xFF; // High byte - write_buffer[4] = this->crc8_(write_buffer, 4); + write_buffer[4] = crc8(write_buffer, 4, 0x00, 0x07, true); for (uint8_t i = 0; i <= LC709203F_I2C_RETRY_COUNT; i++) { // Note: we don't write the first byte of the write buffer to the device. @@ -239,20 +240,6 @@ uint8_t Lc709203f::set_register_(uint8_t register_to_set, uint16_t value_to_set) return return_code; } -uint8_t Lc709203f::crc8_(uint8_t *byte_buffer, uint8_t length_of_crc) { - uint8_t crc = 0x00; - const uint8_t polynomial(0x07); - - for (uint8_t j = length_of_crc; j; --j) { - crc ^= *byte_buffer++; - - for (uint8_t i = 8; i; --i) { - crc = (crc & 0x80) ? (crc << 1) ^ polynomial : (crc << 1); - } - } - return crc; -} - void Lc709203f::set_pack_size(uint16_t pack_size) { static const uint16_t PACK_SIZE_ARRAY[6] = {100, 200, 500, 1000, 2000, 3000}; static const uint16_t APA_ARRAY[6] = {0x08, 0x0B, 0x10, 0x19, 0x2D, 0x36}; diff --git a/esphome/components/lc709203f/lc709203f.h b/esphome/components/lc709203f/lc709203f.h index 3b5b04775f..59988a0079 100644 --- a/esphome/components/lc709203f/lc709203f.h +++ b/esphome/components/lc709203f/lc709203f.h @@ -1,8 +1,8 @@ #pragma once -#include "esphome/core/component.h" -#include "esphome/components/sensor/sensor.h" #include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" namespace esphome { namespace lc709203f { @@ -38,7 +38,6 @@ class Lc709203f : public sensor::Sensor, public PollingComponent, public i2c::I2 private: uint8_t get_register_(uint8_t register_to_read, uint16_t *register_value); uint8_t set_register_(uint8_t register_to_set, uint16_t value_to_set); - uint8_t crc8_(uint8_t *byte_buffer, uint8_t length_of_crc); protected: sensor::Sensor *voltage_sensor_{nullptr}; diff --git a/esphome/components/ld2420/text_sensor/text_sensor.cpp b/esphome/components/ld2420/text_sensor/ld2420_text_sensor.cpp similarity index 91% rename from esphome/components/ld2420/text_sensor/text_sensor.cpp rename to esphome/components/ld2420/text_sensor/ld2420_text_sensor.cpp index 73af3b3660..f647a36936 100644 --- a/esphome/components/ld2420/text_sensor/text_sensor.cpp +++ b/esphome/components/ld2420/text_sensor/ld2420_text_sensor.cpp @@ -1,4 +1,4 @@ -#include "text_sensor.h" +#include "ld2420_text_sensor.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" diff --git a/esphome/components/ld2420/text_sensor/text_sensor.h b/esphome/components/ld2420/text_sensor/ld2420_text_sensor.h similarity index 100% rename from esphome/components/ld2420/text_sensor/text_sensor.h rename to esphome/components/ld2420/text_sensor/ld2420_text_sensor.h diff --git a/esphome/components/libretiny/preferences.cpp b/esphome/components/libretiny/preferences.cpp index ce4ed915c0..fc535c99b4 100644 --- a/esphome/components/libretiny/preferences.cpp +++ b/esphome/components/libretiny/preferences.cpp @@ -5,7 +5,7 @@ #include "esphome/core/preferences.h" #include #include -#include +#include #include namespace esphome { @@ -139,21 +139,29 @@ class LibreTinyPreferences : public ESPPreferences { } bool is_changed(const fdb_kvdb_t db, const NVSData &to_save) { - NVSData stored_data{}; struct fdb_kv kv; fdb_kv_t kvp = fdb_kv_get_obj(db, to_save.key.c_str(), &kv); if (kvp == nullptr) { ESP_LOGV(TAG, "fdb_kv_get_obj('%s'): nullptr - the key might not be set yet", to_save.key.c_str()); return true; } - stored_data.data.resize(kv.value_len); - fdb_blob_make(&blob, stored_data.data.data(), kv.value_len); + + // Check size first - if different, data has changed + if (kv.value_len != to_save.data.size()) { + return true; + } + + // Allocate buffer on heap to avoid stack allocation for large data + auto stored_data = std::make_unique(kv.value_len); + fdb_blob_make(&blob, stored_data.get(), kv.value_len); size_t actual_len = fdb_kv_get_blob(db, to_save.key.c_str(), &blob); if (actual_len != kv.value_len) { ESP_LOGV(TAG, "fdb_kv_get_blob('%s') len mismatch: %u != %u", to_save.key.c_str(), actual_len, kv.value_len); return true; } - return to_save.data != stored_data.data; + + // Compare the actual data + return memcmp(to_save.data.data(), stored_data.get(), kv.value_len) != 0; } bool reset() override { diff --git a/esphome/components/light/addressable_light_effect.h b/esphome/components/light/addressable_light_effect.h index d622ec0375..fcf76b3cb0 100644 --- a/esphome/components/light/addressable_light_effect.h +++ b/esphome/components/light/addressable_light_effect.h @@ -44,6 +44,13 @@ class AddressableLightEffect : public LightEffect { this->apply(*this->get_addressable_(), current_color); } + /// Get effect index specifically for addressable effects. + /// Can be used by effects to modify behavior based on their position in the list. + uint32_t get_effect_index() const { return this->get_index(); } + + /// Check if this is the currently running addressable effect. + bool is_current_effect() const { return this->is_active() && this->get_addressable_()->is_effect_active(); } + protected: AddressableLight *get_addressable_() const { return (AddressableLight *) this->state_->get_output(); } }; diff --git a/esphome/components/light/base_light_effects.h b/esphome/components/light/base_light_effects.h index 9e02e889c9..ff6cd1ccfe 100644 --- a/esphome/components/light/base_light_effects.h +++ b/esphome/components/light/base_light_effects.h @@ -125,6 +125,10 @@ class LambdaLightEffect : public LightEffect { } } + /// Get the current effect index for use in lambda functions. + /// This can be useful for lambda effects that need to know their own index. + uint32_t get_current_index() const { return this->get_index(); } + protected: std::function f_; uint32_t update_interval_; @@ -143,6 +147,10 @@ class AutomationLightEffect : public LightEffect { } Trigger<> *get_trig() const { return trig_; } + /// Get the current effect index for use in automations. + /// Useful for automations that need to know which effect is running. + uint32_t get_current_index() const { return this->get_index(); } + protected: Trigger<> *trig_{new Trigger<>}; }; diff --git a/esphome/components/light/light_effect.cpp b/esphome/components/light/light_effect.cpp new file mode 100644 index 0000000000..a210b48e5b --- /dev/null +++ b/esphome/components/light/light_effect.cpp @@ -0,0 +1,36 @@ +#include "light_effect.h" +#include "light_state.h" + +namespace esphome { +namespace light { + +uint32_t LightEffect::get_index() const { + if (this->state_ == nullptr) { + return 0; + } + return this->get_index_in_parent_(); +} + +bool LightEffect::is_active() const { + if (this->state_ == nullptr) { + return false; + } + return this->get_index() != 0 && this->state_->get_current_effect_index() == this->get_index(); +} + +uint32_t LightEffect::get_index_in_parent_() const { + if (this->state_ == nullptr) { + return 0; + } + + const auto &effects = this->state_->get_effects(); + for (size_t i = 0; i < effects.size(); i++) { + if (effects[i] == this) { + return i + 1; // Effects are 1-indexed in the API + } + } + return 0; // Not found +} + +} // namespace light +} // namespace esphome diff --git a/esphome/components/light/light_effect.h b/esphome/components/light/light_effect.h index 8da51fe8b3..dbaf1faf24 100644 --- a/esphome/components/light/light_effect.h +++ b/esphome/components/light/light_effect.h @@ -34,9 +34,23 @@ class LightEffect { this->init(); } + /// Get the index of this effect in the parent light's effect list. + /// Returns 0 if not found or not initialized. + uint32_t get_index() const; + + /// Check if this effect is currently active. + bool is_active() const; + + /// Get a reference to the parent light state. + /// Returns nullptr if not initialized. + LightState *get_light_state() const { return this->state_; } + protected: LightState *state_{nullptr}; std::string name_; + + /// Internal method to find this effect's index in the parent light's effect list. + uint32_t get_index_in_parent_() const; }; } // namespace light diff --git a/esphome/components/light/light_json_schema.cpp b/esphome/components/light/light_json_schema.cpp index 896b821705..010e130612 100644 --- a/esphome/components/light/light_json_schema.cpp +++ b/esphome/components/light/light_json_schema.cpp @@ -36,8 +36,11 @@ static constexpr const char *get_color_mode_json_str(ColorMode mode) { void LightJSONSchema::dump_json(LightState &state, JsonObject root) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - if (state.supports_effects()) + if (state.supports_effects()) { root["effect"] = state.get_effect_name(); + root["effect_index"] = state.get_current_effect_index(); + root["effect_count"] = state.get_effect_count(); + } auto values = state.remote_values; auto traits = state.get_output()->get_traits(); @@ -160,6 +163,11 @@ void LightJSONSchema::parse_json(LightState &state, LightCall &call, JsonObject const char *effect = root["effect"]; call.set_effect(effect); } + + if (root["effect_index"].is()) { + uint32_t effect_index = root["effect_index"]; + call.set_effect(effect_index); + } } } // namespace light diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 5b57707d6b..9e42b2f1e2 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -140,12 +140,22 @@ float LightState::get_setup_priority() const { return setup_priority::HARDWARE - void LightState::publish_state() { this->remote_values_callback_.call(); } LightOutput *LightState::get_output() const { return this->output_; } + +static constexpr const char *EFFECT_NONE = "None"; +static constexpr auto EFFECT_NONE_REF = StringRef::from_lit("None"); + std::string LightState::get_effect_name() { if (this->active_effect_index_ > 0) { return this->effects_[this->active_effect_index_ - 1]->get_name(); - } else { - return "None"; } + return EFFECT_NONE; +} + +StringRef LightState::get_effect_name_ref() { + if (this->active_effect_index_ > 0) { + return StringRef(this->effects_[this->active_effect_index_ - 1]->get_name()); + } + return EFFECT_NONE_REF; } void LightState::add_new_remote_values_callback(std::function &&send_callback) { diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index 72cb99223e..48323dd3c3 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -4,6 +4,7 @@ #include "esphome/core/entity_base.h" #include "esphome/core/optional.h" #include "esphome/core/preferences.h" +#include "esphome/core/string_ref.h" #include "light_call.h" #include "light_color_values.h" #include "light_effect.h" @@ -116,6 +117,8 @@ class LightState : public EntityBase, public Component { /// Return the name of the current effect, or if no effect is active "None". std::string get_effect_name(); + /// Return the name of the current effect as StringRef (for API usage) + StringRef get_effect_name_ref(); /** * This lets front-end components subscribe to light change events. This callback is called once @@ -160,6 +163,44 @@ class LightState : public EntityBase, public Component { /// Add effects for this light state. void add_effects(const std::vector &effects); + /// Get the total number of effects available for this light. + size_t get_effect_count() const { return this->effects_.size(); } + + /// Get the currently active effect index (0 = no effect, 1+ = effect index). + uint32_t get_current_effect_index() const { return this->active_effect_index_; } + + /// Get effect index by name. Returns 0 if effect not found. + uint32_t get_effect_index(const std::string &effect_name) const { + if (strcasecmp(effect_name.c_str(), "none") == 0) { + return 0; + } + for (size_t i = 0; i < this->effects_.size(); i++) { + if (strcasecmp(effect_name.c_str(), this->effects_[i]->get_name().c_str()) == 0) { + return i + 1; // Effects are 1-indexed in active_effect_index_ + } + } + return 0; // Effect not found + } + + /// Get effect by index. Returns nullptr if index is invalid. + LightEffect *get_effect_by_index(uint32_t index) const { + if (index == 0 || index > this->effects_.size()) { + return nullptr; + } + return this->effects_[index - 1]; // Effects are 1-indexed in active_effect_index_ + } + + /// Get effect name by index. Returns "None" for index 0, empty string for invalid index. + std::string get_effect_name_by_index(uint32_t index) const { + if (index == 0) { + return "None"; + } + if (index > this->effects_.size()) { + return ""; // Invalid index + } + return this->effects_[index - 1]->get_name(); + } + /// The result of all the current_values_as_* methods have gamma correction applied. void current_values_as_binary(bool *binary); diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index 640750720d..316a10596f 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -24,100 +24,139 @@ static const char *const TAG = "mdns"; void MDNSComponent::compile_records_() { this->hostname_ = App.get_name(); - this->services_.clear(); + // Calculate exact capacity needed for services vector + size_t services_count = 0; #ifdef USE_API if (api::global_api_server != nullptr) { - MDNSService service{}; + services_count++; + } +#endif +#ifdef USE_PROMETHEUS + services_count++; +#endif +#ifdef USE_WEBSERVER + services_count++; +#endif +#ifdef USE_MDNS_EXTRA_SERVICES + services_count += this->services_extra_.size(); +#endif + // Reserve for fallback service if needed + if (services_count == 0) { + services_count = 1; + } + this->services_.reserve(services_count); + +#ifdef USE_API + if (api::global_api_server != nullptr) { + this->services_.emplace_back(); + auto &service = this->services_.back(); service.service_type = "_esphomelib"; service.proto = "_tcp"; service.port = api::global_api_server->get_port(); - if (!App.get_friendly_name().empty()) { - service.txt_records.push_back({"friendly_name", App.get_friendly_name()}); - } - service.txt_records.push_back({"version", ESPHOME_VERSION}); - service.txt_records.push_back({"mac", get_mac_address()}); - const char *platform = nullptr; -#ifdef USE_ESP8266 - platform = "ESP8266"; -#endif -#ifdef USE_ESP32 - platform = "ESP32"; -#endif -#ifdef USE_RP2040 - platform = "RP2040"; -#endif -#ifdef USE_LIBRETINY - platform = lt_cpu_get_model_name(); -#endif - if (platform != nullptr) { - service.txt_records.push_back({"platform", platform}); - } - service.txt_records.push_back({"board", ESPHOME_BOARD}); + const std::string &friendly_name = App.get_friendly_name(); + bool friendly_name_empty = friendly_name.empty(); + + // Calculate exact capacity for txt_records + size_t txt_count = 3; // version, mac, board (always present) + if (!friendly_name_empty) { + txt_count++; // friendly_name + } +#if defined(USE_ESP8266) || defined(USE_ESP32) || defined(USE_RP2040) || defined(USE_LIBRETINY) + txt_count++; // platform +#endif +#if defined(USE_WIFI) || defined(USE_ETHERNET) || defined(USE_OPENTHREAD) + txt_count++; // network +#endif +#ifdef USE_API_NOISE + txt_count++; // api_encryption or api_encryption_supported +#endif +#ifdef ESPHOME_PROJECT_NAME + txt_count += 2; // project_name and project_version +#endif +#ifdef USE_DASHBOARD_IMPORT + txt_count++; // package_import_url +#endif + + auto &txt_records = service.txt_records; + txt_records.reserve(txt_count); + + if (!friendly_name_empty) { + txt_records.emplace_back(MDNSTXTRecord{"friendly_name", friendly_name}); + } + txt_records.emplace_back(MDNSTXTRecord{"version", ESPHOME_VERSION}); + txt_records.emplace_back(MDNSTXTRecord{"mac", get_mac_address()}); + +#ifdef USE_ESP8266 + txt_records.emplace_back(MDNSTXTRecord{"platform", "ESP8266"}); +#elif defined(USE_ESP32) + txt_records.emplace_back(MDNSTXTRecord{"platform", "ESP32"}); +#elif defined(USE_RP2040) + txt_records.emplace_back(MDNSTXTRecord{"platform", "RP2040"}); +#elif defined(USE_LIBRETINY) + txt_records.emplace_back(MDNSTXTRecord{"platform", lt_cpu_get_model_name()}); +#endif + + txt_records.emplace_back(MDNSTXTRecord{"board", ESPHOME_BOARD}); #if defined(USE_WIFI) - service.txt_records.push_back({"network", "wifi"}); + txt_records.emplace_back(MDNSTXTRecord{"network", "wifi"}); #elif defined(USE_ETHERNET) - service.txt_records.push_back({"network", "ethernet"}); + txt_records.emplace_back(MDNSTXTRecord{"network", "ethernet"}); #elif defined(USE_OPENTHREAD) - service.txt_records.push_back({"network", "thread"}); + txt_records.emplace_back(MDNSTXTRecord{"network", "thread"}); #endif #ifdef USE_API_NOISE + static constexpr const char *NOISE_ENCRYPTION = "Noise_NNpsk0_25519_ChaChaPoly_SHA256"; if (api::global_api_server->get_noise_ctx()->has_psk()) { - service.txt_records.push_back({"api_encryption", "Noise_NNpsk0_25519_ChaChaPoly_SHA256"}); + txt_records.emplace_back(MDNSTXTRecord{"api_encryption", NOISE_ENCRYPTION}); } else { - service.txt_records.push_back({"api_encryption_supported", "Noise_NNpsk0_25519_ChaChaPoly_SHA256"}); + txt_records.emplace_back(MDNSTXTRecord{"api_encryption_supported", NOISE_ENCRYPTION}); } #endif #ifdef ESPHOME_PROJECT_NAME - service.txt_records.push_back({"project_name", ESPHOME_PROJECT_NAME}); - service.txt_records.push_back({"project_version", ESPHOME_PROJECT_VERSION}); + txt_records.emplace_back(MDNSTXTRecord{"project_name", ESPHOME_PROJECT_NAME}); + txt_records.emplace_back(MDNSTXTRecord{"project_version", ESPHOME_PROJECT_VERSION}); #endif // ESPHOME_PROJECT_NAME #ifdef USE_DASHBOARD_IMPORT - service.txt_records.push_back({"package_import_url", dashboard_import::get_package_import_url()}); + txt_records.emplace_back(MDNSTXTRecord{"package_import_url", dashboard_import::get_package_import_url()}); #endif - - this->services_.push_back(service); } #endif // USE_API #ifdef USE_PROMETHEUS - { - MDNSService service{}; - service.service_type = "_prometheus-http"; - service.proto = "_tcp"; - service.port = USE_WEBSERVER_PORT; - this->services_.push_back(service); - } + this->services_.emplace_back(); + auto &prom_service = this->services_.back(); + prom_service.service_type = "_prometheus-http"; + prom_service.proto = "_tcp"; + prom_service.port = USE_WEBSERVER_PORT; #endif #ifdef USE_WEBSERVER - { - MDNSService service{}; - service.service_type = "_http"; - service.proto = "_tcp"; - service.port = USE_WEBSERVER_PORT; - this->services_.push_back(service); - } + this->services_.emplace_back(); + auto &web_service = this->services_.back(); + web_service.service_type = "_http"; + web_service.proto = "_tcp"; + web_service.port = USE_WEBSERVER_PORT; #endif #ifdef USE_MDNS_EXTRA_SERVICES this->services_.insert(this->services_.end(), this->services_extra_.begin(), this->services_extra_.end()); #endif - if (this->services_.empty()) { - // Publish "http" service if not using native API - // This is just to have *some* mDNS service so that .local resolution works - MDNSService service{}; - service.service_type = "_http"; - service.proto = "_tcp"; - service.port = USE_WEBSERVER_PORT; - service.txt_records.push_back({"version", ESPHOME_VERSION}); - this->services_.push_back(service); - } +#if !defined(USE_API) && !defined(USE_PROMETHEUS) && !defined(USE_WEBSERVER) && !defined(USE_MDNS_EXTRA_SERVICES) + // Publish "http" service if not using native API or any other services + // This is just to have *some* mDNS service so that .local resolution works + this->services_.emplace_back(); + auto &fallback_service = this->services_.back(); + fallback_service.service_type = "_http"; + fallback_service.proto = "_tcp"; + fallback_service.port = USE_WEBSERVER_PORT; + fallback_service.txt_records.emplace_back(MDNSTXTRecord{"version", ESPHOME_VERSION}); +#endif } void MDNSComponent::dump_config() { @@ -125,6 +164,7 @@ void MDNSComponent::dump_config() { "mDNS:\n" " Hostname: %s", this->hostname_.c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE ESP_LOGV(TAG, " Services:"); for (const auto &service : this->services_) { ESP_LOGV(TAG, " - %s, %s, %d", service.service_type.c_str(), service.proto.c_str(), @@ -134,6 +174,7 @@ void MDNSComponent::dump_config() { const_cast &>(record.value).value().c_str()); } } +#endif } std::vector MDNSComponent::get_services() { return this->services_; } diff --git a/esphome/components/mlx90614/mlx90614.cpp b/esphome/components/mlx90614/mlx90614.cpp index 2e711baf9a..24024df090 100644 --- a/esphome/components/mlx90614/mlx90614.cpp +++ b/esphome/components/mlx90614/mlx90614.cpp @@ -50,28 +50,13 @@ bool MLX90614Component::write_emissivity_() { return true; } -uint8_t MLX90614Component::crc8_pec_(const uint8_t *data, uint8_t len) { - uint8_t crc = 0; - for (uint8_t i = 0; i < len; i++) { - uint8_t in = data[i]; - for (uint8_t j = 0; j < 8; j++) { - bool carry = (crc ^ in) & 0x80; - crc <<= 1; - if (carry) - crc ^= 0x07; - in <<= 1; - } - } - return crc; -} - bool MLX90614Component::write_bytes_(uint8_t reg, uint16_t data) { uint8_t buf[5]; buf[0] = this->address_ << 1; buf[1] = reg; buf[2] = data & 0xFF; buf[3] = data >> 8; - buf[4] = this->crc8_pec_(buf, 4); + buf[4] = crc8(buf, 4, 0x00, 0x07, true); return this->write_bytes(reg, buf + 2, 3); } diff --git a/esphome/components/mlx90614/mlx90614.h b/esphome/components/mlx90614/mlx90614.h index b6bd44172d..fa6fb523bb 100644 --- a/esphome/components/mlx90614/mlx90614.h +++ b/esphome/components/mlx90614/mlx90614.h @@ -22,7 +22,6 @@ class MLX90614Component : public PollingComponent, public i2c::I2CDevice { protected: bool write_emissivity_(); - uint8_t crc8_pec_(const uint8_t *data, uint8_t len); bool write_bytes_(uint8_t reg, uint16_t data); sensor::Sensor *ambient_sensor_{nullptr}; diff --git a/esphome/components/ntc/ntc.cpp b/esphome/components/ntc/ntc.cpp index 333dbc5a75..b08f84029b 100644 --- a/esphome/components/ntc/ntc.cpp +++ b/esphome/components/ntc/ntc.cpp @@ -11,7 +11,7 @@ void NTC::setup() { if (this->sensor_->has_state()) this->process_(this->sensor_->state); } -void NTC::dump_config() { LOG_SENSOR("", "NTC Sensor", this) } +void NTC::dump_config() { LOG_SENSOR("", "NTC Sensor", this); } float NTC::get_setup_priority() const { return setup_priority::DATA; } void NTC::process_(float value) { if (std::isnan(value)) { diff --git a/esphome/components/number/number.cpp b/esphome/components/number/number.cpp index b6a845b19b..4769c1ed12 100644 --- a/esphome/components/number/number.cpp +++ b/esphome/components/number/number.cpp @@ -6,6 +6,27 @@ namespace number { static const char *const TAG = "number"; +// Function implementation of LOG_NUMBER macro to reduce code size +void log_number(const char *tag, const char *prefix, const char *type, Number *obj) { + if (obj == nullptr) { + return; + } + + ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str()); + + if (!obj->get_icon().empty()) { + ESP_LOGCONFIG(tag, "%s Icon: '%s'", prefix, obj->get_icon().c_str()); + } + + if (!obj->traits.get_unit_of_measurement().empty()) { + ESP_LOGCONFIG(tag, "%s Unit of Measurement: '%s'", prefix, obj->traits.get_unit_of_measurement().c_str()); + } + + if (!obj->traits.get_device_class().empty()) { + ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->traits.get_device_class().c_str()); + } +} + void Number::publish_state(float state) { this->set_has_state(true); this->state = state; diff --git a/esphome/components/number/number.h b/esphome/components/number/number.h index 49bcbb857c..da91d70d53 100644 --- a/esphome/components/number/number.h +++ b/esphome/components/number/number.h @@ -9,19 +9,10 @@ namespace esphome { namespace number { -#define LOG_NUMBER(prefix, type, obj) \ - if ((obj) != nullptr) { \ - ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ - if (!(obj)->get_icon().empty()) { \ - ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ - } \ - if (!(obj)->traits.get_unit_of_measurement().empty()) { \ - ESP_LOGCONFIG(TAG, "%s Unit of Measurement: '%s'", prefix, (obj)->traits.get_unit_of_measurement().c_str()); \ - } \ - if (!(obj)->traits.get_device_class().empty()) { \ - ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->traits.get_device_class().c_str()); \ - } \ - } +class Number; +void log_number(const char *tag, const char *prefix, const char *type, Number *obj); + +#define LOG_NUMBER(prefix, type, obj) log_number(TAG, prefix, LOG_STR_LITERAL(type), obj) #define SUB_NUMBER(name) \ protected: \ diff --git a/esphome/components/opentherm/hub.h b/esphome/components/opentherm/hub.h index 80fd268820..ee0cfd104d 100644 --- a/esphome/components/opentherm/hub.h +++ b/esphome/components/opentherm/hub.h @@ -1,10 +1,10 @@ #pragma once +#include +#include "esphome/core/component.h" #include "esphome/core/defines.h" #include "esphome/core/hal.h" -#include "esphome/core/component.h" #include "esphome/core/log.h" -#include #include "opentherm.h" @@ -17,21 +17,21 @@ #endif #ifdef OPENTHERM_USE_SWITCH -#include "esphome/components/opentherm/switch/switch.h" +#include "esphome/components/opentherm/switch/opentherm_switch.h" #endif #ifdef OPENTHERM_USE_OUTPUT -#include "esphome/components/opentherm/output/output.h" +#include "esphome/components/opentherm/output/opentherm_output.h" #endif #ifdef OPENTHERM_USE_NUMBER -#include "esphome/components/opentherm/number/number.h" +#include "esphome/components/opentherm/number/opentherm_number.h" #endif +#include #include #include #include -#include #include "opentherm_macros.h" diff --git a/esphome/components/opentherm/number/number.cpp b/esphome/components/opentherm/number/opentherm_number.cpp similarity index 97% rename from esphome/components/opentherm/number/number.cpp rename to esphome/components/opentherm/number/opentherm_number.cpp index 90ab5d6490..c5d094aa36 100644 --- a/esphome/components/opentherm/number/number.cpp +++ b/esphome/components/opentherm/number/opentherm_number.cpp @@ -1,4 +1,4 @@ -#include "number.h" +#include "opentherm_number.h" namespace esphome { namespace opentherm { diff --git a/esphome/components/opentherm/number/number.h b/esphome/components/opentherm/number/opentherm_number.h similarity index 100% rename from esphome/components/opentherm/number/number.h rename to esphome/components/opentherm/number/opentherm_number.h diff --git a/esphome/components/opentherm/output/output.cpp b/esphome/components/opentherm/output/opentherm_output.cpp similarity index 95% rename from esphome/components/opentherm/output/output.cpp rename to esphome/components/opentherm/output/opentherm_output.cpp index 486aa0d4e7..ff82ddd72c 100644 --- a/esphome/components/opentherm/output/output.cpp +++ b/esphome/components/opentherm/output/opentherm_output.cpp @@ -1,5 +1,5 @@ #include "esphome/core/helpers.h" // for clamp() and lerp() -#include "output.h" +#include "opentherm_output.h" namespace esphome { namespace opentherm { diff --git a/esphome/components/opentherm/output/output.h b/esphome/components/opentherm/output/opentherm_output.h similarity index 100% rename from esphome/components/opentherm/output/output.h rename to esphome/components/opentherm/output/opentherm_output.h diff --git a/esphome/components/opentherm/switch/switch.cpp b/esphome/components/opentherm/switch/opentherm_switch.cpp similarity index 96% rename from esphome/components/opentherm/switch/switch.cpp rename to esphome/components/opentherm/switch/opentherm_switch.cpp index 228d9ac8f3..5c5d62e68e 100644 --- a/esphome/components/opentherm/switch/switch.cpp +++ b/esphome/components/opentherm/switch/opentherm_switch.cpp @@ -1,4 +1,4 @@ -#include "switch.h" +#include "opentherm_switch.h" namespace esphome { namespace opentherm { diff --git a/esphome/components/opentherm/switch/switch.h b/esphome/components/opentherm/switch/opentherm_switch.h similarity index 100% rename from esphome/components/opentherm/switch/switch.h rename to esphome/components/opentherm/switch/opentherm_switch.h diff --git a/esphome/components/pipsolar/__init__.py b/esphome/components/pipsolar/__init__.py index 1e4ea8492b..e3966aa2cc 100644 --- a/esphome/components/pipsolar/__init__.py +++ b/esphome/components/pipsolar/__init__.py @@ -26,7 +26,7 @@ CONFIG_SCHEMA = cv.All( ) -def to_code(config): +async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - yield cg.register_component(var, config) - yield uart.register_uart_device(var, config) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) diff --git a/esphome/components/pipsolar/output/__init__.py b/esphome/components/pipsolar/output/__init__.py index 1eb7249119..829f8f7037 100644 --- a/esphome/components/pipsolar/output/__init__.py +++ b/esphome/components/pipsolar/output/__init__.py @@ -99,9 +99,9 @@ async def to_code(config): } ), ) -def output_pipsolar_set_level_to_code(config, action_id, template_arg, args): - paren = yield cg.get_variable(config[CONF_ID]) +async def output_pipsolar_set_level_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) - template_ = yield cg.templatable(config[CONF_VALUE], args, float) + template_ = await cg.templatable(config[CONF_VALUE], args, float) cg.add(var.set_level(template_)) - yield var + return var diff --git a/esphome/components/pulse_width/pulse_width.cpp b/esphome/components/pulse_width/pulse_width.cpp index 8d66861049..c086ceaa23 100644 --- a/esphome/components/pulse_width/pulse_width.cpp +++ b/esphome/components/pulse_width/pulse_width.cpp @@ -17,7 +17,7 @@ void IRAM_ATTR PulseWidthSensorStore::gpio_intr(PulseWidthSensorStore *arg) { } void PulseWidthSensor::dump_config() { - LOG_SENSOR("", "Pulse Width", this) + LOG_SENSOR("", "Pulse Width", this); LOG_UPDATE_INTERVAL(this) LOG_PIN(" Pin: ", this->pin_); } diff --git a/esphome/components/radon_eye_ble/__init__.py b/esphome/components/radon_eye_ble/__init__.py index 01910c81a8..99daef30e5 100644 --- a/esphome/components/radon_eye_ble/__init__.py +++ b/esphome/components/radon_eye_ble/__init__.py @@ -18,6 +18,6 @@ CONFIG_SCHEMA = cv.Schema( ).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) -def to_code(config): +async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - yield esp32_ble_tracker.register_ble_device(var, config) + await esp32_ble_tracker.register_ble_device(var, config) diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index 8163661c65..42ebae77f7 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -1782,14 +1782,12 @@ def nexa_dumper(var, config): @register_action("nexa", NexaAction, NEXA_SCHEMA) -def nexa_action(var, config, args): - cg.add(var.set_device((yield cg.templatable(config[CONF_DEVICE], args, cg.uint32)))) - cg.add(var.set_group((yield cg.templatable(config[CONF_GROUP], args, cg.uint8)))) - cg.add(var.set_state((yield cg.templatable(config[CONF_STATE], args, cg.uint8)))) - cg.add( - var.set_channel((yield cg.templatable(config[CONF_CHANNEL], args, cg.uint8))) - ) - cg.add(var.set_level((yield cg.templatable(config[CONF_LEVEL], args, cg.uint8)))) +async def nexa_action(var, config, args): + cg.add(var.set_device(await cg.templatable(config[CONF_DEVICE], args, cg.uint32))) + cg.add(var.set_group(await cg.templatable(config[CONF_GROUP], args, cg.uint8))) + cg.add(var.set_state(await cg.templatable(config[CONF_STATE], args, cg.uint8))) + cg.add(var.set_channel(await cg.templatable(config[CONF_CHANNEL], args, cg.uint8))) + cg.add(var.set_level(await cg.templatable(config[CONF_LEVEL], args, cg.uint8))) # Midea diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 0a82677bc9..6df6347c18 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -6,6 +6,33 @@ namespace sensor { static const char *const TAG = "sensor"; +// Function implementation of LOG_SENSOR macro to reduce code size +void log_sensor(const char *tag, const char *prefix, const char *type, Sensor *obj) { + if (obj == nullptr) { + return; + } + + ESP_LOGCONFIG(tag, + "%s%s '%s'\n" + "%s State Class: '%s'\n" + "%s Unit of Measurement: '%s'\n" + "%s Accuracy Decimals: %d", + prefix, type, obj->get_name().c_str(), prefix, state_class_to_string(obj->get_state_class()).c_str(), + prefix, obj->get_unit_of_measurement().c_str(), prefix, obj->get_accuracy_decimals()); + + if (!obj->get_device_class().empty()) { + ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->get_device_class().c_str()); + } + + if (!obj->get_icon().empty()) { + ESP_LOGCONFIG(tag, "%s Icon: '%s'", prefix, obj->get_icon().c_str()); + } + + if (obj->get_force_update()) { + ESP_LOGV(tag, "%s Force Update: YES", prefix); + } +} + std::string state_class_to_string(StateClass state_class) { switch (state_class) { case STATE_CLASS_MEASUREMENT: diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index c2ded0f2c3..b3206d8dab 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -12,26 +12,9 @@ namespace esphome { namespace sensor { -#define LOG_SENSOR(prefix, type, obj) \ - if ((obj) != nullptr) { \ - ESP_LOGCONFIG(TAG, \ - "%s%s '%s'\n" \ - "%s State Class: '%s'\n" \ - "%s Unit of Measurement: '%s'\n" \ - "%s Accuracy Decimals: %d", \ - prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str(), prefix, \ - state_class_to_string((obj)->get_state_class()).c_str(), prefix, \ - (obj)->get_unit_of_measurement().c_str(), prefix, (obj)->get_accuracy_decimals()); \ - if (!(obj)->get_device_class().empty()) { \ - ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \ - } \ - if (!(obj)->get_icon().empty()) { \ - ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ - } \ - if ((obj)->get_force_update()) { \ - ESP_LOGV(TAG, "%s Force Update: YES", prefix); \ - } \ - } +void log_sensor(const char *tag, const char *prefix, const char *type, Sensor *obj); + +#define LOG_SENSOR(prefix, type, obj) log_sensor(TAG, prefix, LOG_STR_LITERAL(type), obj) #define SUB_SENSOR(name) \ protected: \ diff --git a/esphome/components/shelly_dimmer/light.py b/esphome/components/shelly_dimmer/light.py index bb2c3ceee8..c96bc380d7 100644 --- a/esphome/components/shelly_dimmer/light.py +++ b/esphome/components/shelly_dimmer/light.py @@ -183,7 +183,7 @@ CONFIG_SCHEMA = ( ) -def to_code(config): +async def to_code(config): fw_hex = get_firmware(config[CONF_FIRMWARE]) fw_major, fw_minor = parse_firmware_version(config[CONF_FIRMWARE][CONF_VERSION]) @@ -193,17 +193,17 @@ def to_code(config): cg.add_define("USE_SHD_FIRMWARE_MINOR_VERSION", fw_minor) var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) - yield cg.register_component(var, config) + await cg.register_component(var, config) config.pop( CONF_UPDATE_INTERVAL ) # drop UPDATE_INTERVAL as it does not apply to the light component - yield light.register_light(var, config) - yield uart.register_uart_device(var, config) + await light.register_light(var, config) + await uart.register_uart_device(var, config) - nrst_pin = yield cg.gpio_pin_expression(config[CONF_NRST_PIN]) + nrst_pin = await cg.gpio_pin_expression(config[CONF_NRST_PIN]) cg.add(var.set_nrst_pin(nrst_pin)) - boot0_pin = yield cg.gpio_pin_expression(config[CONF_BOOT0_PIN]) + boot0_pin = await cg.gpio_pin_expression(config[CONF_BOOT0_PIN]) cg.add(var.set_boot0_pin(boot0_pin)) cg.add(var.set_leading_edge(config[CONF_LEADING_EDGE])) @@ -217,5 +217,5 @@ def to_code(config): continue conf = config[key] - sens = yield sensor.new_sensor(conf) + sens = await sensor.new_sensor(conf) cg.add(getattr(var, f"set_{key}_sensor")(sens)) diff --git a/esphome/components/tee501/tee501.cpp b/esphome/components/tee501/tee501.cpp index 460f446865..dc803be775 100644 --- a/esphome/components/tee501/tee501.cpp +++ b/esphome/components/tee501/tee501.cpp @@ -12,7 +12,7 @@ void TEE501Component::setup() { this->write(address, 2, false); uint8_t identification[9]; this->read(identification, 9); - if (identification[8] != calc_crc8_(identification, 0, 7)) { + if (identification[8] != crc8(identification, 8, 0xFF, 0x31, true)) { this->error_code_ = CRC_CHECK_FAILED; this->mark_failed(); return; @@ -45,7 +45,7 @@ void TEE501Component::update() { this->set_timeout(50, [this]() { uint8_t i2c_response[3]; this->read(i2c_response, 3); - if (i2c_response[2] != calc_crc8_(i2c_response, 0, 1)) { + if (i2c_response[2] != crc8(i2c_response, 2, 0xFF, 0x31, true)) { this->error_code_ = CRC_CHECK_FAILED; this->status_set_warning(); return; @@ -62,24 +62,5 @@ void TEE501Component::update() { }); } -unsigned char TEE501Component::calc_crc8_(const unsigned char buf[], unsigned char from, unsigned char to) { - unsigned char crc_val = 0xFF; - unsigned char i = 0; - unsigned char j = 0; - for (i = from; i <= to; i++) { - int cur_val = buf[i]; - for (j = 0; j < 8; j++) { - if (((crc_val ^ cur_val) & 0x80) != 0) // If MSBs are not equal - { - crc_val = ((crc_val << 1) ^ 0x31); - } else { - crc_val = (crc_val << 1); - } - cur_val = cur_val << 1; - } - } - return crc_val; -} - } // namespace tee501 } // namespace esphome diff --git a/esphome/components/tee501/tee501.h b/esphome/components/tee501/tee501.h index fc655e58c9..2437ac92eb 100644 --- a/esphome/components/tee501/tee501.h +++ b/esphome/components/tee501/tee501.h @@ -1,8 +1,8 @@ #pragma once -#include "esphome/core/component.h" -#include "esphome/components/sensor/sensor.h" #include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" namespace esphome { namespace tee501 { @@ -16,8 +16,6 @@ class TEE501Component : public sensor::Sensor, public PollingComponent, public i void update() override; protected: - unsigned char calc_crc8_(const unsigned char buf[], unsigned char from, unsigned char to); - enum ErrorCode { NONE = 0, COMMUNICATION_FAILED, CRC_CHECK_FAILED } error_code_{NONE}; }; diff --git a/esphome/components/ufire_ec/ufire_ec.cpp b/esphome/components/ufire_ec/ufire_ec.cpp index 364a133776..9e0055a2cc 100644 --- a/esphome/components/ufire_ec/ufire_ec.cpp +++ b/esphome/components/ufire_ec/ufire_ec.cpp @@ -105,9 +105,9 @@ void UFireECComponent::dump_config() { ESP_LOGCONFIG(TAG, "uFire-EC"); LOG_I2C_DEVICE(this) LOG_UPDATE_INTERVAL(this) - LOG_SENSOR(" ", "EC Sensor", this->ec_sensor_) - LOG_SENSOR(" ", "Temperature Sensor", this->temperature_sensor_) - LOG_SENSOR(" ", "Temperature Sensor external", this->temperature_sensor_external_) + LOG_SENSOR(" ", "EC Sensor", this->ec_sensor_); + LOG_SENSOR(" ", "Temperature Sensor", this->temperature_sensor_); + LOG_SENSOR(" ", "Temperature Sensor external", this->temperature_sensor_external_); ESP_LOGCONFIG(TAG, " Temperature Compensation: %f\n" " Temperature Coefficient: %f", diff --git a/esphome/components/ufire_ise/ufire_ise.cpp b/esphome/components/ufire_ise/ufire_ise.cpp index 503d993fb7..9e0e7e265d 100644 --- a/esphome/components/ufire_ise/ufire_ise.cpp +++ b/esphome/components/ufire_ise/ufire_ise.cpp @@ -142,9 +142,9 @@ void UFireISEComponent::dump_config() { ESP_LOGCONFIG(TAG, "uFire-ISE"); LOG_I2C_DEVICE(this) LOG_UPDATE_INTERVAL(this) - LOG_SENSOR(" ", "PH Sensor", this->ph_sensor_) - LOG_SENSOR(" ", "Temperature Sensor", this->temperature_sensor_) - LOG_SENSOR(" ", "Temperature Sensor external", this->temperature_sensor_external_) + LOG_SENSOR(" ", "PH Sensor", this->ph_sensor_); + LOG_SENSOR(" ", "Temperature Sensor", this->temperature_sensor_); + LOG_SENSOR(" ", "Temperature Sensor external", this->temperature_sensor_external_); } } // namespace ufire_ise diff --git a/esphome/components/valve/__init__.py b/esphome/components/valve/__init__.py index 53254068af..8185bd6ea2 100644 --- a/esphome/components/valve/__init__.py +++ b/esphome/components/valve/__init__.py @@ -202,9 +202,9 @@ async def valve_stop_to_code(config, action_id, template_arg, args): @automation.register_action("valve.toggle", ToggleAction, VALVE_ACTION_SCHEMA) -def valve_toggle_to_code(config, action_id, template_arg, args): - paren = yield cg.get_variable(config[CONF_ID]) - yield cg.new_Pvariable(action_id, template_arg, paren) +async def valve_toggle_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(action_id, template_arg, paren) VALVE_CONTROL_ACTION_SCHEMA = cv.Schema( diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 399b8785ae..290992b096 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -507,14 +507,37 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM auto detail = get_request_detail(request); std::string data = this->switch_json(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); - } else if (match.method_equals("toggle")) { - this->defer([obj]() { obj->toggle(); }); - request->send(200); + return; + } + + // Handle action methods with single defer and response + enum SwitchAction { NONE, TOGGLE, TURN_ON, TURN_OFF }; + SwitchAction action = NONE; + + if (match.method_equals("toggle")) { + action = TOGGLE; } else if (match.method_equals("turn_on")) { - this->defer([obj]() { obj->turn_on(); }); - request->send(200); + action = TURN_ON; } else if (match.method_equals("turn_off")) { - this->defer([obj]() { obj->turn_off(); }); + action = TURN_OFF; + } + + if (action != NONE) { + this->defer([obj, action]() { + switch (action) { + case TOGGLE: + obj->toggle(); + break; + case TURN_ON: + obj->turn_on(); + break; + case TURN_OFF: + obj->turn_off(); + break; + default: + break; + } + }); request->send(200); } else { request->send(404); @@ -1332,14 +1355,37 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat auto detail = get_request_detail(request); std::string data = this->lock_json(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); - } else if (match.method_equals("lock")) { - this->defer([obj]() { obj->lock(); }); - request->send(200); + return; + } + + // Handle action methods with single defer and response + enum LockAction { NONE, LOCK, UNLOCK, OPEN }; + LockAction action = NONE; + + if (match.method_equals("lock")) { + action = LOCK; } else if (match.method_equals("unlock")) { - this->defer([obj]() { obj->unlock(); }); - request->send(200); + action = UNLOCK; } else if (match.method_equals("open")) { - this->defer([obj]() { obj->open(); }); + action = OPEN; + } + + if (action != NONE) { + this->defer([obj, action]() { + switch (action) { + case LOCK: + obj->lock(); + break; + case UNLOCK: + obj->unlock(); + break; + case OPEN: + obj->open(); + break; + default: + break; + } + }); request->send(200); } else { request->send(404); diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index d2d47fe171..dc745a2a46 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -256,30 +256,79 @@ void Application::run_powerdown_hooks() { void Application::teardown_components(uint32_t timeout_ms) { uint32_t start_time = millis(); - // Copy all components in reverse order using reverse iterators + // Use a StaticVector instead of std::vector to avoid heap allocation + // since we know the actual size at compile time + StaticVector pending_components; + + // Copy all components in reverse order // Reverse order matches the behavior of run_safe_shutdown_hooks() above and ensures // components are torn down in the opposite order of their setup_priority (which is // used to sort components during Application::setup()) - std::vector pending_components(this->components_.rbegin(), this->components_.rend()); + size_t num_components = this->components_.size(); + for (size_t i = 0; i < num_components; ++i) { + pending_components[i] = this->components_[num_components - 1 - i]; + } uint32_t now = start_time; - while (!pending_components.empty() && (now - start_time) < timeout_ms) { + size_t pending_count = num_components; + + // Teardown Algorithm + // ================== + // We iterate through pending components, calling teardown() on each. + // Components that return false (need more time) are copied forward + // in the array. Components that return true (finished) are skipped. + // + // The compaction happens in-place during iteration: + // - still_pending tracks the write position (where to put next pending component) + // - i tracks the read position (which component we're testing) + // - When teardown() returns false, we copy component[i] to component[still_pending] + // - When teardown() returns true, we just skip it (don't increment still_pending) + // + // Example with 4 components where B can teardown immediately: + // + // Start: + // pending_components: [A, B, C, D] + // pending_count: 4 ^----------^ + // + // Iteration 1: + // i=0: A needs more time → keep at pos 0 (no copy needed) + // i=1: B finished → skip + // i=2: C needs more time → copy to pos 1 + // i=3: D needs more time → copy to pos 2 + // + // After iteration 1: + // pending_components: [A, C, D | D] + // pending_count: 3 ^--------^ + // + // Iteration 2: + // i=0: A finished → skip + // i=1: C needs more time → copy to pos 0 + // i=2: D finished → skip + // + // After iteration 2: + // pending_components: [C | C, D, D] (positions 1-3 have old values) + // pending_count: 1 ^--^ + + while (pending_count > 0 && (now - start_time) < timeout_ms) { // Feed watchdog during teardown to prevent triggering this->feed_wdt(now); - // Use iterator to safely erase elements - for (auto it = pending_components.begin(); it != pending_components.end();) { - if ((*it)->teardown()) { - // Component finished teardown, erase it - it = pending_components.erase(it); - } else { - // Component still needs time - ++it; + // Process components and compact the array, keeping only those still pending + size_t still_pending = 0; + for (size_t i = 0; i < pending_count; ++i) { + if (!pending_components[i]->teardown()) { + // Component still needs time, copy it forward + if (still_pending != i) { + pending_components[still_pending] = pending_components[i]; + } + ++still_pending; } + // Component finished teardown, skip it (don't increment still_pending) } + pending_count = still_pending; // Give some time for I/O operations if components are still pending - if (!pending_components.empty()) { + if (pending_count > 0) { this->yield_with_select_(1); } @@ -287,11 +336,11 @@ void Application::teardown_components(uint32_t timeout_ms) { now = millis(); } - if (!pending_components.empty()) { + if (pending_count > 0) { // Note: At this point, connections are either disconnected or in a bad state, // so this warning will only appear via serial rather than being transmitted to clients - for (auto *component : pending_components) { - ESP_LOGW(TAG, "%s did not complete teardown within %" PRIu32 " ms", component->get_component_source(), + for (size_t i = 0; i < pending_count; ++i) { + ESP_LOGW(TAG, "%s did not complete teardown within %" PRIu32 " ms", pending_components[i]->get_component_source(), timeout_ms); } } diff --git a/esphome/core/application.h b/esphome/core/application.h index 4120afff53..9cb2a4c638 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -10,6 +10,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" #include "esphome/core/scheduler.h" +#include "esphome/core/string_ref.h" #ifdef USE_DEVICES #include "esphome/core/device.h" @@ -248,6 +249,8 @@ class Application { bool is_name_add_mac_suffix_enabled() const { return this->name_add_mac_suffix_; } std::string get_compilation_time() const { return this->compilation_time_; } + /// Get the compilation time as StringRef (for API usage) + StringRef get_compilation_time_ref() const { return StringRef(this->compilation_time_); } /// Get the cached time in milliseconds from when the current component started its loop execution inline uint32_t IRAM_ATTR HOT get_loop_component_start_time() const { return this->loop_component_start_time_; } diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index 2ea9c77a3e..411a877bbf 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -1,6 +1,7 @@ #include "esphome/core/entity_base.h" #include "esphome/core/application.h" #include "esphome/core/helpers.h" +#include "esphome/core/string_ref.h" namespace esphome { @@ -50,13 +51,18 @@ std::string EntityBase::get_object_id() const { if (!this->flags_.has_own_name && App.is_name_add_mac_suffix_enabled()) { // `App.get_friendly_name()` is dynamic. return str_sanitize(str_snake_case(App.get_friendly_name())); - } else { - // `App.get_friendly_name()` is constant. - if (this->object_id_c_str_ == nullptr) { - return ""; - } - return this->object_id_c_str_; } + // `App.get_friendly_name()` is constant. + return this->object_id_c_str_ == nullptr ? "" : this->object_id_c_str_; +} +StringRef EntityBase::get_object_id_ref_for_api_() const { + static constexpr auto EMPTY_STRING = StringRef::from_lit(""); + // Return empty for dynamic case (MAC suffix) + if (!this->flags_.has_own_name && App.is_name_add_mac_suffix_enabled()) { + return EMPTY_STRING; + } + // For static case, return the string or empty if null + return this->object_id_c_str_ == nullptr ? EMPTY_STRING : StringRef(this->object_id_c_str_); } void EntityBase::set_object_id(const char *object_id) { this->object_id_c_str_ = object_id; diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index e60e0728bc..68163ce8c3 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -12,6 +12,11 @@ namespace esphome { +// Forward declaration for friend access +namespace api { +class APIConnection; +} // namespace api + enum EntityCategory : uint8_t { ENTITY_CATEGORY_NONE = 0, ENTITY_CATEGORY_CONFIG = 1, @@ -81,6 +86,12 @@ class EntityBase { void set_has_state(bool state) { this->flags_.has_state = state; } protected: + friend class api::APIConnection; + + // Get object_id as StringRef when it's static (for API usage) + // Returns empty StringRef if object_id is dynamic (needs allocation) + StringRef get_object_id_ref_for_api_() const; + /// The hash_base() function has been deprecated. It is kept in this /// class for now, to prevent external components from not compiling. virtual uint32_t hash_base() { return 0L; } diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index e84f5a7317..44e9193994 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -41,17 +41,28 @@ static const uint16_t CRC16_1021_BE_LUT_H[] = {0x0000, 0x1231, 0x2462, 0x3653, 0 // Mathematics -uint8_t crc8(const uint8_t *data, uint8_t len) { - uint8_t crc = 0; - +uint8_t crc8(const uint8_t *data, uint8_t len, uint8_t crc, uint8_t poly, bool msb_first) { while ((len--) != 0u) { uint8_t inbyte = *data++; - for (uint8_t i = 8; i != 0u; i--) { - bool mix = (crc ^ inbyte) & 0x01; - crc >>= 1; - if (mix) - crc ^= 0x8C; - inbyte >>= 1; + if (msb_first) { + // MSB first processing (for polynomials like 0x31, 0x07) + crc ^= inbyte; + for (uint8_t i = 8; i != 0u; i--) { + if (crc & 0x80) { + crc = (crc << 1) ^ poly; + } else { + crc <<= 1; + } + } + } else { + // LSB first processing (default for Dallas/Maxim 0x8C) + for (uint8_t i = 8; i != 0u; i--) { + bool mix = (crc ^ inbyte) & 0x01; + crc >>= 1; + if (mix) + crc ^= poly; + inbyte >>= 1; + } } } return crc; diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index b5fe59c4fd..53ec7a2a5a 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -145,8 +145,8 @@ template T remap(U value, U min, U max, T min_out, T max return (value - min) * (max_out - min_out) / (max - min) + min_out; } -/// Calculate a CRC-8 checksum of \p data with size \p len using the CRC-8-Dallas/Maxim polynomial. -uint8_t crc8(const uint8_t *data, uint8_t len); +/// Calculate a CRC-8 checksum of \p data with size \p len. +uint8_t crc8(const uint8_t *data, uint8_t len, uint8_t crc = 0x00, uint8_t poly = 0x8C, bool msb_first = false); /// Calculate a CRC-16 checksum of \p data with size \p len. uint16_t crc16(const uint8_t *data, uint16_t len, uint16_t crc = 0xffff, uint16_t reverse_poly = 0xa001, diff --git a/platformio.ini b/platformio.ini index d9f2f879ec..47fc5205bc 100644 --- a/platformio.ini +++ b/platformio.ini @@ -357,6 +357,19 @@ build_flags = ${common:esp32-idf.build_flags} ${flags:runtime.build_flags} -DUSE_ESP32_VARIANT_ESP32C6 +build_unflags = + ${common.build_unflags} + +[env:esp32c6-idf-tidy] +extends = common:esp32-idf +board = esp32-c6-devkitc-1 +board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32c6-idf-tidy +build_flags = + ${common:esp32-idf.build_flags} + ${flags:clangtidy.build_flags} + -DUSE_ESP32_VARIANT_ESP32C6 +build_unflags = + ${common.build_unflags} ;;;;;;;; ESP32-S2 ;;;;;;;; diff --git a/requirements.txt b/requirements.txt index 0675115c02..2665211381 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,7 @@ esphome-dashboard==20250814.0 aioesphomeapi==39.0.0 zeroconf==0.147.0 puremagic==1.30 -ruamel.yaml==0.18.14 # dashboard_import +ruamel.yaml==0.18.15 # dashboard_import esphome-glyphsets==0.2.0 pillow==10.4.0 cairosvg==2.8.2 diff --git a/tests/components/homeassistant/test-tag-scanned.esp32-idf.yaml b/tests/components/homeassistant/test-tag-scanned.esp32-idf.yaml new file mode 100644 index 0000000000..ef148174d7 --- /dev/null +++ b/tests/components/homeassistant/test-tag-scanned.esp32-idf.yaml @@ -0,0 +1,14 @@ +wifi: + ssid: MySSID + password: password1 + +api: + +esphome: + on_boot: + then: + - homeassistant.tag_scanned: 'test_tag_123' + - homeassistant.tag_scanned: + tag: 'another_tag' + - homeassistant.tag_scanned: + tag: !lambda 'return "dynamic_tag";' diff --git a/tests/integration/fixtures/crc8_helper.yaml b/tests/integration/fixtures/crc8_helper.yaml new file mode 100644 index 0000000000..e97e23eab0 --- /dev/null +++ b/tests/integration/fixtures/crc8_helper.yaml @@ -0,0 +1,17 @@ +esphome: + name: crc8-helper-test + +host: + +api: + +logger: + level: INFO + +external_components: + - source: + type: local + path: EXTERNAL_COMPONENT_PATH + components: [crc8_test_component] + +crc8_test_component: diff --git a/tests/integration/fixtures/external_components/crc8_test_component/__init__.py b/tests/integration/fixtures/external_components/crc8_test_component/__init__.py new file mode 100644 index 0000000000..6032b0861f --- /dev/null +++ b/tests/integration/fixtures/external_components/crc8_test_component/__init__.py @@ -0,0 +1,17 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import CONF_ID + +crc8_test_component_ns = cg.esphome_ns.namespace("crc8_test_component") +CRC8TestComponent = crc8_test_component_ns.class_("CRC8TestComponent", cg.Component) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(CRC8TestComponent), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) diff --git a/tests/integration/fixtures/external_components/crc8_test_component/crc8_test_component.cpp b/tests/integration/fixtures/external_components/crc8_test_component/crc8_test_component.cpp new file mode 100644 index 0000000000..6c46af19fd --- /dev/null +++ b/tests/integration/fixtures/external_components/crc8_test_component/crc8_test_component.cpp @@ -0,0 +1,170 @@ +#include "crc8_test_component.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace crc8_test_component { + +static const char *const TAG = "crc8_test"; + +void CRC8TestComponent::setup() { + ESP_LOGI(TAG, "CRC8 Helper Function Integration Test Starting"); + + // Run all test suites + test_crc8_dallas_maxim(); + test_crc8_sensirion_style(); + test_crc8_pec_style(); + test_crc8_parameter_equivalence(); + test_crc8_edge_cases(); + test_component_compatibility(); + + ESP_LOGI(TAG, "CRC8 Integration Test Complete"); +} + +void CRC8TestComponent::test_crc8_dallas_maxim() { + ESP_LOGI(TAG, "Testing Dallas/Maxim CRC8 (default parameters)"); + + // Test vectors for Dallas/Maxim CRC8 (polynomial 0x8C, LSB-first, init 0x00) + const uint8_t test1[] = {0x01}; + const uint8_t test2[] = {0xFF}; + const uint8_t test3[] = {0x12, 0x34}; + const uint8_t test4[] = {0xAA, 0xBB, 0xCC}; + const uint8_t test5[] = {0x01, 0x02, 0x03, 0x04, 0x05}; + + bool all_passed = true; + all_passed &= verify_crc8("Dallas [0x01]", test1, sizeof(test1), 0x5E); + all_passed &= verify_crc8("Dallas [0xFF]", test2, sizeof(test2), 0x35); + all_passed &= verify_crc8("Dallas [0x12, 0x34]", test3, sizeof(test3), 0xA2); + all_passed &= verify_crc8("Dallas [0xAA, 0xBB, 0xCC]", test4, sizeof(test4), 0xD4); + all_passed &= verify_crc8("Dallas [0x01...0x05]", test5, sizeof(test5), 0x2A); + + log_test_result("Dallas/Maxim CRC8", all_passed); +} + +void CRC8TestComponent::test_crc8_sensirion_style() { + ESP_LOGI(TAG, "Testing Sensirion CRC8 (0x31 poly, MSB-first, init 0xFF)"); + + const uint8_t test1[] = {0x00}; + const uint8_t test2[] = {0x01}; + const uint8_t test3[] = {0xFF}; + const uint8_t test4[] = {0x12, 0x34}; + const uint8_t test5[] = {0xBE, 0xEF}; + + bool all_passed = true; + all_passed &= verify_crc8("Sensirion [0x00]", test1, sizeof(test1), 0xAC, 0xFF, 0x31, true); + all_passed &= verify_crc8("Sensirion [0x01]", test2, sizeof(test2), 0x9D, 0xFF, 0x31, true); + all_passed &= verify_crc8("Sensirion [0xFF]", test3, sizeof(test3), 0x00, 0xFF, 0x31, true); + all_passed &= verify_crc8("Sensirion [0x12, 0x34]", test4, sizeof(test4), 0x37, 0xFF, 0x31, true); + all_passed &= verify_crc8("Sensirion [0xBE, 0xEF]", test5, sizeof(test5), 0x92, 0xFF, 0x31, true); + + log_test_result("Sensirion CRC8", all_passed); +} + +void CRC8TestComponent::test_crc8_pec_style() { + ESP_LOGI(TAG, "Testing PEC CRC8 (0x07 poly, MSB-first, init 0x00)"); + + const uint8_t test1[] = {0x00}; + const uint8_t test2[] = {0x01}; + const uint8_t test3[] = {0xFF}; + const uint8_t test4[] = {0x12, 0x34}; + const uint8_t test5[] = {0xAA, 0xBB}; + + bool all_passed = true; + all_passed &= verify_crc8("PEC [0x00]", test1, sizeof(test1), 0x00, 0x00, 0x07, true); + all_passed &= verify_crc8("PEC [0x01]", test2, sizeof(test2), 0x07, 0x00, 0x07, true); + all_passed &= verify_crc8("PEC [0xFF]", test3, sizeof(test3), 0xF3, 0x00, 0x07, true); + all_passed &= verify_crc8("PEC [0x12, 0x34]", test4, sizeof(test4), 0xF1, 0x00, 0x07, true); + all_passed &= verify_crc8("PEC [0xAA, 0xBB]", test5, sizeof(test5), 0xB2, 0x00, 0x07, true); + + log_test_result("PEC CRC8", all_passed); +} + +void CRC8TestComponent::test_crc8_parameter_equivalence() { + ESP_LOGI(TAG, "Testing parameter equivalence"); + + const uint8_t test_data[] = {0x12, 0x34, 0x56, 0x78}; + + // Test that default parameters work as expected + uint8_t default_result = crc8(test_data, sizeof(test_data)); + uint8_t explicit_result = crc8(test_data, sizeof(test_data), 0x00, 0x8C, false); + + bool passed = (default_result == explicit_result); + if (!passed) { + ESP_LOGE(TAG, "Parameter equivalence FAILED: default=0x%02X, explicit=0x%02X", default_result, explicit_result); + } + + log_test_result("Parameter equivalence", passed); +} + +void CRC8TestComponent::test_crc8_edge_cases() { + ESP_LOGI(TAG, "Testing edge cases"); + + bool all_passed = true; + + // Empty array test + const uint8_t empty[] = {}; + uint8_t empty_result = crc8(empty, 0); + bool empty_passed = (empty_result == 0x00); // Should return init value + if (!empty_passed) { + ESP_LOGE(TAG, "Empty array test FAILED: expected 0x00, got 0x%02X", empty_result); + } + all_passed &= empty_passed; + + // Single byte tests + const uint8_t single_zero[] = {0x00}; + const uint8_t single_ff[] = {0xFF}; + all_passed &= verify_crc8("Single [0x00]", single_zero, 1, 0x00); + all_passed &= verify_crc8("Single [0xFF]", single_ff, 1, 0x35); + + log_test_result("Edge cases", all_passed); +} + +void CRC8TestComponent::test_component_compatibility() { + ESP_LOGI(TAG, "Testing component compatibility"); + + // Test specific component use cases + bool all_passed = true; + + // AGS10-style data (Sensirion CRC8) + const uint8_t ags10_data[] = {0x12, 0x34, 0x56}; + uint8_t ags10_result = crc8(ags10_data, sizeof(ags10_data), 0xFF, 0x31, true); + ESP_LOGI(TAG, "AGS10-style CRC8: 0x%02X", ags10_result); + + // LC709203F-style data (PEC CRC8) + const uint8_t lc_data[] = {0xAA, 0xBB}; + uint8_t lc_result = crc8(lc_data, sizeof(lc_data), 0x00, 0x07, true); + ESP_LOGI(TAG, "LC709203F-style CRC8: 0x%02X", lc_result); + + // DallasTemperature-style data (Dallas CRC8) + const uint8_t dallas_data[] = {0x28, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC}; + uint8_t dallas_result = crc8(dallas_data, sizeof(dallas_data)); + ESP_LOGI(TAG, "Dallas-style CRC8: 0x%02X", dallas_result); + + all_passed = true; // These are just demonstration tests + log_test_result("Component compatibility", all_passed); +} + +bool CRC8TestComponent::verify_crc8(const char *test_name, const uint8_t *data, uint8_t len, uint8_t expected, + uint8_t crc, uint8_t poly, bool msb_first) { + uint8_t result = esphome::crc8(data, len, crc, poly, msb_first); + bool passed = (result == expected); + + if (passed) { + ESP_LOGI(TAG, "%s: PASS (0x%02X)", test_name, result); + } else { + ESP_LOGE(TAG, "%s: FAIL - expected 0x%02X, got 0x%02X", test_name, expected, result); + } + + return passed; +} + +void CRC8TestComponent::log_test_result(const char *test_name, bool passed) { + if (passed) { + ESP_LOGI(TAG, "%s: ALL TESTS PASSED", test_name); + } else { + ESP_LOGE(TAG, "%s: SOME TESTS FAILED", test_name); + } +} + +} // namespace crc8_test_component +} // namespace esphome diff --git a/tests/integration/fixtures/external_components/crc8_test_component/crc8_test_component.h b/tests/integration/fixtures/external_components/crc8_test_component/crc8_test_component.h new file mode 100644 index 0000000000..3b8847259c --- /dev/null +++ b/tests/integration/fixtures/external_components/crc8_test_component/crc8_test_component.h @@ -0,0 +1,29 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace crc8_test_component { + +class CRC8TestComponent : public Component { + public: + void setup() override; + + private: + void test_crc8_dallas_maxim(); + void test_crc8_sensirion_style(); + void test_crc8_pec_style(); + void test_crc8_parameter_equivalence(); + void test_crc8_edge_cases(); + void test_component_compatibility(); + void test_old_vs_new_implementations(); + + void log_test_result(const char *test_name, bool passed); + bool verify_crc8(const char *test_name, const uint8_t *data, uint8_t len, uint8_t expected, uint8_t crc = 0x00, + uint8_t poly = 0x8C, bool msb_first = false); +}; + +} // namespace crc8_test_component +} // namespace esphome diff --git a/tests/integration/test_crc8_helper.py b/tests/integration/test_crc8_helper.py new file mode 100644 index 0000000000..ffe6244598 --- /dev/null +++ b/tests/integration/test_crc8_helper.py @@ -0,0 +1,100 @@ +"""Integration test for CRC8 helper function.""" + +from __future__ import annotations + +import asyncio +from pathlib import Path + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_crc8_helper( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test the CRC8 helper function through integration testing.""" + # Get the path to the external components directory + external_components_path = str( + Path(__file__).parent / "fixtures" / "external_components" + ) + + # Replace the placeholder in the YAML config with the actual path + yaml_config = yaml_config.replace( + "EXTERNAL_COMPONENT_PATH", external_components_path + ) + + # Track test completion with asyncio.Event + test_complete = asyncio.Event() + + # Track test results + test_results = { + "dallas_maxim": False, + "sensirion": False, + "pec": False, + "parameter_equivalence": False, + "edge_cases": False, + "component_compatibility": False, + "setup_started": False, + } + + def on_log_line(line): + """Process log lines to track test progress and results.""" + # Track test start + if "CRC8 Helper Function Integration Test Starting" in line: + test_results["setup_started"] = True + + # Track test completion + elif "CRC8 Integration Test Complete" in line: + test_complete.set() + + # Track individual test results + elif "ALL TESTS PASSED" in line: + if "Dallas/Maxim CRC8" in line: + test_results["dallas_maxim"] = True + elif "Sensirion CRC8" in line: + test_results["sensirion"] = True + elif "PEC CRC8" in line: + test_results["pec"] = True + elif "Parameter equivalence" in line: + test_results["parameter_equivalence"] = True + elif "Edge cases" in line: + test_results["edge_cases"] = True + elif "Component compatibility" in line: + test_results["component_compatibility"] = True + + # Log failures for debugging + elif "TEST FAILED:" in line or "SUBTEST FAILED:" in line: + print(f"CRC8 Test Failure: {line}") + + # Compile and run the test + async with ( + run_compiled(yaml_config, line_callback=on_log_line), + api_client_connected() as client, + ): + # Verify device info + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "crc8-helper-test" + + # Wait for tests to complete with timeout + try: + await asyncio.wait_for(test_complete.wait(), timeout=5.0) + except TimeoutError: + pytest.fail("CRC8 integration test timed out after 5 seconds") + + # Verify all tests passed + assert test_results["setup_started"], "CRC8 test setup never started" + assert test_results["dallas_maxim"], "Dallas/Maxim CRC8 test failed" + assert test_results["sensirion"], "Sensirion CRC8 test failed" + assert test_results["pec"], "PEC CRC8 test failed" + assert test_results["parameter_equivalence"], ( + "Parameter equivalence test failed" + ) + assert test_results["edge_cases"], "Edge cases test failed" + assert test_results["component_compatibility"], ( + "Component compatibility test failed" + )