diff --git a/esphome/components/exposure_notifications/__init__.py b/esphome/components/exposure_notifications/__init__.py index ab7416a264..6cb5b750dd 100644 --- a/esphome/components/exposure_notifications/__init__.py +++ b/esphome/components/exposure_notifications/__init__.py @@ -1,33 +1,59 @@ +from typing import Any + from esphome import automation import esphome.codegen as cg -from esphome.components import esp32_ble_tracker +from esphome.components import ble_device_base import esphome.config_validation as cv from esphome.const import CONF_TRIGGER_ID +from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor +from esphome.types import ConfigType CODEOWNERS = ["@OttoWinter"] -DEPENDENCIES = ["esp32_ble_tracker"] +AUTO_LOAD = ["ble_device_base"] exposure_notifications_ns = cg.esphome_ns.namespace("exposure_notifications") ExposureNotification = exposure_notifications_ns.struct("ExposureNotification") ExposureNotificationTrigger = exposure_notifications_ns.class_( "ExposureNotificationTrigger", - esp32_ble_tracker.ESPBTDeviceListener, + ble_device_base.ESPBTDeviceListener, automation.Trigger.template(ExposureNotification), ) CONF_ON_EXPOSURE_NOTIFICATION = "on_exposure_notification" +_RENAME_HUB_ID = ble_device_base.rename_legacy_hub_id("exposure_notifications") + +_VALIDATE_AUTOMATION = automation.validate_automation( + cv.Schema( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ExposureNotificationTrigger), + } + # The trigger is the BLE listener, so the hub id lives on it. + ).extend(ble_device_base.BLE_DEVICE_SCHEMA) +) + + +# validate_automation() needs a dict-based schema, so the rename cannot go +# inside it and has to run on the option value first. That value may also be a +# list of automations or malformed, and rename_legacy_hub_id() is dict-only, so +# map over lists and let validate_automation() report anything else. +# schema_extractor keeps the key typed as a trigger in the generated editor +# schema; build_language_schema.py recurses into cv.All but not into a plain +# function. +@schema_extractor("automation") +def _validate_on_exposure_notification(value: Any) -> list[ConfigType]: + if value is SCHEMA_EXTRACT: + return _VALIDATE_AUTOMATION(value) + if isinstance(value, dict): + value = _RENAME_HUB_ID(value) + elif isinstance(value, list): + value = [_RENAME_HUB_ID(v) if isinstance(v, dict) else v for v in value] + return _VALIDATE_AUTOMATION(value) + + CONFIG_SCHEMA = cv.Schema( { - cv.Required(CONF_ON_EXPOSURE_NOTIFICATION): automation.validate_automation( - cv.Schema( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - ExposureNotificationTrigger - ), - } - ).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) - ), + cv.Required(CONF_ON_EXPOSURE_NOTIFICATION): _validate_on_exposure_notification, } ) @@ -36,4 +62,4 @@ async def to_code(config): for conf in config.get(CONF_ON_EXPOSURE_NOTIFICATION, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) await automation.build_automation(trigger, [(ExposureNotification, "x")], conf) - await esp32_ble_tracker.register_ble_device(trigger, conf) + await ble_device_base.register_ble_device(trigger, conf) diff --git a/esphome/components/exposure_notifications/exposure_notifications.cpp b/esphome/components/exposure_notifications/exposure_notifications.cpp index e7038d2ca9..4f4b93b59c 100644 --- a/esphome/components/exposure_notifications/exposure_notifications.cpp +++ b/esphome/components/exposure_notifications/exposure_notifications.cpp @@ -2,11 +2,9 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#ifdef USE_ESP32 - namespace esphome::exposure_notifications { -using namespace esp32_ble_tracker; +using namespace ble_device_base; static const char *const TAG = "exposure_notifications"; @@ -43,5 +41,3 @@ bool ExposureNotificationTrigger::parse_device(const ESPBTDevice &device) { } } // namespace esphome::exposure_notifications - -#endif diff --git a/esphome/components/exposure_notifications/exposure_notifications.h b/esphome/components/exposure_notifications/exposure_notifications.h index 6a703a9a92..dc1241db56 100644 --- a/esphome/components/exposure_notifications/exposure_notifications.h +++ b/esphome/components/exposure_notifications/exposure_notifications.h @@ -2,11 +2,9 @@ #include "esphome/core/component.h" #include "esphome/core/automation.h" -#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/components/ble_device_base/ble_device.h" #include -#ifdef USE_ESP32 - namespace esphome::exposure_notifications { struct ExposureNotification { @@ -17,11 +15,9 @@ struct ExposureNotification { }; class ExposureNotificationTrigger final : public Trigger, - public esp32_ble_tracker::ESPBTDeviceListener { + public ble_device_base::ESPBTDeviceListener { public: - bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; + bool parse_device(const ble_device_base::ESPBTDevice &device) override; }; } // namespace esphome::exposure_notifications - -#endif diff --git a/esphome/components/thermopro_ble/sensor.py b/esphome/components/thermopro_ble/sensor.py index de63229621..d0d6cdacb7 100644 --- a/esphome/components/thermopro_ble/sensor.py +++ b/esphome/components/thermopro_ble/sensor.py @@ -1,5 +1,5 @@ import esphome.codegen as cg -from esphome.components import esp32_ble_tracker, sensor +from esphome.components import ble_device_base, sensor import esphome.config_validation as cv from esphome.const import ( CONF_BATTERY_LEVEL, @@ -22,14 +22,15 @@ from esphome.const import ( CODEOWNERS = ["@sittner"] -DEPENDENCIES = ["esp32_ble_tracker"] +AUTO_LOAD = ["ble_device_base"] thermopro_ble_ns = cg.esphome_ns.namespace("thermopro_ble") ThermoProBLE = thermopro_ble_ns.class_( - "ThermoProBLE", esp32_ble_tracker.ESPBTDeviceListener, cg.Component + "ThermoProBLE", ble_device_base.ESPBTDeviceListener, cg.Component ) -CONFIG_SCHEMA = ( +CONFIG_SCHEMA = cv.All( + ble_device_base.rename_legacy_hub_id("thermopro_ble"), cv.Schema( { cv.GenerateID(): cv.declare_id(ThermoProBLE), @@ -68,15 +69,15 @@ CONFIG_SCHEMA = ( ), } ) - .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) .extend(cv.COMPONENT_SCHEMA) + .extend(ble_device_base.BLE_DEVICE_SCHEMA), ) async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) - await esp32_ble_tracker.register_ble_device(var, config) + await ble_device_base.register_ble_device(var, config) cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) diff --git a/esphome/components/thermopro_ble/thermopro_ble.cpp b/esphome/components/thermopro_ble/thermopro_ble.cpp index 72e398f774..d10a6c33cd 100644 --- a/esphome/components/thermopro_ble/thermopro_ble.cpp +++ b/esphome/components/thermopro_ble/thermopro_ble.cpp @@ -2,8 +2,6 @@ #include #include "esphome/core/log.h" -#ifdef USE_ESP32 - namespace esphome::thermopro_ble { // this size must be large enough to hold the largest data frame @@ -34,7 +32,7 @@ void ThermoProBLE::dump_config() { LOG_SENSOR(" ", "Battery Level", this->battery_level_); } -bool ThermoProBLE::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { +bool ThermoProBLE::parse_device(const ble_device_base::ESPBTDevice &device) { // check for matching mac address if (device.address_uint64() != this->address_) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); @@ -66,8 +64,8 @@ bool ThermoProBLE::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { } // reconstruct whole record from 2 byte uuid and data - esp_bt_uuid_t uuid = service_data.uuid.get_uuid(); - uint8_t data[MAX_DATA_SIZE] = {static_cast(uuid.uuid.uuid16), static_cast(uuid.uuid.uuid16 >> 8)}; + uint16_t svc_uuid16 = service_data.uuid.uuid16(); + uint8_t data[MAX_DATA_SIZE] = {static_cast(svc_uuid16), static_cast(svc_uuid16 >> 8)}; std::copy(service_data.data.begin(), service_data.data.end(), std::begin(data) + 2); // dispatch data to parser @@ -202,5 +200,3 @@ static optional parse_tp3(const uint8_t *data, std::size_t data_siz } } // namespace esphome::thermopro_ble - -#endif diff --git a/esphome/components/thermopro_ble/thermopro_ble.h b/esphome/components/thermopro_ble/thermopro_ble.h index ca04fbea39..1c05516e47 100644 --- a/esphome/components/thermopro_ble/thermopro_ble.h +++ b/esphome/components/thermopro_ble/thermopro_ble.h @@ -2,9 +2,7 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" -#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" - -#ifdef USE_ESP32 +#include "esphome/components/ble_device_base/ble_device.h" namespace esphome::thermopro_ble { @@ -17,11 +15,11 @@ struct ParseResult { using DeviceParser = optional (*)(const uint8_t *data, std::size_t data_size); -class ThermoProBLE final : public Component, public esp32_ble_tracker::ESPBTDeviceListener { +class ThermoProBLE final : public Component, public ble_device_base::ESPBTDeviceListener { public: void set_address(uint64_t address) { this->address_ = address; }; - bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; + bool parse_device(const ble_device_base::ESPBTDevice &device) override; void dump_config() override; void set_signal_strength(sensor::Sensor *signal_strength) { this->signal_strength_ = signal_strength; } void set_temperature(sensor::Sensor *temperature) { this->temperature_ = temperature; } @@ -45,5 +43,3 @@ class ThermoProBLE final : public Component, public esp32_ble_tracker::ESPBTDevi }; } // namespace esphome::thermopro_ble - -#endif diff --git a/esphome/components/xiaomi_ble/__init__.py b/esphome/components/xiaomi_ble/__init__.py index 541a0e7894..7f5045f1ce 100644 --- a/esphome/components/xiaomi_ble/__init__.py +++ b/esphome/components/xiaomi_ble/__init__.py @@ -1,22 +1,25 @@ import esphome.codegen as cg -from esphome.components import esp32_ble_tracker +from esphome.components import ble_device_base import esphome.config_validation as cv from esphome.const import CONF_ID -DEPENDENCIES = ["esp32_ble_tracker"] +AUTO_LOAD = ["ble_device_base"] xiaomi_ble_ns = cg.esphome_ns.namespace("xiaomi_ble") XiaomiListener = xiaomi_ble_ns.class_( - "XiaomiListener", esp32_ble_tracker.ESPBTDeviceListener + "XiaomiListener", ble_device_base.ESPBTDeviceListener ) -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(XiaomiListener), - } -).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) +CONFIG_SCHEMA = cv.All( + ble_device_base.rename_legacy_hub_id("xiaomi_ble"), + cv.Schema( + { + cv.GenerateID(): cv.declare_id(XiaomiListener), + } + ).extend(ble_device_base.BLE_DEVICE_SCHEMA), +) async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - await esp32_ble_tracker.register_ble_device(var, config) + await ble_device_base.register_ble_device(var, config) diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index 0961df2bd6..0a05950c5a 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -2,14 +2,21 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#ifdef USE_ESP32 - #include + +// AES-CCM backend for encrypted-payload (bindkey) decryption: +// - ESP32 + ESP-IDF >= 6.0 -> PSA crypto (psa_aead_decrypt), hardware-backed. +// - every other platform -> the portable software AES-CCM in ble_device_base, so +// decryption never depends on the SDK exposing mbedtls/PSA to application code. +#ifdef USE_ESP32 #include #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) #include -#else -#include "mbedtls/ccm.h" +#define XIAOMI_CRYPTO_PSA +#endif +#endif +#ifndef XIAOMI_CRYPTO_PSA +#include "esphome/components/ble_device_base/ble_aes_ccm.h" #endif namespace esphome::xiaomi_ble { @@ -166,7 +173,7 @@ bool parse_xiaomi_message(const std::vector &message, XiaomiParseResult return success; } -optional parse_xiaomi_header(const esp32_ble_tracker::ServiceData &service_data) { +optional parse_xiaomi_header(const ble_device_base::ServiceData &service_data) { XiaomiParseResult result; if (!service_data.uuid.contains(0x95, 0xFE)) { ESP_LOGVV(TAG, "parse_xiaomi_header(): no service data UUID magic bytes."); @@ -318,7 +325,7 @@ bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, c memcpy(vector.iv + 6, v + 2, 3); // sensor type (2) + packet id (1) memcpy(vector.iv + 9, v + raw.size() - 7, 3); // payload counter -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) +#ifdef XIAOMI_CRYPTO_PSA // PSA AEAD expects ciphertext + tag concatenated uint8_t ct_with_tag[sizeof(vector.ciphertext) + sizeof(vector.tag)]; memcpy(ct_with_tag, vector.ciphertext, vector.datasize); @@ -344,20 +351,10 @@ bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, c psa_destroy_key(key_id); bool decrypt_ok = (status == PSA_SUCCESS && plaintext_length == vector.datasize); #else - mbedtls_ccm_context ctx; - mbedtls_ccm_init(&ctx); - - int ret = mbedtls_ccm_setkey(&ctx, MBEDTLS_CIPHER_ID_AES, vector.key, vector.keysize * 8); - if (ret) { - ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): mbedtls_ccm_setkey() failed."); - mbedtls_ccm_free(&ctx); - return false; - } - - ret = mbedtls_ccm_auth_decrypt(&ctx, vector.datasize, vector.iv, vector.ivsize, vector.authdata, vector.authsize, - vector.ciphertext, vector.plaintext, vector.tag, vector.tagsize); - mbedtls_ccm_free(&ctx); - bool decrypt_ok = (ret == 0); + // Portable software AES-CCM (ble_device_base) — no SDK mbedtls/PSA dependency. + bool decrypt_ok = ble_device_base::aes_ccm_auth_decrypt(vector.key, vector.iv, vector.ivsize, vector.authdata, + vector.authsize, vector.ciphertext, vector.datasize, + vector.plaintext, vector.tag, vector.tagsize); #endif if (!decrypt_ok) { @@ -448,7 +445,7 @@ bool report_xiaomi_results(const optional &result, const char return true; } -bool XiaomiListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { +bool XiaomiListener::parse_device(const ble_device_base::ESPBTDevice &device) { // Previously the message was parsed twice per packet, once by XiaomiListener::parse_device() // and then again by the respective device class's parse_device() function. Parsing the header // here and then for each device seems to be unnecessary and complicates the duplicate packet filtering. @@ -460,5 +457,3 @@ bool XiaomiListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) } } // namespace esphome::xiaomi_ble - -#endif diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.h b/esphome/components/xiaomi_ble/xiaomi_ble.h index 1ebcf0e2f5..2f3a14c150 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.h +++ b/esphome/components/xiaomi_ble/xiaomi_ble.h @@ -1,12 +1,10 @@ #pragma once -#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/components/ble_device_base/ble_device.h" #include "esphome/core/component.h" #include -#ifdef USE_ESP32 - namespace esphome::xiaomi_ble { struct XiaomiParseResult { @@ -68,15 +66,13 @@ struct XiaomiAESVector { bool parse_xiaomi_value(uint16_t value_type, const uint8_t *data, uint8_t value_length, XiaomiParseResult &result); bool parse_xiaomi_message(const std::vector &message, XiaomiParseResult &result); -optional parse_xiaomi_header(const esp32_ble_tracker::ServiceData &service_data); +optional parse_xiaomi_header(const ble_device_base::ServiceData &service_data); bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, const uint64_t &address); bool report_xiaomi_results(const optional &result, const char *address); -class XiaomiListener final : public esp32_ble_tracker::ESPBTDeviceListener { +class XiaomiListener final : public ble_device_base::ESPBTDeviceListener { public: - bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; + bool parse_device(const ble_device_base::ESPBTDevice &device) override; }; } // namespace esphome::xiaomi_ble - -#endif diff --git a/tests/components/ble_device_base/test_aes_ccm.cpp b/tests/components/ble_device_base/test_aes_ccm.cpp index 39b2f81dcf..c844a30b2c 100644 --- a/tests/components/ble_device_base/test_aes_ccm.cpp +++ b/tests/components/ble_device_base/test_aes_ccm.cpp @@ -19,6 +19,33 @@ const uint8_t TAG[4] = {0x48, 0x4d, 0xaa, 0x56}; const uint8_t PLAINTEXT[7] = {0x02, 0x01, 0x64, 0x03, 0x10, 0x8a, 0x01}; } // namespace +// Xiaomi's parameters differ from BTHome's: a 12-byte nonce and a 1-byte AAD +// (0x11). Both the AAD block and the l = 3 length encoding are only reachable +// through this shape, so they need their own vector. Generated the same way. +namespace { +const uint8_t NONCE_XIAOMI[12] = {0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b}; +const uint8_t AAD_XIAOMI[1] = {0x11}; +const uint8_t CIPHERTEXT_XIAOMI[5] = {0xc3, 0x7e, 0x0a, 0x1d, 0x23}; +const uint8_t TAG_XIAOMI[4] = {0x98, 0x79, 0x87, 0xc6}; +const uint8_t PLAINTEXT_XIAOMI[5] = {0x04, 0x10, 0x02, 0xd4, 0x00}; +} // namespace + +TEST(BleAesCcm, DecryptsXiaomiShapedVector) { + uint8_t out[sizeof(PLAINTEXT_XIAOMI)] = {}; + EXPECT_TRUE(aes_ccm_auth_decrypt(KEY, NONCE_XIAOMI, sizeof(NONCE_XIAOMI), AAD_XIAOMI, sizeof(AAD_XIAOMI), + CIPHERTEXT_XIAOMI, sizeof(CIPHERTEXT_XIAOMI), out, TAG_XIAOMI, sizeof(TAG_XIAOMI))); + EXPECT_EQ(0, memcmp(out, PLAINTEXT_XIAOMI, sizeof(PLAINTEXT_XIAOMI))); +} + +TEST(BleAesCcm, RejectsWrongAssociatedData) { + uint8_t bad_aad[sizeof(AAD_XIAOMI)]; + memcpy(bad_aad, AAD_XIAOMI, sizeof(AAD_XIAOMI)); + bad_aad[0] ^= 0x01; + uint8_t out[sizeof(PLAINTEXT_XIAOMI)] = {}; + EXPECT_FALSE(aes_ccm_auth_decrypt(KEY, NONCE_XIAOMI, sizeof(NONCE_XIAOMI), bad_aad, sizeof(bad_aad), + CIPHERTEXT_XIAOMI, sizeof(CIPHERTEXT_XIAOMI), out, TAG_XIAOMI, sizeof(TAG_XIAOMI))); +} + TEST(BleAesCcm, DecryptsAndAuthenticatesKnownVector) { uint8_t out[sizeof(PLAINTEXT)] = {}; EXPECT_TRUE(aes_ccm_auth_decrypt(KEY, NONCE, sizeof(NONCE), nullptr, 0, CIPHERTEXT, sizeof(CIPHERTEXT), out, TAG, diff --git a/tests/components/exposure_notifications/common-ln.yaml b/tests/components/exposure_notifications/common-ln.yaml new file mode 100644 index 0000000000..f3f9b93464 --- /dev/null +++ b/tests/components/exposure_notifications/common-ln.yaml @@ -0,0 +1,5 @@ +exposure_notifications: + on_exposure_notification: + then: + - lambda: | + ESP_LOGD("main", "RSSI: %d", x.rssi); diff --git a/tests/components/exposure_notifications/common.yaml b/tests/components/exposure_notifications/common.yaml index faba5bb2d1..8cc209ff4e 100644 --- a/tests/components/exposure_notifications/common.yaml +++ b/tests/components/exposure_notifications/common.yaml @@ -1,7 +1,10 @@ esp32_ble_tracker: + id: ble_tracker_hub exposure_notifications: on_exposure_notification: + # Explicit ble_hub_id: pins the neutral binding as a declared key. + ble_hub_id: ble_tracker_hub then: - lambda: | ESP_LOGD("main", "Got notification:"); diff --git a/tests/components/exposure_notifications/test.ln882x-ard.yaml b/tests/components/exposure_notifications/test.ln882x-ard.yaml new file mode 100644 index 0000000000..964f5b68b0 --- /dev/null +++ b/tests/components/exposure_notifications/test.ln882x-ard.yaml @@ -0,0 +1,3 @@ +packages: + ln882h_ble_tracker: !include ../ln882h_ble_tracker/common.yaml + exposure_notifications: !include common-ln.yaml diff --git a/tests/components/exposure_notifications/validate.bk72xx-ard.yaml b/tests/components/exposure_notifications/validate.bk72xx-ard.yaml new file mode 100644 index 0000000000..b487591d95 --- /dev/null +++ b/tests/components/exposure_notifications/validate.bk72xx-ard.yaml @@ -0,0 +1,15 @@ +# Config-only: the CI base board (generic-bk7252, BLE 4.2) cannot compile the +# BLE 5.x tracker, so this fixture proves validation (schema + neutral binding) +# on a non-esp32 platform; codegen and compilation are not exercised here. +bk72xx_ble_tracker: + id: ble_tracker_hub + +exposure_notifications: + on_exposure_notification: + # Explicit ble_hub_id: pins the neutral binding as a declared key. + ble_hub_id: ble_tracker_hub + then: + - lambda: | + ESP_LOGD("main", "Got notification:"); + ESP_LOGD("main", " RPI: %s", format_hex(x.rolling_proximity_identifier).c_str()); + ESP_LOGD("main", " RSSI: %d", x.rssi); diff --git a/tests/components/thermopro_ble/common-ln.yaml b/tests/components/thermopro_ble/common-ln.yaml new file mode 100644 index 0000000000..10aff2d658 --- /dev/null +++ b/tests/components/thermopro_ble/common-ln.yaml @@ -0,0 +1,7 @@ +sensor: + - platform: thermopro_ble + mac_address: FE:74:B8:6A:97:B7 + temperature: + name: ThermoPro Temperature + humidity: + name: ThermoPro Humidity diff --git a/tests/components/thermopro_ble/common.yaml b/tests/components/thermopro_ble/common.yaml index 297725e1c3..63fab83c01 100644 --- a/tests/components/thermopro_ble/common.yaml +++ b/tests/components/thermopro_ble/common.yaml @@ -1,7 +1,10 @@ esp32_ble_tracker: + id: ble_tracker_hub sensor: + # Explicit ble_hub_id: pins the neutral binding as a declared key. - platform: thermopro_ble + ble_hub_id: ble_tracker_hub mac_address: FE:74:B8:6A:97:B7 temperature: name: "ThermoPro Temperature" diff --git a/tests/components/thermopro_ble/test.ln882x-ard.yaml b/tests/components/thermopro_ble/test.ln882x-ard.yaml new file mode 100644 index 0000000000..b3a59e83fc --- /dev/null +++ b/tests/components/thermopro_ble/test.ln882x-ard.yaml @@ -0,0 +1,3 @@ +packages: + ln882h_ble_tracker: !include ../ln882h_ble_tracker/common.yaml + thermopro_ble: !include common-ln.yaml diff --git a/tests/components/thermopro_ble/validate.bk72xx-ard.yaml b/tests/components/thermopro_ble/validate.bk72xx-ard.yaml new file mode 100644 index 0000000000..8f1ec417be --- /dev/null +++ b/tests/components/thermopro_ble/validate.bk72xx-ard.yaml @@ -0,0 +1,24 @@ +# Config-only: the CI base board (generic-bk7252, BLE 4.2) cannot compile the +# BLE 5.x tracker, so this fixture proves validation (schema + neutral binding) +# on a non-esp32 platform; codegen and compilation are not exercised here. +bk72xx_ble_tracker: + id: ble_tracker_hub + +sensor: + # Explicit ble_hub_id: pins the neutral binding as a declared key. + - platform: thermopro_ble + ble_hub_id: ble_tracker_hub + mac_address: FE:74:B8:6A:97:B7 + temperature: + name: "ThermoPro Temperature" + humidity: + name: "ThermoPro Humidity" + battery_level: + name: "ThermoPro Battery Level" + signal_strength: + name: "ThermoPro Signal Strength" + # No ble_hub_id: exercises the generated binding real configs use. + - platform: thermopro_ble + mac_address: FE:74:B8:6A:97:B8 + temperature: + name: BK ThermoPro Implicit Temperature diff --git a/tests/components/xiaomi_ble/common-ln.yaml b/tests/components/xiaomi_ble/common-ln.yaml new file mode 100644 index 0000000000..d46c306d65 --- /dev/null +++ b/tests/components/xiaomi_ble/common-ln.yaml @@ -0,0 +1 @@ +xiaomi_ble: diff --git a/tests/components/xiaomi_ble/common.yaml b/tests/components/xiaomi_ble/common.yaml index 9d10393177..f218426c23 100644 --- a/tests/components/xiaomi_ble/common.yaml +++ b/tests/components/xiaomi_ble/common.yaml @@ -1,3 +1,6 @@ esp32_ble_tracker: + id: ble_tracker_hub +# Explicit ble_hub_id: pins the neutral binding as a declared key. xiaomi_ble: + ble_hub_id: ble_tracker_hub diff --git a/tests/components/xiaomi_ble/test.ln882x-ard.yaml b/tests/components/xiaomi_ble/test.ln882x-ard.yaml new file mode 100644 index 0000000000..014985c5c5 --- /dev/null +++ b/tests/components/xiaomi_ble/test.ln882x-ard.yaml @@ -0,0 +1,3 @@ +packages: + ln882h_ble_tracker: !include ../ln882h_ble_tracker/common.yaml + xiaomi_ble: !include common-ln.yaml diff --git a/tests/components/xiaomi_ble/validate.bk72xx-ard.yaml b/tests/components/xiaomi_ble/validate.bk72xx-ard.yaml new file mode 100644 index 0000000000..20b42c4263 --- /dev/null +++ b/tests/components/xiaomi_ble/validate.bk72xx-ard.yaml @@ -0,0 +1,9 @@ +# Config-only: the CI base board (generic-bk7252, BLE 4.2) cannot compile the +# BLE 5.x tracker, so this fixture proves validation (schema + neutral binding) +# on a non-esp32 platform; codegen and compilation are not exercised here. +bk72xx_ble_tracker: + id: ble_tracker_hub + +# Explicit ble_hub_id: pins the neutral binding as a declared key. +xiaomi_ble: + ble_hub_id: ble_tracker_hub