mirror of
https://github.com/esphome/esphome.git
synced 2026-10-14 02:02:51 +00:00
[ecocomfort2] Add component for Fantini Cosmi VMC units via BLE (Part 1: hub + fan) (#17092)
Co-authored-by: gledian <gledina.kruja@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
co-authored by
gledian
Claude Opus 4.8
J. Nick Koston
J. Nick Koston
parent
5a819f4b48
commit
93183c661e
@@ -160,6 +160,7 @@ esphome/components/ds248x/* @tomwellnitz
|
||||
esphome/components/ds3231/* @linkedupbits
|
||||
esphome/components/dsmr/* @glmnet @PolarGoose
|
||||
esphome/components/duty_time/* @dudanov
|
||||
esphome/components/ecocomfort2/* @gledian
|
||||
esphome/components/ee895/* @Stock-M
|
||||
esphome/components/ektf2232/touchscreen/* @jesserockz
|
||||
esphome/components/emc2101/* @ellull
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import ble_client, esp32_ble
|
||||
from esphome.components.esp32_ble import (
|
||||
CONF_AUTH_REQ_MODE,
|
||||
CONF_IO_CAPABILITY,
|
||||
CONF_MAX_KEY_SIZE,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.cpp_generator import MockObj
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
DOMAIN = "ecocomfort2"
|
||||
CONF_ECOCOMFORT2_ID = "ecocomfort2_id"
|
||||
|
||||
CODEOWNERS = ["@gledian"]
|
||||
DEPENDENCIES = ["ble_client"]
|
||||
MULTI_CONF = True
|
||||
|
||||
ecocomfort2_ns = cg.esphome_ns.namespace(DOMAIN)
|
||||
Ecocomfort2Hub = ecocomfort2_ns.class_(
|
||||
"Ecocomfort2Hub", ble_client.BLEClientNode, cg.PollingComponent
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Ecocomfort2Hub),
|
||||
}
|
||||
)
|
||||
.extend(ble_client.BLE_CLIENT_SCHEMA)
|
||||
.extend(cv.polling_component_schema("30s"))
|
||||
)
|
||||
|
||||
ECOCOMFORT2_CLIENT_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ECOCOMFORT2_ID): cv.use_id(Ecocomfort2Hub),
|
||||
}
|
||||
)
|
||||
|
||||
_request_child_slot = cg.slot_counter("ECOCOMFORT2_CHILD_COUNT")
|
||||
|
||||
|
||||
async def register_ecocomfort2_child(var: MockObj, config: ConfigType) -> None:
|
||||
parent = await cg.get_variable(config[CONF_ECOCOMFORT2_ID])
|
||||
_request_child_slot(str(parent))
|
||||
cg.add(parent.register_child(var))
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await ble_client.register_ble_node(var, config)
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
# The unit only talks over an encrypted, bonded link
|
||||
ble_conf = fv.full_config.get()[esp32_ble.DOMAIN]
|
||||
if (
|
||||
ble_conf[CONF_IO_CAPABILITY] != "none"
|
||||
or ble_conf.get(CONF_AUTH_REQ_MODE) != "sc_bond"
|
||||
or ble_conf.get(CONF_MAX_KEY_SIZE) != 16
|
||||
):
|
||||
raise cv.Invalid(
|
||||
"ecocomfort2 needs esp32_ble configured with io_capability: none, "
|
||||
"auth_req_mode: sc_bond and max_key_size: 16"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::ecocomfort2 {
|
||||
|
||||
class Ecocomfort2Hub;
|
||||
|
||||
class Ecocomfort2Client : public Parented<Ecocomfort2Hub> {
|
||||
public:
|
||||
/// Called after each successful C_OPER readback.
|
||||
virtual void on_status() = 0;
|
||||
};
|
||||
|
||||
} // namespace esphome::ecocomfort2
|
||||
|
||||
#endif // USE_ESP32
|
||||
@@ -0,0 +1,146 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_ESP32
|
||||
#include "ecocomfort2_fan.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <iterator>
|
||||
|
||||
namespace esphome::ecocomfort2 {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "ecocomfort2.fan");
|
||||
|
||||
static constexpr int SPEED_COUNT = SPEED_VEL3;
|
||||
|
||||
struct Preset {
|
||||
const char *name;
|
||||
uint8_t mode;
|
||||
bool auto_mode;
|
||||
};
|
||||
|
||||
// Order matters: the fan core persists the preset as an index into this list.
|
||||
static constexpr Preset PRESETS[] = {
|
||||
{"In", OPER_IN, false},
|
||||
{"Out", OPER_OUT, false},
|
||||
{"In-Out", OPER_IN_OUT, false},
|
||||
{"Sensor", OPER_SENSOR_OR_AUTO, false},
|
||||
{"Auto", OPER_SENSOR_OR_AUTO, true},
|
||||
};
|
||||
static constexpr const Preset &PRESET_IN_OUT = PRESETS[2];
|
||||
static constexpr const Preset &PRESET_SENSOR = PRESETS[3];
|
||||
|
||||
static const Preset *find_preset(const char *name) {
|
||||
for (const auto &preset : PRESETS) {
|
||||
if (preset.name == name)
|
||||
return &preset;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static const Preset *find_preset(uint8_t mode, bool auto_mode) {
|
||||
for (const auto &preset : PRESETS) {
|
||||
if (preset.mode == mode && preset.auto_mode == auto_mode)
|
||||
return &preset;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Ecocomfort2Fan::setup() {
|
||||
this->set_supported_preset_modes(
|
||||
{PRESETS[0].name, PRESETS[1].name, PRESETS[2].name, PRESETS[3].name, PRESETS[4].name});
|
||||
// Restore the last preset and speed so turning on resumes them; on/off comes from the device.
|
||||
auto restore = this->restore_state_();
|
||||
if (restore.has_value()) {
|
||||
this->speed = restore->speed;
|
||||
if (restore->preset_mode < std::size(PRESETS))
|
||||
this->set_preset_mode_(PRESETS[restore->preset_mode].name);
|
||||
}
|
||||
}
|
||||
|
||||
void Ecocomfort2Fan::dump_config() { LOG_FAN("", "Ecocomfort2 Fan", this); }
|
||||
|
||||
fan::FanTraits Ecocomfort2Fan::get_traits() {
|
||||
fan::FanTraits traits;
|
||||
traits.set_speed(true);
|
||||
traits.set_supported_speed_count(SPEED_COUNT);
|
||||
this->wire_preset_modes_(traits);
|
||||
return traits;
|
||||
}
|
||||
|
||||
void Ecocomfort2Fan::control(const fan::FanCall &call) {
|
||||
if (!this->parent_->is_ready()) {
|
||||
ESP_LOGW(TAG, "Not ready, cannot handle control call");
|
||||
return;
|
||||
}
|
||||
|
||||
// Any call that does not turn the fan off turns it on
|
||||
auto state = call.get_state();
|
||||
this->state = !state.has_value() || *state;
|
||||
// The core clears the preset in a speed call; here the preset is the air flow mode, so keep it.
|
||||
if (call.has_preset_mode())
|
||||
this->set_preset_mode_(call.get_preset_mode());
|
||||
|
||||
const Preset *preset = find_preset(this->get_preset_mode().c_str());
|
||||
if (preset == nullptr) {
|
||||
preset = &PRESET_IN_OUT;
|
||||
this->set_preset_mode_(preset->name);
|
||||
}
|
||||
auto speed = call.get_speed();
|
||||
if (speed.has_value()) {
|
||||
this->speed = *speed;
|
||||
// A manual speed leaves automatic speed control
|
||||
if (preset->auto_mode) {
|
||||
preset = &PRESET_SENSOR;
|
||||
this->set_preset_mode_(preset->name);
|
||||
}
|
||||
}
|
||||
this->speed = clamp(this->speed, 1, SPEED_COUNT);
|
||||
|
||||
if (!this->state) {
|
||||
this->parent_->write_oper(OPER_OFF, SPEED_OFF);
|
||||
} else {
|
||||
this->parent_->write_oper(preset->mode, preset->auto_mode ? FLAG_AUTO : static_cast<uint8_t>(this->speed));
|
||||
}
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
void Ecocomfort2Fan::on_status() {
|
||||
uint8_t mode = this->parent_->get_mode();
|
||||
uint8_t raw_speed = this->parent_->get_speed();
|
||||
|
||||
// Off keeps the preset and speed so turning on resumes them
|
||||
if (mode == OPER_OFF) {
|
||||
if (!this->state)
|
||||
return;
|
||||
this->state = false;
|
||||
this->publish_state();
|
||||
return;
|
||||
}
|
||||
|
||||
int new_speed = this->speed;
|
||||
uint8_t base_speed = raw_speed & SPEED_MASK;
|
||||
if (raw_speed & FLAG_BOOST) {
|
||||
new_speed = SPEED_COUNT;
|
||||
} else if (raw_speed & FLAG_NIGHT) {
|
||||
new_speed = SPEED_SLEEP;
|
||||
} else if (base_speed >= SPEED_SLEEP && base_speed <= SPEED_VEL3) {
|
||||
new_speed = base_speed;
|
||||
}
|
||||
const Preset *preset = find_preset(mode, (raw_speed & FLAG_AUTO) != 0);
|
||||
const char *new_preset = preset != nullptr ? preset->name : nullptr;
|
||||
bool same_preset = new_preset == nullptr ? !this->has_preset_mode() : this->get_preset_mode().c_str() == new_preset;
|
||||
if (this->state && this->speed == new_speed && same_preset)
|
||||
return;
|
||||
|
||||
this->state = true;
|
||||
this->speed = new_speed;
|
||||
if (new_preset != nullptr) {
|
||||
this->set_preset_mode_(new_preset);
|
||||
} else {
|
||||
this->clear_preset_mode_();
|
||||
}
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
} // namespace esphome::ecocomfort2
|
||||
|
||||
#endif // USE_ESP32
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/fan/fan.h"
|
||||
#include "ecocomfort2_child.h"
|
||||
#include "ecocomfort2_hub.h"
|
||||
|
||||
namespace esphome::ecocomfort2 {
|
||||
|
||||
class Ecocomfort2Fan : public fan::Fan, public Ecocomfort2Client, public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
fan::FanTraits get_traits() override;
|
||||
void on_status() override;
|
||||
|
||||
protected:
|
||||
void control(const fan::FanCall &call) override;
|
||||
};
|
||||
|
||||
} // namespace esphome::ecocomfort2
|
||||
|
||||
#endif // USE_ESP32
|
||||
@@ -0,0 +1,154 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_ESP32
|
||||
#include "ecocomfort2_hub.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::ecocomfort2 {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "ecocomfort2");
|
||||
|
||||
static const espbt::ESPBTUUID SERVICE_UUID = espbt::ESPBTUUID::from_raw("f4b827c3-e660-4bc8-bdf6-3c8e9b845e0d");
|
||||
static const espbt::ESPBTUUID C_OPER_UUID = espbt::ESPBTUUID::from_raw("b9d6f678-bc0d-4a73-90c8-60b0f07301f1");
|
||||
|
||||
void Ecocomfort2Hub::update() {
|
||||
if (!this->is_ready()) {
|
||||
ESP_LOGD(TAG, "[%s] Not ready, skipping update", this->parent_->address_str());
|
||||
return;
|
||||
}
|
||||
auto status = esp_ble_gattc_read_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(),
|
||||
this->char_handle_oper_, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "[%s] C_OPER read request failed, status=%d", this->parent_->address_str(), status);
|
||||
}
|
||||
}
|
||||
|
||||
void Ecocomfort2Hub::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Ecocomfort2 Hub:\n"
|
||||
" Address: %s",
|
||||
this->parent_->address_str());
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
void Ecocomfort2Hub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
switch (event) {
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
ESP_LOGD(TAG, "[%s] Disconnected: reason=%d", this->parent_->address_str(), param->disconnect.reason);
|
||||
this->cancel_timeout(AUTH_TIMEOUT_ID);
|
||||
this->cancel_timeout(READBACK_TIMEOUT_ID);
|
||||
this->status_set_warning();
|
||||
this->char_handle_oper_ = 0;
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
// Keep the handle, not the characteristic: ble_client frees its services once all nodes are established.
|
||||
auto *chr = this->parent_->get_characteristic(SERVICE_UUID, C_OPER_UUID);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW(TAG, "[%s] No C_OPER characteristic found, disconnecting", this->parent_->address_str());
|
||||
this->parent_->disconnect();
|
||||
break;
|
||||
}
|
||||
this->char_handle_oper_ = chr->handle;
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
this->status_set_warning();
|
||||
|
||||
// A bonded device may already have re-encrypted the link before discovery finished.
|
||||
if (this->parent_->is_paired()) {
|
||||
this->on_paired_();
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(TAG, "[%s] Requesting BLE encryption", this->parent_->address_str());
|
||||
if (this->parent_->pair() != ESP_OK) {
|
||||
ESP_LOGW(TAG, "[%s] BLE pair request failed, disconnecting", this->parent_->address_str());
|
||||
this->parent_->disconnect();
|
||||
break;
|
||||
}
|
||||
this->set_timeout(AUTH_TIMEOUT_ID, AUTH_TIMEOUT_MS, [this]() {
|
||||
ESP_LOGW(TAG, "[%s] BLE authentication timed out, disconnecting", this->parent_->address_str());
|
||||
this->parent_->disconnect();
|
||||
});
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_READ_CHAR_EVT: {
|
||||
if (param->read.conn_id != this->parent_->get_conn_id() || param->read.handle != this->char_handle_oper_) {
|
||||
break;
|
||||
}
|
||||
if (param->read.status == ESP_GATT_INSUF_ENCRYPTION || param->read.status == ESP_GATT_INSUF_AUTHENTICATION) {
|
||||
// ble_client reconnects and pairs again
|
||||
ESP_LOGW(TAG, "[%s] Encryption lost, disconnecting", this->parent_->address_str());
|
||||
this->parent_->disconnect();
|
||||
break;
|
||||
}
|
||||
if (param->read.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "[%s] C_OPER read failed, status=%d", this->parent_->address_str(), param->read.status);
|
||||
break;
|
||||
}
|
||||
if (param->read.value_len < 2) {
|
||||
ESP_LOGW(TAG, "[%s] C_OPER too short: %u bytes", this->parent_->address_str(), param->read.value_len);
|
||||
break;
|
||||
}
|
||||
this->mode_ = param->read.value[0];
|
||||
this->speed_ = param->read.value[1];
|
||||
ESP_LOGD(TAG, "[%s] C_OPER: mode=0x%02x, speed=0x%02x", this->parent_->address_str(), this->mode_, this->speed_);
|
||||
#ifdef ECOCOMFORT2_CHILD_COUNT
|
||||
for (auto *child : this->children_) {
|
||||
child->on_status();
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Ecocomfort2Hub::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||
if (event != ESP_GAP_BLE_AUTH_CMPL_EVT || !this->parent_->check_addr(param->ble_security.auth_cmpl.bd_addr) ||
|
||||
this->node_state != espbt::ClientState::ESTABLISHED) {
|
||||
return;
|
||||
}
|
||||
if (!param->ble_security.auth_cmpl.success) {
|
||||
this->cancel_timeout(AUTH_TIMEOUT_ID);
|
||||
ESP_LOGW(TAG, "[%s] BLE authentication failed, reason=%d, disconnecting", this->parent_->address_str(),
|
||||
param->ble_security.auth_cmpl.fail_reason);
|
||||
this->parent_->disconnect();
|
||||
return;
|
||||
}
|
||||
this->on_paired_();
|
||||
}
|
||||
|
||||
void Ecocomfort2Hub::on_paired_() {
|
||||
this->cancel_timeout(AUTH_TIMEOUT_ID);
|
||||
this->status_clear_warning();
|
||||
ESP_LOGI(TAG, "[%s] Encryption confirmed, device ready", this->parent_->address_str());
|
||||
this->update();
|
||||
}
|
||||
|
||||
void Ecocomfort2Hub::write_oper(uint8_t mode, uint8_t speed) {
|
||||
if (!this->is_ready()) {
|
||||
ESP_LOGW(TAG, "[%s] Not ready, cannot write C_OPER", this->parent_->address_str());
|
||||
return;
|
||||
}
|
||||
uint8_t data[2]{mode, speed};
|
||||
ESP_LOGD(TAG, "[%s] Writing C_OPER: mode=0x%02x, speed=0x%02x", this->parent_->address_str(), mode, speed);
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_oper_,
|
||||
sizeof(data), data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "[%s] C_OPER write failed, status=%d", this->parent_->address_str(), status);
|
||||
return;
|
||||
}
|
||||
this->set_timeout(READBACK_TIMEOUT_ID, READBACK_DELAY_MS, [this]() { this->update(); });
|
||||
}
|
||||
|
||||
#ifdef ECOCOMFORT2_CHILD_COUNT
|
||||
void Ecocomfort2Hub::register_child(Ecocomfort2Client *obj) {
|
||||
this->children_.push_back(obj);
|
||||
obj->set_parent(this);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace esphome::ecocomfort2
|
||||
|
||||
#endif // USE_ESP32
|
||||
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "ecocomfort2_child.h"
|
||||
|
||||
namespace esphome::ecocomfort2 {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
// C_OPER byte 0: operating mode
|
||||
static constexpr uint8_t OPER_OFF = 0x00;
|
||||
static constexpr uint8_t OPER_IN = 0x01;
|
||||
static constexpr uint8_t OPER_OUT = 0x02;
|
||||
static constexpr uint8_t OPER_IN_OUT = 0x03;
|
||||
static constexpr uint8_t OPER_SENSOR_OR_AUTO = 0x04;
|
||||
|
||||
// C_OPER byte 1: speed in the low nibble, flags in the high nibble
|
||||
static constexpr uint8_t SPEED_OFF = 0x00;
|
||||
static constexpr uint8_t SPEED_SLEEP = 0x01;
|
||||
static constexpr uint8_t SPEED_VEL1 = 0x02;
|
||||
static constexpr uint8_t SPEED_VEL2 = 0x03;
|
||||
static constexpr uint8_t SPEED_VEL3 = 0x04;
|
||||
static constexpr uint8_t SPEED_MASK = 0x0F;
|
||||
static constexpr uint8_t FLAG_AUTO = 0x10;
|
||||
static constexpr uint8_t FLAG_BOOST = 0x40;
|
||||
static constexpr uint8_t FLAG_NIGHT = 0x80;
|
||||
|
||||
/** Hub component for Ecocomfort 2.0 VMC via BLE. */
|
||||
class Ecocomfort2Hub : public esphome::ble_client::BLEClientNode, public PollingComponent {
|
||||
public:
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::BLUETOOTH; }
|
||||
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
|
||||
|
||||
#ifdef ECOCOMFORT2_CHILD_COUNT
|
||||
void register_child(Ecocomfort2Client *obj);
|
||||
#endif
|
||||
|
||||
/// True once the link is established and paired.
|
||||
bool is_ready() const { return this->node_state == espbt::ClientState::ESTABLISHED && this->parent_->is_paired(); }
|
||||
|
||||
/// Write C_OPER (mode byte, speed/flags byte) and read it back shortly after.
|
||||
void write_oper(uint8_t mode, uint8_t speed);
|
||||
|
||||
/// Raw C_OPER values from the last readback.
|
||||
uint8_t get_mode() const { return this->mode_; }
|
||||
uint8_t get_speed() const { return this->speed_; }
|
||||
|
||||
protected:
|
||||
static constexpr uint32_t AUTH_TIMEOUT_ID = 0;
|
||||
static constexpr uint32_t READBACK_TIMEOUT_ID = 1;
|
||||
static constexpr uint32_t AUTH_TIMEOUT_MS = 10000;
|
||||
static constexpr uint32_t READBACK_DELAY_MS = 300;
|
||||
|
||||
void on_paired_();
|
||||
|
||||
#ifdef ECOCOMFORT2_CHILD_COUNT
|
||||
StaticVector<Ecocomfort2Client *, ECOCOMFORT2_CHILD_COUNT> children_;
|
||||
#endif
|
||||
uint16_t char_handle_oper_{0};
|
||||
uint8_t mode_{OPER_OFF};
|
||||
uint8_t speed_{SPEED_OFF};
|
||||
};
|
||||
|
||||
} // namespace esphome::ecocomfort2
|
||||
|
||||
#endif // USE_ESP32
|
||||
@@ -0,0 +1,23 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import fan
|
||||
import esphome.config_validation as cv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from . import ECOCOMFORT2_CLIENT_SCHEMA, ecocomfort2_ns, register_ecocomfort2_child
|
||||
|
||||
CODEOWNERS = ["@gledian"]
|
||||
DEPENDENCIES = ["ecocomfort2"]
|
||||
|
||||
Ecocomfort2Fan = ecocomfort2_ns.class_("Ecocomfort2Fan", fan.Fan, cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
fan.fan_schema(Ecocomfort2Fan)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(ECOCOMFORT2_CLIENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = await fan.new_fan(config)
|
||||
await cg.register_component(var, config)
|
||||
await register_ecocomfort2_child(var, config)
|
||||
@@ -430,6 +430,7 @@
|
||||
#define ESPHOME_ESP32_BLE_GAP_SCAN_EVENT_HANDLER_COUNT 1
|
||||
#define ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT 1
|
||||
#define ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT 1
|
||||
#define ECOCOMFORT2_CHILD_COUNT 1
|
||||
#define ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT 2
|
||||
#define ESPHOME_ESP32_BLE_EXTENDED_AUTH_PARAMS
|
||||
#define ESPHOME_LOOP_TASK_STACK_SIZE 8192
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
esp32_ble:
|
||||
io_capability: none
|
||||
auth_req_mode: sc_bond
|
||||
max_key_size: 16
|
||||
|
||||
ble_client:
|
||||
- mac_address: 01:02:03:04:05:06
|
||||
id: ecocomfort2_ble_1
|
||||
- mac_address: 01:02:03:04:05:07
|
||||
id: ecocomfort2_ble_2
|
||||
|
||||
ecocomfort2:
|
||||
- id: ecocomfort2_hub_1
|
||||
ble_client_id: ecocomfort2_ble_1
|
||||
- id: ecocomfort2_hub_2
|
||||
ble_client_id: ecocomfort2_ble_2
|
||||
|
||||
fan:
|
||||
- platform: ecocomfort2
|
||||
name: Ecocomfort2 Fan 1
|
||||
ecocomfort2_id: ecocomfort2_hub_1
|
||||
- platform: ecocomfort2
|
||||
name: Ecocomfort2 Fan 2
|
||||
ecocomfort2_id: ecocomfort2_hub_2
|
||||
@@ -0,0 +1,3 @@
|
||||
packages:
|
||||
ble: !include ../../test_build_components/common/ble/esp32-idf.yaml
|
||||
ecocomfort2: !include common.yaml
|
||||
Reference in New Issue
Block a user