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>
46 lines
1.2 KiB
C++
46 lines
1.2 KiB
C++
#pragma once
|
|
|
|
#include "esphome/core/component.h"
|
|
#include "esphome/components/fan/fan.h"
|
|
#include "ap2_hub.h"
|
|
|
|
namespace esphome {
|
|
namespace ap2_hub {
|
|
|
|
/* Per-slot fan: off + speeds 1..4 -> gear A0..A4. Commands are silently dropped
|
|
* while the slot is not ready (unset / disconnected / scanning). */
|
|
class AP2Fan : public fan::Fan, public Component {
|
|
public:
|
|
void set_hub(AP2Hub *hub) { this->hub_ = hub; }
|
|
void set_slot(uint8_t slot) { this->slot_ = slot; }
|
|
|
|
fan::FanTraits get_traits() override {
|
|
auto t = fan::FanTraits();
|
|
t.set_speed(true);
|
|
t.set_supported_speed_count(4);
|
|
return t;
|
|
}
|
|
|
|
/* Reflect the AP2's actual gear (from an M9033 poll) into this entity WITHOUT
|
|
* sending a command back — keeps the UI in sync with the physical button. */
|
|
void update_from_device(uint8_t gear) {
|
|
bool st = gear > 0;
|
|
int sp = gear > 0 ? (int) gear : this->speed;
|
|
if (this->state == st && (!st || this->speed == sp))
|
|
return; // no change
|
|
this->state = st;
|
|
if (st)
|
|
this->speed = sp;
|
|
this->publish_state();
|
|
}
|
|
|
|
protected:
|
|
void control(const fan::FanCall &call) override;
|
|
|
|
AP2Hub *hub_{nullptr};
|
|
uint8_t slot_{0};
|
|
};
|
|
|
|
} // namespace ap2_hub
|
|
} // namespace esphome
|