Files
mrambossekandClaude Opus 4.8 878f7c9c27 Initial commit: esphome-xtool-ap2 ESPHome component
Control one or two xTool SafetyPro AP2 air purifiers over BLE and expose them to Home Assistant via an ESP32 (the ap2_hub external component): per-slot fan control with live gear sync, runtime-settable flash-persisted MACs, an on-device scanner, M9033 status polling (filter life, serial, firmware), buzzer control, and BLE bonding. Includes the PC debug tool (tools/ap2_ble.py) and the reverse-engineered protocol spec (SPEC.md).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 14:45:11 +02:00

55 lines
1.6 KiB
Python

import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor
from . import AP2Hub
CONF_AP2_HUB_ID = "ap2_hub_id"
CONF_SLOT = "slot"
CONF_TYPE = "type"
# "connected" / "bonded" are per-slot; "scan_active" is a hub-level flag.
TYPES = ["connected", "bonded", "scan_active"]
_SLOT_TYPES = ("connected", "bonded")
def _validate(config):
has_slot = CONF_SLOT in config
t = config.get(CONF_TYPE)
if t is None:
t = "connected" if has_slot else None
if t is None:
raise cv.Invalid(
"set 'type: scan_active', or give a 'slot' for a connected/bonded sensor"
)
config[CONF_TYPE] = t
if t in _SLOT_TYPES and not has_slot:
raise cv.Invalid(f"type '{t}' requires a 'slot'")
if t == "scan_active" and has_slot:
raise cv.Invalid("type 'scan_active' must not have a 'slot'")
return config
CONFIG_SCHEMA = cv.All(
binary_sensor.binary_sensor_schema().extend(
{
cv.GenerateID(CONF_AP2_HUB_ID): cv.use_id(AP2Hub),
cv.Optional(CONF_TYPE): cv.one_of(*TYPES, lower=True),
cv.Optional(CONF_SLOT): cv.int_range(min=0, max=1),
}
),
_validate,
)
async def to_code(config):
var = await binary_sensor.new_binary_sensor(config)
parent = await cg.get_variable(config[CONF_AP2_HUB_ID])
t = config[CONF_TYPE]
if t == "connected":
cg.add(parent.set_connected_sensor(config[CONF_SLOT], var))
elif t == "bonded":
cg.add(parent.set_bonded_sensor(config[CONF_SLOT], var))
else:
cg.add(parent.set_scan_active_sensor(var))