diff --git a/.clang-tidy.hash b/.clang-tidy.hash index 30c52f5baa..d36ae1f164 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -a3cdfc378d28b53b416a1d5bf0ab9077ee18867f0d39436ea8013cf5a4ead87a \ No newline at end of file +a3cdfc378d28b53b416a1d5bf0ab9077ee18867f0d39436ea8013cf5a4ead87a diff --git a/.coveragerc b/.coveragerc index 12e48ec395..f23592be24 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,4 +1,4 @@ [run] -omit = +omit = esphome/components/* tests/integration/* diff --git a/.github/workflows/ci-clang-tidy-hash.yml b/.github/workflows/ci-clang-tidy-hash.yml index 4e89da267c..1c7a62e40b 100644 --- a/.github/workflows/ci-clang-tidy-hash.yml +++ b/.github/workflows/ci-clang-tidy-hash.yml @@ -73,4 +73,3 @@ jobs: }); } } - diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e2e114fdb..94e490a4c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,29 +84,6 @@ jobs: run: script/ci-suggest-changes if: always() - pyupgrade: - name: Check pyupgrade - runs-on: ubuntu-24.04 - needs: - - common - - determine-jobs - if: needs.determine-jobs.outputs.python-linters == 'true' - steps: - - name: Check out code from GitHub - uses: actions/checkout@v4.2.2 - - name: Restore Python - uses: ./.github/actions/restore-python - with: - python-version: ${{ env.DEFAULT_PYTHON }} - cache-key: ${{ needs.common.outputs.cache-key }} - - name: Run pyupgrade - run: | - . venv/bin/activate - pyupgrade ${{ env.PYUPGRADE_TARGET }} `find esphome -name "*.py" -type f` - - name: Suggested changes - run: script/ci-suggest-changes - if: always() - ci-custom: name: Run script/ci-custom runs-on: ubuntu-24.04 @@ -512,7 +489,7 @@ jobs: cache-key: ${{ needs.common.outputs.cache-key }} - uses: pre-commit/action@v3.0.1 env: - SKIP: pylint,clang-tidy-hash,yamllint + SKIP: pylint,clang-tidy-hash - uses: pre-commit-ci/lite-action@v1.1.0 if: always() @@ -525,7 +502,6 @@ jobs: - pylint - pytest - integration-tests - - pyupgrade - clang-tidy-deps - clang-tidy - determine-jobs diff --git a/.github/workflows/yaml-lint.yml b/.github/workflows/yaml-lint.yml deleted file mode 100644 index ed9b4407a2..0000000000 --- a/.github/workflows/yaml-lint.yml +++ /dev/null @@ -1,25 +0,0 @@ ---- -name: YAML lint - -on: - push: - branches: [dev, beta, release] - paths: - - "**.yaml" - - "**.yml" - pull_request: - paths: - - "**.yaml" - - "**.yml" - -jobs: - yamllint: - name: yamllint - runs-on: ubuntu-latest - steps: - - name: Check out code from GitHub - uses: actions/checkout@v4.2.2 - - name: Run yamllint - uses: frenck/action-yamllint@v1.5.0 - with: - strict: true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8db7f21599..118253861d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ ci: autoupdate_commit_msg: 'pre-commit: autoupdate' autoupdate_schedule: off # Disabled until ruff versions are synced between deps and pre-commit # Skip hooks that have issues in pre-commit CI environment - skip: [pylint, clang-tidy-hash, yamllint] + skip: [pylint, clang-tidy-hash] repos: - repo: https://github.com/astral-sh/ruff-pre-commit @@ -27,13 +27,15 @@ repos: - pydocstyle==5.1.1 files: ^(esphome|tests)/.+\.py$ - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.4.0 + rev: v5.0.0 hooks: - id: no-commit-to-branch args: - --branch=dev - --branch=release - --branch=beta + - id: end-of-file-fixer + - id: trailing-whitespace - repo: https://github.com/asottile/pyupgrade rev: v3.20.0 hooks: @@ -43,6 +45,7 @@ repos: rev: v1.37.1 hooks: - id: yamllint + exclude: ^(\.clang-format|\.clang-tidy)$ - repo: https://github.com/pre-commit/mirrors-clang-format rev: v13.0.1 hooks: diff --git a/esphome/components/async_tcp/__init__.py b/esphome/components/async_tcp/__init__.py index 29097ce1b6..4a469fa0e0 100644 --- a/esphome/components/async_tcp/__init__.py +++ b/esphome/components/async_tcp/__init__.py @@ -31,7 +31,7 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): if CORE.is_esp32 or CORE.is_libretiny: # https://github.com/ESP32Async/AsyncTCP - cg.add_library("ESP32Async/AsyncTCP", "3.4.4") + cg.add_library("ESP32Async/AsyncTCP", "3.4.5") elif CORE.is_esp8266: # https://github.com/ESP32Async/ESPAsyncTCP cg.add_library("ESP32Async/ESPAsyncTCP", "2.0.0") diff --git a/esphome/components/bluetooth_proxy/__init__.py b/esphome/components/bluetooth_proxy/__init__.py index 5c144cadcc..a1e9d464df 100644 --- a/esphome/components/bluetooth_proxy/__init__.py +++ b/esphome/components/bluetooth_proxy/__init__.py @@ -85,13 +85,13 @@ async def to_code(config): await cg.register_component(var, config) cg.add(var.set_active(config[CONF_ACTIVE])) - await esp32_ble_tracker.register_ble_device(var, config) + await esp32_ble_tracker.register_raw_ble_device(var, config) for connection_conf in config.get(CONF_CONNECTIONS, []): connection_var = cg.new_Pvariable(connection_conf[CONF_ID]) await cg.register_component(connection_var, connection_conf) cg.add(var.register_connection(connection_var)) - await esp32_ble_tracker.register_client(connection_var, connection_conf) + await esp32_ble_tracker.register_raw_client(connection_var, connection_conf) if config.get(CONF_CACHE_SERVICES): add_idf_sdkconfig_option("CONFIG_BT_GATTC_CACHE_NVS_FLASH", True) diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index a5e8ec0860..1c856b8d93 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -42,15 +42,13 @@ void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerSta this->api_connection_->send_message(resp); } +#ifdef USE_ESP32_BLE_DEVICE bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { - if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || this->raw_advertisements_) - return false; - - ESP_LOGV(TAG, "Proxying packet from %s - %s. RSSI: %d dB", device.get_name().c_str(), device.address_str().c_str(), - device.get_rssi()); - this->send_api_packet_(device); - return true; + // This method should never be called since bluetooth_proxy always uses raw advertisements + // but we need to provide an implementation to satisfy the virtual method requirement + return false; } +#endif // Batch size for BLE advertisements to maximize WiFi efficiency // Each advertisement is up to 80 bytes when packaged (including protocol overhead) @@ -69,7 +67,7 @@ std::vector batch_buffer; static std::vector &get_batch_buffer() { return batch_buffer; } bool BluetoothProxy::parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) { - if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || !this->raw_advertisements_) + if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) return false; // Get the batch buffer reference @@ -116,6 +114,7 @@ void BluetoothProxy::flush_pending_advertisements() { this->api_connection_->send_message(resp); } +#ifdef USE_ESP32_BLE_DEVICE void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) { api::BluetoothLEAdvertisementResponse resp; resp.address = device.address_uint64(); @@ -153,14 +152,14 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi this->api_connection_->send_message(resp); } +#endif // USE_ESP32_BLE_DEVICE void BluetoothProxy::dump_config() { ESP_LOGCONFIG(TAG, "Bluetooth Proxy:"); ESP_LOGCONFIG(TAG, " Active: %s\n" - " Connections: %d\n" - " Raw advertisements: %s", - YESNO(this->active_), this->connections_.size(), YESNO(this->raw_advertisements_)); + " Connections: %d", + YESNO(this->active_), this->connections_.size()); } int BluetoothProxy::get_bluetooth_connections_free() { @@ -188,15 +187,13 @@ void BluetoothProxy::loop() { } // Flush any pending BLE advertisements that have been accumulated but not yet sent - if (this->raw_advertisements_) { - static uint32_t last_flush_time = 0; - uint32_t now = App.get_loop_component_start_time(); + static uint32_t last_flush_time = 0; + uint32_t now = App.get_loop_component_start_time(); - // Flush accumulated advertisements every 100ms - if (now - last_flush_time >= 100) { - this->flush_pending_advertisements(); - last_flush_time = now; - } + // Flush accumulated advertisements every 100ms + if (now - last_flush_time >= 100) { + this->flush_pending_advertisements(); + last_flush_time = now; } for (auto *connection : this->connections_) { if (connection->send_service_ == connection->service_count_) { @@ -318,9 +315,7 @@ void BluetoothProxy::loop() { } esp32_ble_tracker::AdvertisementParserType BluetoothProxy::get_advertisement_parser_type() { - if (this->raw_advertisements_) - return esp32_ble_tracker::AdvertisementParserType::RAW_ADVERTISEMENTS; - return esp32_ble_tracker::AdvertisementParserType::PARSED_ADVERTISEMENTS; + return esp32_ble_tracker::AdvertisementParserType::RAW_ADVERTISEMENTS; } BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool reserve) { @@ -565,7 +560,6 @@ void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection return; } this->api_connection_ = api_connection; - this->raw_advertisements_ = flags & BluetoothProxySubscriptionFlag::SUBSCRIPTION_RAW_ADVERTISEMENTS; this->parent_->recalculate_advertisement_parser_types(); this->send_bluetooth_scanner_state_(this->parent_->get_scanner_state()); @@ -577,7 +571,6 @@ void BluetoothProxy::unsubscribe_api_connection(api::APIConnection *api_connecti return; } this->api_connection_ = nullptr; - this->raw_advertisements_ = false; this->parent_->recalculate_advertisement_parser_types(); } diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.h b/esphome/components/bluetooth_proxy/bluetooth_proxy.h index f0632350e0..3ccf0706a7 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.h +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.h @@ -51,7 +51,9 @@ enum BluetoothProxySubscriptionFlag : uint32_t { class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Component { public: BluetoothProxy(); +#ifdef USE_ESP32_BLE_DEVICE bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; +#endif bool parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) override; void dump_config() override; void setup() override; @@ -129,7 +131,9 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com } protected: +#ifdef USE_ESP32_BLE_DEVICE void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device); +#endif void send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state); BluetoothConnection *get_connection_(uint64_t address, bool reserve); @@ -143,8 +147,7 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com // Group 3: 1-byte types grouped together bool active_; - bool raw_advertisements_{false}; - // 2 bytes used, 2 bytes padding + // 1 byte used, 3 bytes padding }; extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp index 7d0a3bbfd5..bf425b3730 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.cpp +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -105,6 +105,7 @@ void BLEClientBase::dump_config() { } } +#ifdef USE_ESP32_BLE_DEVICE bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) { if (!this->auto_connect_) return false; @@ -122,6 +123,7 @@ bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) { this->remote_addr_type_ = device.get_address_type(); return true; } +#endif void BLEClientBase::connect() { ESP_LOGI(TAG, "[%d] [%s] 0x%02x Attempting BLE connection", this->connection_index_, this->address_str_.c_str(), diff --git a/esphome/components/esp32_ble_client/ble_client_base.h b/esphome/components/esp32_ble_client/ble_client_base.h index bf3b589b1b..457a88ec1d 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.h +++ b/esphome/components/esp32_ble_client/ble_client_base.h @@ -31,7 +31,9 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { void dump_config() override; void run_later(std::function &&f); // NOLINT +#ifdef USE_ESP32_BLE_DEVICE bool parse_device(const espbt::ESPBTDevice &device) override; +#endif void on_scan_end() override {} bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override; diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index 547cf84ed1..68f4657515 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -31,6 +31,8 @@ from esphome.const import ( CONF_TRIGGER_ID, ) from esphome.core import CORE +from esphome.enum import StrEnum +from esphome.types import ConfigType AUTO_LOAD = ["esp32_ble"] DEPENDENCIES = ["esp32"] @@ -50,6 +52,25 @@ IDF_MAX_CONNECTIONS = 9 _LOGGER = logging.getLogger(__name__) + +# Enum for BLE features +class BLEFeatures(StrEnum): + ESP_BT_DEVICE = "ESP_BT_DEVICE" + + +# Set to track which features are needed by components +_required_features: set[BLEFeatures] = set() + + +def register_ble_features(features: set[BLEFeatures]) -> None: + """Register BLE features that a component needs. + + Args: + features: Set of BLEFeatures enum members + """ + _required_features.update(features) + + esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker") ESP32BLETracker = esp32_ble_tracker_ns.class_( "ESP32BLETracker", @@ -277,6 +298,15 @@ async def to_code(config): cg.add(var.set_scan_window(int(params[CONF_WINDOW].total_milliseconds / 0.625))) cg.add(var.set_scan_active(params[CONF_ACTIVE])) cg.add(var.set_scan_continuous(params[CONF_CONTINUOUS])) + + # Register ESP_BT_DEVICE feature if any of the automation triggers are used + if ( + config.get(CONF_ON_BLE_ADVERTISE) + or config.get(CONF_ON_BLE_SERVICE_DATA_ADVERTISE) + or config.get(CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE) + ): + register_ble_features({BLEFeatures.ESP_BT_DEVICE}) + for conf in config.get(CONF_ON_BLE_ADVERTISE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) if CONF_MAC_ADDRESS in conf: @@ -334,6 +364,11 @@ async def to_code(config): cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts cg.add_define("USE_ESP32_BLE_CLIENT") + + # Add feature-specific defines based on what's needed + if BLEFeatures.ESP_BT_DEVICE in _required_features: + cg.add_define("USE_ESP32_BLE_DEVICE") + if config.get(CONF_SOFTWARE_COEXISTENCE): cg.add_define("USE_ESP32_BLE_SOFTWARE_COEXISTENCE") @@ -382,13 +417,43 @@ async def esp32_ble_tracker_stop_scan_action_to_code( return var -async def register_ble_device(var, config): +async def register_ble_device( + var: cg.SafeExpType, config: ConfigType +) -> cg.SafeExpType: + register_ble_features({BLEFeatures.ESP_BT_DEVICE}) paren = await cg.get_variable(config[CONF_ESP32_BLE_ID]) cg.add(paren.register_listener(var)) return var -async def register_client(var, config): +async def register_client(var: cg.SafeExpType, config: ConfigType) -> cg.SafeExpType: + register_ble_features({BLEFeatures.ESP_BT_DEVICE}) + paren = await cg.get_variable(config[CONF_ESP32_BLE_ID]) + cg.add(paren.register_client(var)) + return var + + +async def register_raw_ble_device( + var: cg.SafeExpType, config: ConfigType +) -> cg.SafeExpType: + """Register a BLE device listener that only needs raw advertisement data. + + This does NOT register the ESP_BT_DEVICE feature, meaning ESPBTDevice + will not be compiled in if this is the only registration method used. + """ + paren = await cg.get_variable(config[CONF_ESP32_BLE_ID]) + cg.add(paren.register_listener(var)) + return var + + +async def register_raw_client( + var: cg.SafeExpType, config: ConfigType +) -> cg.SafeExpType: + """Register a BLE client that only needs raw advertisement data. + + This does NOT register the ESP_BT_DEVICE feature, meaning ESPBTDevice + will not be compiled in if this is the only registration method used. + """ paren = await cg.get_variable(config[CONF_ESP32_BLE_ID]) cg.add(paren.register_client(var)) return var diff --git a/esphome/components/esp32_ble_tracker/automation.h b/esphome/components/esp32_ble_tracker/automation.h index 6bef9edcb3..ef677922e3 100644 --- a/esphome/components/esp32_ble_tracker/automation.h +++ b/esphome/components/esp32_ble_tracker/automation.h @@ -7,6 +7,7 @@ namespace esphome { namespace esp32_ble_tracker { +#ifdef USE_ESP32_BLE_DEVICE class ESPBTAdvertiseTrigger : public Trigger, public ESPBTDeviceListener { public: explicit ESPBTAdvertiseTrigger(ESP32BLETracker *parent) { parent->register_listener(this); } @@ -87,6 +88,7 @@ class BLEEndOfScanTrigger : public Trigger<>, public ESPBTDeviceListener { bool parse_device(const ESPBTDevice &device) override { return false; } void on_scan_end() override { this->trigger(); } }; +#endif // USE_ESP32_BLE_DEVICE template class ESP32BLEStartScanAction : public Action { public: diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index d950ccb5f1..44577afbbd 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -141,6 +141,7 @@ void ESP32BLETracker::loop() { } if (this->parse_advertisements_) { +#ifdef USE_ESP32_BLE_DEVICE ESPBTDevice device; device.parse_scan_rst(scan_result); @@ -162,6 +163,7 @@ void ESP32BLETracker::loop() { if (!found && !this->scan_continuous_) { this->print_bt_device_info(device); } +#endif // USE_ESP32_BLE_DEVICE } // Move to next entry in ring buffer @@ -511,6 +513,7 @@ void ESP32BLETracker::set_scanner_state_(ScannerState state) { this->scanner_state_callbacks_.call(state); } +#ifdef USE_ESP32_BLE_DEVICE ESPBLEiBeacon::ESPBLEiBeacon(const uint8_t *data) { memcpy(&this->beacon_data_, data, sizeof(beacon_data_)); } optional ESPBLEiBeacon::from_manufacturer_data(const ServiceData &data) { if (!data.uuid.contains(0x4C, 0x00)) @@ -751,13 +754,16 @@ void ESPBTDevice::parse_adv_(const uint8_t *payload, uint8_t len) { } } } + std::string ESPBTDevice::address_str() const { char mac[24]; snprintf(mac, sizeof(mac), "%02X:%02X:%02X:%02X:%02X:%02X", this->address_[0], this->address_[1], this->address_[2], this->address_[3], this->address_[4], this->address_[5]); return mac; } + uint64_t ESPBTDevice::address_uint64() const { return esp32_ble::ble_addr_to_uint64(this->address_); } +#endif // USE_ESP32_BLE_DEVICE void ESP32BLETracker::dump_config() { ESP_LOGCONFIG(TAG, "BLE Tracker:"); @@ -796,6 +802,7 @@ void ESP32BLETracker::dump_config() { } } +#ifdef USE_ESP32_BLE_DEVICE void ESP32BLETracker::print_bt_device_info(const ESPBTDevice &device) { const uint64_t address = device.address_uint64(); for (auto &disc : this->already_discovered_) { @@ -866,8 +873,9 @@ bool ESPBTDevice::resolve_irk(const uint8_t *irk) const { return ecb_ciphertext[15] == (addr64 & 0xff) && ecb_ciphertext[14] == ((addr64 >> 8) & 0xff) && ecb_ciphertext[13] == ((addr64 >> 16) & 0xff); } +#endif // USE_ESP32_BLE_DEVICE } // namespace esp32_ble_tracker } // namespace esphome -#endif +#endif // USE_ESP32 diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index f5ed75a93e..e10f4551e8 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -39,6 +39,7 @@ struct ServiceData { adv_data_t data; }; +#ifdef USE_ESP32_BLE_DEVICE class ESPBLEiBeacon { public: ESPBLEiBeacon() { memset(&this->beacon_data_, 0, sizeof(this->beacon_data_)); } @@ -116,13 +117,16 @@ class ESPBTDevice { std::vector service_datas_{}; const BLEScanResult *scan_result_{nullptr}; }; +#endif // USE_ESP32_BLE_DEVICE class ESP32BLETracker; class ESPBTDeviceListener { public: virtual void on_scan_end() {} +#ifdef USE_ESP32_BLE_DEVICE virtual bool parse_device(const ESPBTDevice &device) = 0; +#endif virtual bool parse_devices(const BLEScanResult *scan_results, size_t count) { return false; }; virtual AdvertisementParserType get_advertisement_parser_type() { return AdvertisementParserType::PARSED_ADVERTISEMENTS; @@ -237,7 +241,9 @@ class ESP32BLETracker : public Component, void register_client(ESPBTClient *client); void recalculate_advertisement_parser_types(); +#ifdef USE_ESP32_BLE_DEVICE void print_bt_device_info(const ESPBTDevice &device); +#endif void start_scan(); void stop_scan(); diff --git a/esphome/components/http_request/update/http_request_update.cpp b/esphome/components/http_request/update/http_request_update.cpp index 202c7b88b2..06aa6da6a4 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -83,7 +83,7 @@ void HttpRequestUpdate::update_task(void *params) { container.reset(); // Release ownership of the container's shared_ptr valid = json::parse_json(response, [this_update](JsonObject root) -> bool { - if (!root.containsKey("name") || !root.containsKey("version") || !root.containsKey("builds")) { + if (!root["name"].is() || !root["version"].is() || !root["builds"].is()) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } @@ -91,26 +91,26 @@ void HttpRequestUpdate::update_task(void *params) { this_update->update_info_.latest_version = root["version"].as(); for (auto build : root["builds"].as()) { - if (!build.containsKey("chipFamily")) { + if (!build["chipFamily"].is()) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } if (build["chipFamily"] == ESPHOME_VARIANT) { - if (!build.containsKey("ota")) { + if (!build["ota"].is()) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } - auto ota = build["ota"]; - if (!ota.containsKey("path") || !ota.containsKey("md5")) { + JsonObject ota = build["ota"].as(); + if (!ota["path"].is() || !ota["md5"].is()) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } this_update->update_info_.firmware_url = ota["path"].as(); this_update->update_info_.md5 = ota["md5"].as(); - if (ota.containsKey("summary")) + if (ota["summary"].is()) this_update->update_info_.summary = ota["summary"].as(); - if (ota.containsKey("release_url")) + if (ota["release_url"].is()) this_update->update_info_.release_url = ota["release_url"].as(); return true; diff --git a/esphome/components/json/__init__.py b/esphome/components/json/__init__.py index 6a0e4c50d2..9773bf67ce 100644 --- a/esphome/components/json/__init__.py +++ b/esphome/components/json/__init__.py @@ -12,6 +12,6 @@ CONFIG_SCHEMA = cv.All( @coroutine_with_priority(1.0) async def to_code(config): - cg.add_library("bblanchon/ArduinoJson", "6.18.5") + cg.add_library("bblanchon/ArduinoJson", "7.4.2") cg.add_define("USE_JSON") cg.add_global(json_ns.using) diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index 6c66476dc1..94c531222a 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -1,83 +1,76 @@ #include "json_util.h" #include "esphome/core/log.h" +// ArduinoJson::Allocator is included via ArduinoJson.h in json_util.h + namespace esphome { namespace json { static const char *const TAG = "json"; -static std::vector global_json_build_buffer; // NOLINT -static const auto ALLOCATOR = RAMAllocator(RAMAllocator::ALLOC_INTERNAL); +// Build an allocator for the JSON Library using the RAMAllocator class +struct SpiRamAllocator : ArduinoJson::Allocator { + void *allocate(size_t size) override { return this->allocator_.allocate(size); } + + void deallocate(void *pointer) override { + // ArduinoJson's Allocator interface doesn't provide the size parameter in deallocate. + // RAMAllocator::deallocate() requires the size, which we don't have access to here. + // RAMAllocator::deallocate implementation just calls free() regardless of whether + // the memory was allocated with heap_caps_malloc or malloc. + // This is safe because ESP-IDF's heap implementation internally tracks the memory region + // and routes free() to the appropriate heap. + free(pointer); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc) + } + + void *reallocate(void *ptr, size_t new_size) override { + return this->allocator_.reallocate(static_cast(ptr), new_size); + } + + protected: + RAMAllocator allocator_{RAMAllocator(RAMAllocator::NONE)}; +}; std::string build_json(const json_build_t &f) { - // Here we are allocating up to 5kb of memory, - // with the heap size minus 2kb to be safe if less than 5kb - // as we can not have a true dynamic sized document. - // The excess memory is freed below with `shrinkToFit()` - auto free_heap = ALLOCATOR.get_max_free_block_size(); - size_t request_size = std::min(free_heap, (size_t) 512); - while (true) { - ESP_LOGV(TAG, "Attempting to allocate %zu bytes for JSON serialization", request_size); - DynamicJsonDocument json_document(request_size); - if (json_document.capacity() == 0) { - ESP_LOGE(TAG, "Could not allocate memory for document! Requested %zu bytes, largest free heap block: %zu bytes", - request_size, free_heap); - return "{}"; - } - JsonObject root = json_document.to(); - f(root); - if (json_document.overflowed()) { - if (request_size == free_heap) { - ESP_LOGE(TAG, "Could not allocate memory for document! Overflowed largest free heap block: %zu bytes", - free_heap); - return "{}"; - } - request_size = std::min(request_size * 2, free_heap); - continue; - } - json_document.shrinkToFit(); - ESP_LOGV(TAG, "Size after shrink %zu bytes", json_document.capacity()); - std::string output; - serializeJson(json_document, output); - return output; + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + auto doc_allocator = SpiRamAllocator(); + JsonDocument json_document(&doc_allocator); + if (json_document.overflowed()) { + ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); + return "{}"; } + JsonObject root = json_document.to(); + f(root); + if (json_document.overflowed()) { + ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); + return "{}"; + } + std::string output; + serializeJson(json_document, output); + return output; + // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) } bool parse_json(const std::string &data, const json_parse_t &f) { - // Here we are allocating 1.5 times the data size, - // with the heap size minus 2kb to be safe if less than that - // as we can not have a true dynamic sized document. - // The excess memory is freed below with `shrinkToFit()` - auto free_heap = ALLOCATOR.get_max_free_block_size(); - size_t request_size = std::min(free_heap, (size_t) (data.size() * 1.5)); - while (true) { - DynamicJsonDocument json_document(request_size); - if (json_document.capacity() == 0) { - ESP_LOGE(TAG, "Could not allocate memory for document! Requested %zu bytes, free heap: %zu", request_size, - free_heap); - return false; - } - DeserializationError err = deserializeJson(json_document, data); - json_document.shrinkToFit(); + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + auto doc_allocator = SpiRamAllocator(); + JsonDocument json_document(&doc_allocator); + if (json_document.overflowed()) { + ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); + return false; + } + DeserializationError err = deserializeJson(json_document, data); - JsonObject root = json_document.as(); + JsonObject root = json_document.as(); - if (err == DeserializationError::Ok) { - return f(root); - } else if (err == DeserializationError::NoMemory) { - if (request_size * 2 >= free_heap) { - ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source string smaller"); - return false; - } - ESP_LOGV(TAG, "Increasing memory allocation."); - request_size *= 2; - continue; - } else { - ESP_LOGE(TAG, "Parse error: %s", err.c_str()); - return false; - } - }; + if (err == DeserializationError::Ok) { + return f(root); + } else if (err == DeserializationError::NoMemory) { + ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source string smaller"); + return false; + } + ESP_LOGE(TAG, "Parse error: %s", err.c_str()); return false; + // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) } } // namespace json diff --git a/esphome/components/light/light_json_schema.cpp b/esphome/components/light/light_json_schema.cpp index 6f8cc11f25..26615bae5c 100644 --- a/esphome/components/light/light_json_schema.cpp +++ b/esphome/components/light/light_json_schema.cpp @@ -9,6 +9,7 @@ namespace light { // See https://www.home-assistant.io/integrations/light.mqtt/#json-schema for documentation on the schema void LightJSONSchema::dump_json(LightState &state, JsonObject root) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson if (state.supports_effects()) root["effect"] = state.get_effect_name(); @@ -52,7 +53,7 @@ void LightJSONSchema::dump_json(LightState &state, JsonObject root) { if (values.get_color_mode() & ColorCapability::BRIGHTNESS) root["brightness"] = uint8_t(values.get_brightness() * 255); - JsonObject color = root.createNestedObject("color"); + JsonObject color = root["color"].to(); if (values.get_color_mode() & ColorCapability::RGB) { color["r"] = uint8_t(values.get_color_brightness() * values.get_red() * 255); color["g"] = uint8_t(values.get_color_brightness() * values.get_green() * 255); @@ -73,7 +74,7 @@ void LightJSONSchema::dump_json(LightState &state, JsonObject root) { } void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonObject root) { - if (root.containsKey("state")) { + if (root["state"].is()) { auto val = parse_on_off(root["state"]); switch (val) { case PARSE_ON: @@ -90,40 +91,40 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO } } - if (root.containsKey("brightness")) { + if (root["brightness"].is()) { call.set_brightness(float(root["brightness"]) / 255.0f); } - if (root.containsKey("color")) { + if (root["color"].is()) { JsonObject color = root["color"]; // HA also encodes brightness information in the r, g, b values, so extract that and set it as color brightness. float max_rgb = 0.0f; - if (color.containsKey("r")) { + if (color["r"].is()) { float r = float(color["r"]) / 255.0f; max_rgb = fmaxf(max_rgb, r); call.set_red(r); } - if (color.containsKey("g")) { + if (color["g"].is()) { float g = float(color["g"]) / 255.0f; max_rgb = fmaxf(max_rgb, g); call.set_green(g); } - if (color.containsKey("b")) { + if (color["b"].is()) { float b = float(color["b"]) / 255.0f; max_rgb = fmaxf(max_rgb, b); call.set_blue(b); } - if (color.containsKey("r") || color.containsKey("g") || color.containsKey("b")) { + if (color["r"].is() || color["g"].is() || color["b"].is()) { call.set_color_brightness(max_rgb); } - if (color.containsKey("c")) { + if (color["c"].is()) { call.set_cold_white(float(color["c"]) / 255.0f); } - if (color.containsKey("w")) { + if (color["w"].is()) { // the HA scheme is ambiguous here, the same key is used for white channel in RGBW and warm // white channel in RGBWW. - if (color.containsKey("c")) { + if (color["c"].is()) { call.set_warm_white(float(color["w"]) / 255.0f); } else { call.set_white(float(color["w"]) / 255.0f); @@ -131,11 +132,11 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO } } - if (root.containsKey("white_value")) { // legacy API + if (root["white_value"].is()) { // legacy API call.set_white(float(root["white_value"]) / 255.0f); } - if (root.containsKey("color_temp")) { + if (root["color_temp"].is()) { call.set_color_temperature(float(root["color_temp"])); } } @@ -143,17 +144,17 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO void LightJSONSchema::parse_json(LightState &state, LightCall &call, JsonObject root) { LightJSONSchema::parse_color_json(state, call, root); - if (root.containsKey("flash")) { + if (root["flash"].is()) { auto length = uint32_t(float(root["flash"]) * 1000); call.set_flash_length(length); } - if (root.containsKey("transition")) { + if (root["transition"].is()) { auto length = uint32_t(float(root["transition"]) * 1000); call.set_transition_length(length); } - if (root.containsKey("effect")) { + if (root["effect"].is()) { const char *effect = root["effect"]; call.set_effect(effect); } diff --git a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp index 0a38598679..94460c31a7 100644 --- a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp +++ b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp @@ -55,7 +55,8 @@ void MQTTAlarmControlPanelComponent::dump_config() { } void MQTTAlarmControlPanelComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - JsonArray supported_features = root.createNestedArray(MQTT_SUPPORTED_FEATURES); + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + JsonArray supported_features = root[MQTT_SUPPORTED_FEATURES].to(); const uint32_t acp_supported_features = this->alarm_control_panel_->get_supported_features(); if (acp_supported_features & ACP_FEAT_ARM_AWAY) { supported_features.add("arm_away"); diff --git a/esphome/components/mqtt/mqtt_binary_sensor.cpp b/esphome/components/mqtt/mqtt_binary_sensor.cpp index 6d12e88391..2ce4928574 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.cpp +++ b/esphome/components/mqtt/mqtt_binary_sensor.cpp @@ -30,6 +30,7 @@ MQTTBinarySensorComponent::MQTTBinarySensorComponent(binary_sensor::BinarySensor } void MQTTBinarySensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson if (!this->binary_sensor_->get_device_class().empty()) root[MQTT_DEVICE_CLASS] = this->binary_sensor_->get_device_class(); if (this->binary_sensor_->is_status_binary_sensor()) diff --git a/esphome/components/mqtt/mqtt_button.cpp b/esphome/components/mqtt/mqtt_button.cpp index 204f60fe67..c619a02344 100644 --- a/esphome/components/mqtt/mqtt_button.cpp +++ b/esphome/components/mqtt/mqtt_button.cpp @@ -31,9 +31,12 @@ void MQTTButtonComponent::dump_config() { } void MQTTButtonComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson config.state_topic = false; - if (!this->button_->get_device_class().empty()) + if (!this->button_->get_device_class().empty()) { root[MQTT_DEVICE_CLASS] = this->button_->get_device_class(); + } + // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) } std::string MQTTButtonComponent::component_type() const { return "button"; } diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index ab7fd15a35..5b93789447 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -92,6 +92,7 @@ void MQTTClientComponent::send_device_info_() { std::string topic = "esphome/discover/"; topic.append(App.get_name()); + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson this->publish_json( topic, [](JsonObject root) { @@ -147,6 +148,7 @@ void MQTTClientComponent::send_device_info_() { #endif }, 2, this->discovery_info_.retain); + // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) } void MQTTClientComponent::dump_config() { diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index a8768114a4..e16f097812 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -14,6 +14,7 @@ static const char *const TAG = "mqtt.climate"; using namespace esphome::climate; void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson auto traits = this->device_->get_traits(); // current_temperature_topic if (traits.get_supports_current_temperature()) { @@ -28,7 +29,7 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo // mode_state_topic root[MQTT_MODE_STATE_TOPIC] = this->get_mode_state_topic(); // modes - JsonArray modes = root.createNestedArray(MQTT_MODES); + JsonArray modes = root[MQTT_MODES].to(); // sort array for nice UI in HA if (traits.supports_mode(CLIMATE_MODE_AUTO)) modes.add("auto"); @@ -89,7 +90,7 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo // preset_mode_state_topic root[MQTT_PRESET_MODE_STATE_TOPIC] = this->get_preset_state_topic(); // presets - JsonArray presets = root.createNestedArray("preset_modes"); + JsonArray presets = root["preset_modes"].to(); if (traits.supports_preset(CLIMATE_PRESET_HOME)) presets.add("home"); if (traits.supports_preset(CLIMATE_PRESET_AWAY)) @@ -119,7 +120,7 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo // fan_mode_state_topic root[MQTT_FAN_MODE_STATE_TOPIC] = this->get_fan_mode_state_topic(); // fan_modes - JsonArray fan_modes = root.createNestedArray("fan_modes"); + JsonArray fan_modes = root["fan_modes"].to(); if (traits.supports_fan_mode(CLIMATE_FAN_ON)) fan_modes.add("on"); if (traits.supports_fan_mode(CLIMATE_FAN_OFF)) @@ -150,7 +151,7 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo // swing_mode_state_topic root[MQTT_SWING_MODE_STATE_TOPIC] = this->get_swing_mode_state_topic(); // swing_modes - JsonArray swing_modes = root.createNestedArray("swing_modes"); + JsonArray swing_modes = root["swing_modes"].to(); if (traits.supports_swing_mode(CLIMATE_SWING_OFF)) swing_modes.add("off"); if (traits.supports_swing_mode(CLIMATE_SWING_BOTH)) @@ -163,6 +164,7 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo config.state_topic = false; config.command_topic = false; + // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) } void MQTTClimateComponent::setup() { auto traits = this->device_->get_traits(); diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index eee5644c9d..b51f4d903e 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -70,6 +70,7 @@ bool MQTTComponent::send_discovery_() { ESP_LOGV(TAG, "'%s': Sending discovery", this->friendly_name().c_str()); + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson return global_mqtt_client->publish_json( this->get_discovery_topic_(discovery_info), [this](JsonObject root) { @@ -155,7 +156,7 @@ bool MQTTComponent::send_discovery_() { } std::string node_area = App.get_area(); - JsonObject device_info = root.createNestedObject(MQTT_DEVICE); + JsonObject device_info = root[MQTT_DEVICE].to(); const auto mac = get_mac_address(); device_info[MQTT_DEVICE_IDENTIFIERS] = mac; device_info[MQTT_DEVICE_NAME] = node_friendly_name; @@ -192,6 +193,7 @@ bool MQTTComponent::send_discovery_() { device_info[MQTT_DEVICE_CONNECTIONS][0][1] = mac; }, this->qos_, discovery_info.retain); + // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) } uint8_t MQTTComponent::get_qos() const { return this->qos_; } diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp index 8d09d836f3..6fb61ee469 100644 --- a/esphome/components/mqtt/mqtt_cover.cpp +++ b/esphome/components/mqtt/mqtt_cover.cpp @@ -67,6 +67,7 @@ void MQTTCoverComponent::dump_config() { } } void MQTTCoverComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson if (!this->cover_->get_device_class().empty()) root[MQTT_DEVICE_CLASS] = this->cover_->get_device_class(); diff --git a/esphome/components/mqtt/mqtt_date.cpp b/esphome/components/mqtt/mqtt_date.cpp index 088a4788ed..0f0a334ae7 100644 --- a/esphome/components/mqtt/mqtt_date.cpp +++ b/esphome/components/mqtt/mqtt_date.cpp @@ -20,13 +20,13 @@ MQTTDateComponent::MQTTDateComponent(DateEntity *date) : date_(date) {} void MQTTDateComponent::setup() { this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) { auto call = this->date_->make_call(); - if (root.containsKey("year")) { + if (root["year"].is()) { call.set_year(root["year"]); } - if (root.containsKey("month")) { + if (root["month"].is()) { call.set_month(root["month"]); } - if (root.containsKey("day")) { + if (root["day"].is()) { call.set_day(root["day"]); } call.perform(); @@ -55,6 +55,7 @@ bool MQTTDateComponent::send_initial_state() { } bool MQTTDateComponent::publish_state(uint16_t year, uint8_t month, uint8_t day) { return this->publish_json(this->get_state_topic_(), [year, month, day](JsonObject root) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root["year"] = year; root["month"] = month; root["day"] = day; diff --git a/esphome/components/mqtt/mqtt_datetime.cpp b/esphome/components/mqtt/mqtt_datetime.cpp index 4ae6d0d416..5c56baabe0 100644 --- a/esphome/components/mqtt/mqtt_datetime.cpp +++ b/esphome/components/mqtt/mqtt_datetime.cpp @@ -20,22 +20,22 @@ MQTTDateTimeComponent::MQTTDateTimeComponent(DateTimeEntity *datetime) : datetim void MQTTDateTimeComponent::setup() { this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) { auto call = this->datetime_->make_call(); - if (root.containsKey("year")) { + if (root["year"].is()) { call.set_year(root["year"]); } - if (root.containsKey("month")) { + if (root["month"].is()) { call.set_month(root["month"]); } - if (root.containsKey("day")) { + if (root["day"].is()) { call.set_day(root["day"]); } - if (root.containsKey("hour")) { + if (root["hour"].is()) { call.set_hour(root["hour"]); } - if (root.containsKey("minute")) { + if (root["minute"].is()) { call.set_minute(root["minute"]); } - if (root.containsKey("second")) { + if (root["second"].is()) { call.set_second(root["second"]); } call.perform(); @@ -68,6 +68,7 @@ bool MQTTDateTimeComponent::send_initial_state() { bool MQTTDateTimeComponent::publish_state(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) { return this->publish_json(this->get_state_topic_(), [year, month, day, hour, minute, second](JsonObject root) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root["year"] = year; root["month"] = month; root["day"] = day; diff --git a/esphome/components/mqtt/mqtt_event.cpp b/esphome/components/mqtt/mqtt_event.cpp index cf0b90e3d6..f972d545c6 100644 --- a/esphome/components/mqtt/mqtt_event.cpp +++ b/esphome/components/mqtt/mqtt_event.cpp @@ -16,7 +16,8 @@ using namespace esphome::event; MQTTEventComponent::MQTTEventComponent(event::Event *event) : event_(event) {} void MQTTEventComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - JsonArray event_types = root.createNestedArray(MQTT_EVENT_TYPES); + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + JsonArray event_types = root[MQTT_EVENT_TYPES].to(); for (const auto &event_type : this->event_->get_event_types()) event_types.add(event_type); @@ -40,8 +41,10 @@ void MQTTEventComponent::dump_config() { } bool MQTTEventComponent::publish_event_(const std::string &event_type) { - return this->publish_json(this->get_state_topic_(), - [event_type](JsonObject root) { root[MQTT_EVENT_TYPE] = event_type; }); + return this->publish_json(this->get_state_topic_(), [event_type](JsonObject root) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + root[MQTT_EVENT_TYPE] = event_type; + }); } std::string MQTTEventComponent::component_type() const { return "event"; } diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index 35713bdab6..70e1ae3b4a 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -143,6 +143,7 @@ void MQTTFanComponent::dump_config() { bool MQTTFanComponent::send_initial_state() { return this->publish_state(); } void MQTTFanComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson if (this->state_->get_traits().supports_direction()) { root[MQTT_DIRECTION_COMMAND_TOPIC] = this->get_direction_command_topic(); root[MQTT_DIRECTION_STATE_TOPIC] = this->get_direction_state_topic(); diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index f970da7d8c..4f5ff408a4 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -32,17 +32,21 @@ void MQTTJSONLightComponent::setup() { MQTTJSONLightComponent::MQTTJSONLightComponent(LightState *state) : state_(state) {} bool MQTTJSONLightComponent::publish_state_() { - return this->publish_json(this->get_state_topic_(), - [this](JsonObject root) { LightJSONSchema::dump_json(*this->state_, root); }); + return this->publish_json(this->get_state_topic_(), [this](JsonObject root) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + LightJSONSchema::dump_json(*this->state_, root); + }); } LightState *MQTTJSONLightComponent::get_state() const { return this->state_; } void MQTTJSONLightComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root["schema"] = "json"; auto traits = this->state_->get_traits(); root[MQTT_COLOR_MODE] = true; - JsonArray color_modes = root.createNestedArray("supported_color_modes"); + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + JsonArray color_modes = root["supported_color_modes"].to(); if (traits.supports_color_mode(ColorMode::ON_OFF)) color_modes.add("onoff"); if (traits.supports_color_mode(ColorMode::BRIGHTNESS)) @@ -67,7 +71,7 @@ void MQTTJSONLightComponent::send_discovery(JsonObject root, mqtt::SendDiscovery if (this->state_->supports_effects()) { root["effect"] = true; - JsonArray effect_list = root.createNestedArray(MQTT_EFFECT_LIST); + JsonArray effect_list = root[MQTT_EFFECT_LIST].to(); for (auto *effect : this->state_->get_effects()) effect_list.add(effect->get_name()); effect_list.add("None"); diff --git a/esphome/components/mqtt/mqtt_lock.cpp b/esphome/components/mqtt/mqtt_lock.cpp index f4a5126d0c..0412624983 100644 --- a/esphome/components/mqtt/mqtt_lock.cpp +++ b/esphome/components/mqtt/mqtt_lock.cpp @@ -38,8 +38,10 @@ void MQTTLockComponent::dump_config() { std::string MQTTLockComponent::component_type() const { return "lock"; } const EntityBase *MQTTLockComponent::get_entity() const { return this->lock_; } void MQTTLockComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - if (this->lock_->traits.get_assumed_state()) + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + if (this->lock_->traits.get_assumed_state()) { root[MQTT_OPTIMISTIC] = true; + } if (this->lock_->traits.get_supports_open()) root[MQTT_PAYLOAD_OPEN] = "OPEN"; } diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index 3a6ea97967..a44632ff30 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -40,6 +40,7 @@ const EntityBase *MQTTNumberComponent::get_entity() const { return this->number_ void MQTTNumberComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { const auto &traits = number_->traits; // https://www.home-assistant.io/integrations/number.mqtt/ + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root[MQTT_MIN] = traits.get_min_value(); root[MQTT_MAX] = traits.get_max_value(); root[MQTT_STEP] = traits.get_step(); diff --git a/esphome/components/mqtt/mqtt_select.cpp b/esphome/components/mqtt/mqtt_select.cpp index ea5130f823..b851348306 100644 --- a/esphome/components/mqtt/mqtt_select.cpp +++ b/esphome/components/mqtt/mqtt_select.cpp @@ -35,7 +35,8 @@ const EntityBase *MQTTSelectComponent::get_entity() const { return this->select_ void MQTTSelectComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { const auto &traits = select_->traits; // https://www.home-assistant.io/integrations/select.mqtt/ - JsonArray options = root.createNestedArray(MQTT_OPTIONS); + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + JsonArray options = root[MQTT_OPTIONS].to(); for (const auto &option : traits.get_options()) options.add(option); diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index 2cbc291ccf..9324ea9bb1 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -44,8 +44,10 @@ void MQTTSensorComponent::set_expire_after(uint32_t expire_after) { this->expire void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; } void MQTTSensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - if (!this->sensor_->get_device_class().empty()) + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + if (!this->sensor_->get_device_class().empty()) { root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class(); + } if (!this->sensor_->get_unit_of_measurement().empty()) root[MQTT_UNIT_OF_MEASUREMENT] = this->sensor_->get_unit_of_measurement(); diff --git a/esphome/components/mqtt/mqtt_switch.cpp b/esphome/components/mqtt/mqtt_switch.cpp index 3fd578825a..8b1323bdb2 100644 --- a/esphome/components/mqtt/mqtt_switch.cpp +++ b/esphome/components/mqtt/mqtt_switch.cpp @@ -45,8 +45,10 @@ void MQTTSwitchComponent::dump_config() { std::string MQTTSwitchComponent::component_type() const { return "switch"; } const EntityBase *MQTTSwitchComponent::get_entity() const { return this->switch_; } void MQTTSwitchComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - if (this->switch_->assumed_state()) + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + if (this->switch_->assumed_state()) { root[MQTT_OPTIMISTIC] = true; + } } bool MQTTSwitchComponent::send_initial_state() { return this->publish_state(this->switch_->state); } diff --git a/esphome/components/mqtt/mqtt_text.cpp b/esphome/components/mqtt/mqtt_text.cpp index cb852b64cd..5ab0ca9688 100644 --- a/esphome/components/mqtt/mqtt_text.cpp +++ b/esphome/components/mqtt/mqtt_text.cpp @@ -34,6 +34,7 @@ std::string MQTTTextComponent::component_type() const { return "text"; } const EntityBase *MQTTTextComponent::get_entity() const { return this->text_; } void MQTTTextComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson switch (this->text_->traits.get_mode()) { case TEXT_MODE_TEXT: root[MQTT_MODE] = "text"; diff --git a/esphome/components/mqtt/mqtt_text_sensor.cpp b/esphome/components/mqtt/mqtt_text_sensor.cpp index b0754bc8b3..0cc5de07a3 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.cpp +++ b/esphome/components/mqtt/mqtt_text_sensor.cpp @@ -15,8 +15,10 @@ using namespace esphome::text_sensor; MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : sensor_(sensor) {} void MQTTTextSensor::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - if (!this->sensor_->get_device_class().empty()) + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + if (!this->sensor_->get_device_class().empty()) { root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class(); + } config.command_topic = false; } void MQTTTextSensor::setup() { diff --git a/esphome/components/mqtt/mqtt_time.cpp b/esphome/components/mqtt/mqtt_time.cpp index 332ef53cbc..0c95bd8147 100644 --- a/esphome/components/mqtt/mqtt_time.cpp +++ b/esphome/components/mqtt/mqtt_time.cpp @@ -20,13 +20,13 @@ MQTTTimeComponent::MQTTTimeComponent(TimeEntity *time) : time_(time) {} void MQTTTimeComponent::setup() { this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) { auto call = this->time_->make_call(); - if (root.containsKey("hour")) { + if (root["hour"].is()) { call.set_hour(root["hour"]); } - if (root.containsKey("minute")) { + if (root["minute"].is()) { call.set_minute(root["minute"]); } - if (root.containsKey("second")) { + if (root["second"].is()) { call.set_second(root["second"]); } call.perform(); @@ -55,6 +55,7 @@ bool MQTTTimeComponent::send_initial_state() { } bool MQTTTimeComponent::publish_state(uint8_t hour, uint8_t minute, uint8_t second) { return this->publish_json(this->get_state_topic_(), [hour, minute, second](JsonObject root) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root["hour"] = hour; root["minute"] = minute; root["second"] = second; diff --git a/esphome/components/mqtt/mqtt_update.cpp b/esphome/components/mqtt/mqtt_update.cpp index 2ed8faf074..5d4807c7f3 100644 --- a/esphome/components/mqtt/mqtt_update.cpp +++ b/esphome/components/mqtt/mqtt_update.cpp @@ -41,6 +41,7 @@ bool MQTTUpdateComponent::publish_state() { } void MQTTUpdateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson root["schema"] = "json"; root[MQTT_PAYLOAD_INSTALL] = "INSTALL"; } diff --git a/esphome/components/mqtt/mqtt_valve.cpp b/esphome/components/mqtt/mqtt_valve.cpp index 85e06fe79c..551398cf42 100644 --- a/esphome/components/mqtt/mqtt_valve.cpp +++ b/esphome/components/mqtt/mqtt_valve.cpp @@ -49,8 +49,10 @@ void MQTTValveComponent::dump_config() { } } void MQTTValveComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - if (!this->valve_->get_device_class().empty()) + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + if (!this->valve_->get_device_class().empty()) { root[MQTT_DEVICE_CLASS] = this->valve_->get_device_class(); + } auto traits = this->valve_->get_traits(); if (traits.get_is_assumed_state()) { diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 2aa6acde0e..9ec667dbc5 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -792,7 +792,7 @@ std::string WebServer::light_json(light::LightState *obj, JsonDetail start_confi light::LightJSONSchema::dump_json(*obj, root); if (start_config == DETAIL_ALL) { - JsonArray opt = root.createNestedArray("effects"); + JsonArray opt = root["effects"].to(); opt.add("None"); for (auto const &option : obj->get_effects()) { opt.add(option->get_name()); @@ -1238,7 +1238,7 @@ std::string WebServer::select_json(select::Select *obj, const std::string &value return json::build_json([this, obj, value, start_config](JsonObject root) { set_json_icon_state_value(root, obj, "select-" + obj->get_object_id(), value, value, start_config); if (start_config == DETAIL_ALL) { - JsonArray opt = root.createNestedArray("option"); + JsonArray opt = root["option"].to(); for (auto &option : obj->traits.get_options()) { opt.add(option); } @@ -1322,6 +1322,7 @@ std::string WebServer::climate_all_json_generator(WebServer *web_server, void *s return web_server->climate_json((climate::Climate *) (source), DETAIL_ALL); } std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_config) { + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson return json::build_json([this, obj, start_config](JsonObject root) { set_json_id(root, obj, "climate-" + obj->get_object_id(), start_config); const auto traits = obj->get_traits(); @@ -1330,32 +1331,32 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf char buf[16]; if (start_config == DETAIL_ALL) { - JsonArray opt = root.createNestedArray("modes"); + JsonArray opt = root["modes"].to(); for (climate::ClimateMode m : traits.get_supported_modes()) opt.add(PSTR_LOCAL(climate::climate_mode_to_string(m))); if (!traits.get_supported_custom_fan_modes().empty()) { - JsonArray opt = root.createNestedArray("fan_modes"); + JsonArray opt = root["fan_modes"].to(); for (climate::ClimateFanMode m : traits.get_supported_fan_modes()) opt.add(PSTR_LOCAL(climate::climate_fan_mode_to_string(m))); } if (!traits.get_supported_custom_fan_modes().empty()) { - JsonArray opt = root.createNestedArray("custom_fan_modes"); + JsonArray opt = root["custom_fan_modes"].to(); for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes()) opt.add(custom_fan_mode); } if (traits.get_supports_swing_modes()) { - JsonArray opt = root.createNestedArray("swing_modes"); + JsonArray opt = root["swing_modes"].to(); for (auto swing_mode : traits.get_supported_swing_modes()) opt.add(PSTR_LOCAL(climate::climate_swing_mode_to_string(swing_mode))); } if (traits.get_supports_presets() && obj->preset.has_value()) { - JsonArray opt = root.createNestedArray("presets"); + JsonArray opt = root["presets"].to(); for (climate::ClimatePreset m : traits.get_supported_presets()) opt.add(PSTR_LOCAL(climate::climate_preset_to_string(m))); } if (!traits.get_supported_custom_presets().empty() && obj->custom_preset.has_value()) { - JsonArray opt = root.createNestedArray("custom_presets"); + JsonArray opt = root["custom_presets"].to(); for (auto const &custom_preset : traits.get_supported_custom_presets()) opt.add(custom_preset); } @@ -1407,6 +1408,7 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf root["state"] = root["target_temperature"]; } }); + // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) } #endif @@ -1635,7 +1637,7 @@ std::string WebServer::event_json(event::Event *obj, const std::string &event_ty root["event_type"] = event_type; } if (start_config == DETAIL_ALL) { - JsonArray event_types = root.createNestedArray("event_types"); + JsonArray event_types = root["event_types"].to(); for (auto const &event_type : obj->get_event_types()) { event_types.add(event_type); } @@ -1682,6 +1684,7 @@ std::string WebServer::update_all_json_generator(WebServer *web_server, void *so return web_server->update_json((update::UpdateEntity *) (source), DETAIL_STATE); } std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_config) { + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson return json::build_json([this, obj, start_config](JsonObject root) { set_json_id(root, obj, "update-" + obj->get_object_id(), start_config); root["value"] = obj->update_info.latest_version; @@ -1707,6 +1710,7 @@ std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_c this->add_sorting_info_(root, obj); } }); + // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) } #endif @@ -1887,7 +1891,7 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { void (WebServer::*handler)(AsyncWebServerRequest *, const UrlMatch &); }; - static const ComponentRoute routes[] = { + static const ComponentRoute ROUTES[] = { #ifdef USE_SENSOR {"sensor", &WebServer::handle_sensor_request}, #endif @@ -1948,7 +1952,7 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { }; // Check each route - for (const auto &route : routes) { + for (const auto &route : ROUTES) { if (match.domain_equals(route.domain)) { (this->*route.handler)(request, match); return; diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index b43fadefbe..9f3371c233 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -40,7 +40,4 @@ async def to_code(config): if CORE.is_esp8266: cg.add_library("ESP8266WiFi", None) # https://github.com/ESP32Async/ESPAsyncWebServer/blob/main/library.json - # Use fork with libretiny compatibility fix - cg.add_library( - "https://github.com/bdraco/ESPAsyncWebServer.git#libretiny_Fix", None - ) + cg.add_library("ESP32Async/ESPAsyncWebServer", "3.7.10") diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 8ed8f4b5aa..7ddb3436cd 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -145,6 +145,7 @@ #define USE_CAPTIVE_PORTAL #define USE_ESP32_BLE #define USE_ESP32_BLE_CLIENT +#define USE_ESP32_BLE_DEVICE #define USE_ESP32_BLE_SERVER #define USE_I2C #define USE_IMPROV diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py index 7f474a1fc4..48f600454c 100644 --- a/esphome/platformio_api.py +++ b/esphome/platformio_api.py @@ -79,6 +79,8 @@ def run_platformio_cli(*args, **kwargs) -> str | int: os.environ.setdefault( "PLATFORMIO_LIBDEPS_DIR", os.path.abspath(CORE.relative_piolibdeps_path()) ) + # Suppress Python syntax warnings from third-party scripts during compilation + os.environ.setdefault("PYTHONWARNINGS", "ignore::SyntaxWarning") cmd = ["platformio"] + list(args) if not CORE.verbose: diff --git a/esphome/writer.py b/esphome/writer.py index 943dfa78cc..ca9e511c19 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -162,6 +162,9 @@ def get_ini_content(): # Sort to avoid changing build unflags order CORE.add_platformio_option("build_unflags", sorted(CORE.build_unflags)) + # Add extra script for C++ flags + CORE.add_platformio_option("extra_scripts", ["pre:cxx_flags.py"]) + content = "[platformio]\n" content += f"description = ESPHome {__version__}\n" @@ -222,6 +225,9 @@ def write_platformio_project(): write_gitignore() write_platformio_ini(content) + # Write extra script for C++ specific flags + write_cxx_flags_script() + DEFINES_H_FORMAT = ESPHOME_H_FORMAT = """\ #pragma once @@ -394,3 +400,16 @@ def write_gitignore(): if not os.path.isfile(path): with open(file=path, mode="w", encoding="utf-8") as f: f.write(GITIGNORE_CONTENT) + + +CXX_FLAGS_SCRIPT = """# Auto-generated ESPHome script for C++ specific compiler flags +Import("env") + +# Add C++ specific warning flags +env.Append(CXXFLAGS=["-Wno-volatile"]) +""" + + +def write_cxx_flags_script() -> None: + path = CORE.relative_build_path("cxx_flags.py") + write_file_if_changed(path, CXX_FLAGS_SCRIPT) diff --git a/platformio.ini b/platformio.ini index 54c72eb28d..8fcc578103 100644 --- a/platformio.ini +++ b/platformio.ini @@ -35,7 +35,7 @@ build_flags = lib_deps = esphome/noise-c@0.1.10 ; api improv/Improv@1.2.4 ; improv_serial / esp32_improv - bblanchon/ArduinoJson@6.18.5 ; json + bblanchon/ArduinoJson@7.4.2 ; json wjtje/qr-code-generator-library@1.7.0 ; qr_code functionpointer/arduino-MLX90393@1.0.2 ; mlx90393 pavlodn/HaierProtocol@0.9.31 ; haier @@ -235,7 +235,7 @@ build_flags = -DUSE_ZEPHYR -DUSE_NRF52 lib_deps = - bblanchon/ArduinoJson@7.0.0 ; json + bblanchon/ArduinoJson@7.4.2 ; json wjtje/qr-code-generator-library@1.7.0 ; qr_code pavlodn/HaierProtocol@0.9.31 ; haier functionpointer/arduino-MLX90393@1.0.2 ; mlx90393 diff --git a/script/clang_tidy_hash.py b/script/clang_tidy_hash.py index 86f4c4e158..8cc135f5d0 100755 --- a/script/clang_tidy_hash.py +++ b/script/clang_tidy_hash.py @@ -126,7 +126,8 @@ def write_file_content(path: Path, content: str) -> None: def write_hash(hash_value: str) -> None: """Write hash to file""" hash_file = Path(__file__).parent.parent / ".clang-tidy.hash" - write_file_content(hash_file, hash_value) + # Strip any trailing newlines to ensure consistent formatting + write_file_content(hash_file, hash_value.strip() + "\n") def main() -> None: diff --git a/script/test_build_components b/script/test_build_components index 83ab947fc1..3796280176 100755 --- a/script/test_build_components +++ b/script/test_build_components @@ -72,7 +72,7 @@ for f in ./tests/components/$target_component/*.*.yaml; do if [ "$target_platform" = "all" ] || [ $file_name_parts = 2 ]; then # Test has *not* defined a specific target platform. Need to run tests for all possible target platforms. - + for target_platform_file in ./tests/test_build_components/build_components_base.*.yaml; do IFS='/' read -r -a folder_name <<< "$target_platform_file" IFS='.' read -r -a file_name <<< "${folder_name[3]}" @@ -83,7 +83,7 @@ for f in ./tests/components/$target_component/*.*.yaml; do else # Test has defined a specific target platform. - + # Validate we have a base test yaml for selected platform. # The target_platform is sourced from the following location. # 1. `./tests/test_build_components/build_components_base.[target_platform].yaml` diff --git a/tests/components/animation/.gitattributes b/tests/components/animation/.gitattributes index ff9fc6f1f1..766b86f19e 100644 --- a/tests/components/animation/.gitattributes +++ b/tests/components/animation/.gitattributes @@ -1,4 +1,3 @@ *.apng -text *.webp -text *.gif -text - diff --git a/tests/components/animation/test.esp32-ard.yaml b/tests/components/animation/test.esp32-ard.yaml index 5d330900df..7d9fe45bff 100644 --- a/tests/components/animation/test.esp32-ard.yaml +++ b/tests/components/animation/test.esp32-ard.yaml @@ -15,4 +15,3 @@ display: packages: animation: !include common.yaml - diff --git a/tests/components/ble_presence/common.yaml b/tests/components/ble_presence/common.yaml index 6e5173eed8..2ba6aa0754 100644 --- a/tests/components/ble_presence/common.yaml +++ b/tests/components/ble_presence/common.yaml @@ -21,4 +21,3 @@ binary_sensor: - platform: ble_presence irk: 1234567890abcdef1234567890abcdef name: "ESP32 BLE Tracker with Identity Resolving Key" - diff --git a/tests/components/color/common.yaml b/tests/components/color/common.yaml index 88524e6a5f..69a77326f7 100644 --- a/tests/components/color/common.yaml +++ b/tests/components/color/common.yaml @@ -17,4 +17,3 @@ color: hex: 008000 - id: cps_blue hex: 000080 - diff --git a/tests/components/const/common.yaml b/tests/components/const/common.yaml index 655af304af..f4b15f2b90 100644 --- a/tests/components/const/common.yaml +++ b/tests/components/const/common.yaml @@ -41,4 +41,3 @@ display: - delay 120ms - [0x29] - delay 20ms - diff --git a/tests/components/cst816/common.yaml b/tests/components/cst816/common.yaml index 9750de15db..9400e4eef0 100644 --- a/tests/components/cst816/common.yaml +++ b/tests/components/cst816/common.yaml @@ -42,4 +42,3 @@ binary_sensor: x_max: 480 y_min: 320 y_max: 360 - diff --git a/tests/components/debug/common.yaml b/tests/components/debug/common.yaml index a9d74e6865..d9a61f8df0 100644 --- a/tests/components/debug/common.yaml +++ b/tests/components/debug/common.yaml @@ -15,4 +15,3 @@ sensor: name: "Loop Time" cpu_frequency: name: "CPU Frequency" - diff --git a/tests/components/ens160_spi/common.yaml b/tests/components/ens160_spi/common.yaml index 7250ead228..c8b663272f 100644 --- a/tests/components/ens160_spi/common.yaml +++ b/tests/components/ens160_spi/common.yaml @@ -14,4 +14,3 @@ sensor: name: "ENS160 Total Volatile Organic Compounds" aqi: name: "ENS160 Air Quality Index" - diff --git a/tests/components/esp32/test.esp32-idf.yaml b/tests/components/esp32/test.esp32-idf.yaml index 582b2c22ce..ccf0b7cbd5 100644 --- a/tests/components/esp32/test.esp32-idf.yaml +++ b/tests/components/esp32/test.esp32-idf.yaml @@ -9,4 +9,3 @@ esp32: wifi: ssid: MySSID password: password1 - diff --git a/tests/components/esphome/test.esp32-ard.yaml b/tests/components/esphome/test.esp32-ard.yaml index a991f7e15a..dade44d145 100644 --- a/tests/components/esphome/test.esp32-ard.yaml +++ b/tests/components/esphome/test.esp32-ard.yaml @@ -1,2 +1 @@ <<: !include common.yaml - diff --git a/tests/components/esphome/test.esp32-c3-ard.yaml b/tests/components/esphome/test.esp32-c3-ard.yaml index a991f7e15a..dade44d145 100644 --- a/tests/components/esphome/test.esp32-c3-ard.yaml +++ b/tests/components/esphome/test.esp32-c3-ard.yaml @@ -1,2 +1 @@ <<: !include common.yaml - diff --git a/tests/components/font/.gitattributes b/tests/components/font/.gitattributes index 18d9a389e8..63ab00e9f2 100644 --- a/tests/components/font/.gitattributes +++ b/tests/components/font/.gitattributes @@ -1,2 +1 @@ *.pcf -text - diff --git a/tests/components/lvgl/.gitattributes b/tests/components/lvgl/.gitattributes index 75e7a44254..9d74867fcf 100644 --- a/tests/components/lvgl/.gitattributes +++ b/tests/components/lvgl/.gitattributes @@ -1,2 +1 @@ *.ttf -text - diff --git a/tests/components/lvgl/test.esp32-ard.yaml b/tests/components/lvgl/test.esp32-ard.yaml index 5b09147de7..f85bedbde6 100644 --- a/tests/components/lvgl/test.esp32-ard.yaml +++ b/tests/components/lvgl/test.esp32-ard.yaml @@ -56,4 +56,3 @@ lvgl: packages: lvgl: !include lvgl-package.yaml xvgl: !include common.yaml - diff --git a/tests/components/max17043/test.esp32-ard.yaml b/tests/components/max17043/test.esp32-ard.yaml index c84e0a5c2e..c6615f51cd 100644 --- a/tests/components/max17043/test.esp32-ard.yaml +++ b/tests/components/max17043/test.esp32-ard.yaml @@ -3,4 +3,3 @@ substitutions: scl_pin: GPIO22 <<: !include common.yaml - diff --git a/tests/components/mipi_spi/common.yaml b/tests/components/mipi_spi/common.yaml index e4b1e2b30c..2c84489ec7 100644 --- a/tests/components/mipi_spi/common.yaml +++ b/tests/components/mipi_spi/common.yaml @@ -35,4 +35,3 @@ display: allow_other_uses: true - number: ${enable_pin} bus_mode: single - diff --git a/tests/components/nextion/test.rp2040-ard.yaml b/tests/components/nextion/test.rp2040-ard.yaml index 20347c6eff..44534b97a8 100644 --- a/tests/components/nextion/test.rp2040-ard.yaml +++ b/tests/components/nextion/test.rp2040-ard.yaml @@ -4,4 +4,3 @@ substitutions: packages: base: !include common.yaml - diff --git a/tests/components/online_image/test.esp32-idf.yaml b/tests/components/online_image/test.esp32-idf.yaml index 3f01009812..d4af284151 100644 --- a/tests/components/online_image/test.esp32-idf.yaml +++ b/tests/components/online_image/test.esp32-idf.yaml @@ -1,4 +1,3 @@ <<: !include common-esp32.yaml http_request: - diff --git a/tests/components/openthread/test.esp32-c6-idf.yaml b/tests/components/openthread/test.esp32-c6-idf.yaml index bbcf48efa5..da5339fb39 100644 --- a/tests/components/openthread/test.esp32-c6-idf.yaml +++ b/tests/components/openthread/test.esp32-c6-idf.yaml @@ -11,4 +11,3 @@ openthread: pskc: 0xc23a76e98f1a6483639b1ac1271e2e27 mesh_local_prefix: fd53:145f:ed22:ad81::/64 force_dataset: true - diff --git a/tests/components/packages/test.esp32-ard.yaml b/tests/components/packages/test.esp32-ard.yaml index 7d0ab2b905..9e4ceb09d6 100644 --- a/tests/components/packages/test.esp32-ard.yaml +++ b/tests/components/packages/test.esp32-ard.yaml @@ -9,4 +9,3 @@ packages: file: common.yaml ref: dev refresh: 1d - diff --git a/tests/components/packages/test.esp32-idf.yaml b/tests/components/packages/test.esp32-idf.yaml index 8c0a34bb1a..5bf40822dc 100644 --- a/tests/components/packages/test.esp32-idf.yaml +++ b/tests/components/packages/test.esp32-idf.yaml @@ -11,4 +11,3 @@ packages: file: common.yaml ref: dev refresh: 1d - diff --git a/tests/components/qspi_dbi/common.yaml b/tests/components/qspi_dbi/common.yaml index 655af304af..f4b15f2b90 100644 --- a/tests/components/qspi_dbi/common.yaml +++ b/tests/components/qspi_dbi/common.yaml @@ -41,4 +41,3 @@ display: - delay 120ms - [0x29] - delay 20ms - diff --git a/tests/components/spi/test.esp32-p4-idf.yaml b/tests/components/spi/test.esp32-p4-idf.yaml index 061e3dd44a..93626dc1d5 100644 --- a/tests/components/spi/test.esp32-p4-idf.yaml +++ b/tests/components/spi/test.esp32-p4-idf.yaml @@ -35,4 +35,3 @@ spi: interface: any clk_pin: 8 mosi_pin: 9 - diff --git a/tests/components/spi/test.esp32-s3-idf.yaml b/tests/components/spi/test.esp32-s3-idf.yaml index 061e3dd44a..93626dc1d5 100644 --- a/tests/components/spi/test.esp32-s3-idf.yaml +++ b/tests/components/spi/test.esp32-s3-idf.yaml @@ -35,4 +35,3 @@ spi: interface: any clk_pin: 8 mosi_pin: 9 - diff --git a/tests/components/udp/common.yaml b/tests/components/udp/common.yaml index 79da02a692..96224d0d1f 100644 --- a/tests/components/udp/common.yaml +++ b/tests/components/udp/common.yaml @@ -17,4 +17,3 @@ udp: id: my_udp data: !lambda |- return std::vector{1,3,4,5,6}; - diff --git a/tests/components/web_server/test_ota.esp32-idf.yaml b/tests/components/web_server/test_ota.esp32-idf.yaml index 37838b3d34..99873aa27b 100644 --- a/tests/components/web_server/test_ota.esp32-idf.yaml +++ b/tests/components/web_server/test_ota.esp32-idf.yaml @@ -27,4 +27,3 @@ logger: logs: web_server: VERBOSE web_server_idf: VERBOSE - diff --git a/tests/components/wk2132_i2c/test.esp32-ard.yaml b/tests/components/wk2132_i2c/test.esp32-ard.yaml index 94552c5a40..3b761d3fc1 100644 --- a/tests/components/wk2132_i2c/test.esp32-ard.yaml +++ b/tests/components/wk2132_i2c/test.esp32-ard.yaml @@ -3,4 +3,3 @@ substitutions: sda_pin: GPIO21 <<: !include common.yaml - diff --git a/tests/integration/fixtures/areas_and_devices.yaml b/tests/integration/fixtures/areas_and_devices.yaml index 4a327b73a1..6bf1519c79 100644 --- a/tests/integration/fixtures/areas_and_devices.yaml +++ b/tests/integration/fixtures/areas_and_devices.yaml @@ -54,4 +54,3 @@ sensor: device_id: smart_switch_device lambda: return 4.0; update_interval: 0.1s - diff --git a/tests/integration/fixtures/device_id_in_state.yaml b/tests/integration/fixtures/device_id_in_state.yaml index f2e320a2e2..c8548617b8 100644 --- a/tests/integration/fixtures/device_id_in_state.yaml +++ b/tests/integration/fixtures/device_id_in_state.yaml @@ -82,4 +82,3 @@ output: write_action: - lambda: |- ESP_LOGD("test", "Light output: %d", state); - diff --git a/tests/integration/fixtures/large_message_batching.yaml b/tests/integration/fixtures/large_message_batching.yaml index 1b2d817cd4..e491da0486 100644 --- a/tests/integration/fixtures/large_message_batching.yaml +++ b/tests/integration/fixtures/large_message_batching.yaml @@ -134,4 +134,3 @@ switch: name: "Test Switch" id: test_switch optimistic: true - diff --git a/tests/script/test_clang_tidy_hash.py b/tests/script/test_clang_tidy_hash.py index dbcb477a4f..e4d4b40473 100644 --- a/tests/script/test_clang_tidy_hash.py +++ b/tests/script/test_clang_tidy_hash.py @@ -158,7 +158,7 @@ def test_write_hash() -> None: mock_write.assert_called_once() args = mock_write.call_args[0] assert str(args[0]).endswith(".clang-tidy.hash") - assert args[1] == hash_value + assert args[1] == hash_value.strip() + "\n" @pytest.mark.parametrize( diff --git a/tests/unit_tests/fixtures/substitutions/.gitignore b/tests/unit_tests/fixtures/substitutions/.gitignore index 0b15cdb2b7..897c9da86c 100644 --- a/tests/unit_tests/fixtures/substitutions/.gitignore +++ b/tests/unit_tests/fixtures/substitutions/.gitignore @@ -1 +1 @@ -*.received.yaml \ No newline at end of file +*.received.yaml