import re import esphome.codegen as cg from esphome.components import esp32_ble from esphome.components.esp32 import add_idf_sdkconfig_option from esphome.components.esp32_ble import CONF_BLE_ID import esphome.config_validation as cv from esphome.const import CONF_ID from esphome.core import TimePeriod AUTO_LOAD = ["esp32_ble"] DEPENDENCIES = ["esp32"] ibeacon_rotator_ns = cg.esphome_ns.namespace("ibeacon_rotator") IBeaconRotator = ibeacon_rotator_ns.class_("IBeaconRotator", cg.Component) CONF_UUID_PREFIX = "uuid_prefix" CONF_BROADCAST_LENGTH = "broadcast_length" CONF_MIN_INTERVAL = "min_interval" CONF_MAX_INTERVAL = "max_interval" CONF_MAJOR = "major" CONF_MINOR = "minor" CONF_MEASURED_POWER = "measured_power" def validate_uuid_prefix(value): if not isinstance(value, str): raise cv.Invalid("uuid_prefix must be a string of hex bytes") stripped = re.sub(r"[:\-\s]", "", value) if len(stripped) != 20: raise cv.Invalid( f"uuid_prefix must be exactly 10 bytes (20 hex chars after stripping separators); got {len(stripped)}" ) if not re.fullmatch(r"[0-9A-Fa-f]+", stripped): raise cv.Invalid("uuid_prefix contains non-hex characters") return [int(stripped[i : i + 2], 16) for i in range(0, 20, 2)] def validate_config(config): if config[CONF_MIN_INTERVAL] > config[CONF_MAX_INTERVAL]: raise cv.Invalid("min_interval must be <= max_interval") return config _INTERVAL = cv.All( cv.positive_time_period_milliseconds, cv.Range(min=TimePeriod(milliseconds=20), max=TimePeriod(milliseconds=10240)), ) CONFIG_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(): cv.declare_id(IBeaconRotator), cv.GenerateID(CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE), cv.Required(CONF_UUID_PREFIX): validate_uuid_prefix, cv.Optional( CONF_BROADCAST_LENGTH, default="30s" ): cv.positive_time_period_milliseconds, cv.Optional(CONF_MIN_INTERVAL, default="100ms"): _INTERVAL, cv.Optional(CONF_MAX_INTERVAL, default="100ms"): _INTERVAL, cv.Optional(CONF_MAJOR, default=0): cv.uint16_t, cv.Optional(CONF_MINOR, default=0): cv.uint16_t, cv.Optional(CONF_MEASURED_POWER, default=-59): cv.int_range( min=-128, max=0 ), } ).extend(cv.COMPONENT_SCHEMA), validate_config, ) FINAL_VALIDATE_SCHEMA = esp32_ble.validate_variant async def to_code(config): prefix_arr = [cg.RawExpression(f"0x{b:02X}") for b in config[CONF_UUID_PREFIX]] var = cg.new_Pvariable(config[CONF_ID], prefix_arr) parent = await cg.get_variable(config[CONF_BLE_ID]) esp32_ble.register_gap_event_handler(parent, var) await cg.register_component(var, config) cg.add(var.set_broadcast_length(config[CONF_BROADCAST_LENGTH])) cg.add(var.set_min_interval(config[CONF_MIN_INTERVAL])) cg.add(var.set_max_interval(config[CONF_MAX_INTERVAL])) cg.add(var.set_major(config[CONF_MAJOR])) cg.add(var.set_minor(config[CONF_MINOR])) cg.add(var.set_measured_power(config[CONF_MEASURED_POWER])) add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True)