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>
35 lines
1.1 KiB
C++
35 lines
1.1 KiB
C++
#include "ap2_fan.h"
|
|
#include "esphome/core/log.h"
|
|
#include <algorithm>
|
|
|
|
namespace esphome {
|
|
namespace ap2_hub {
|
|
|
|
static const char *const TAG = "ap2_hub.fan";
|
|
|
|
void AP2Fan::control(const fan::FanCall &call) {
|
|
if (this->hub_ == nullptr || !this->hub_->slot_ready(this->slot_)) {
|
|
ESP_LOGD(TAG, "slot %u not ready; command ignored", (unsigned) this->slot_);
|
|
return; // leave published state unchanged
|
|
}
|
|
bool had_state = call.get_state().has_value();
|
|
bool had_speed = call.get_speed().has_value();
|
|
if (had_state)
|
|
this->state = *call.get_state();
|
|
if (had_speed)
|
|
this->speed = *call.get_speed();
|
|
|
|
// A speed-only change (e.g. the web_server slider) of 0 means "off", any
|
|
// other speed means "on" — keeps the slider's 0 position consistent with the
|
|
// on/off toggle. An explicit state in the call always wins.
|
|
if (had_speed && !had_state)
|
|
this->state = this->speed > 0;
|
|
|
|
uint8_t gear = this->state ? (uint8_t) std::max(1, this->speed) : 0;
|
|
this->hub_->set_gear(this->slot_, gear);
|
|
this->publish_state();
|
|
}
|
|
|
|
} // namespace ap2_hub
|
|
} // namespace esphome
|