[ecocomfort2] Add the read-only sensors and clock sync (Part 2) (#15447)

Co-authored-by: gledian <gledina.kruja@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
gledian
2026-10-12 14:27:16 -10:00
committed by GitHub
co-authored by gledian Claude Opus 4.6 pre-commit-ci-lite[bot] J. Nick Koston
parent 8aab968136
commit 4cae63bbaa
7 changed files with 420 additions and 34 deletions
+5 -2
View File
@@ -1,12 +1,12 @@
import esphome.codegen as cg
from esphome.components import ble_client, esp32_ble
from esphome.components import ble_client, esp32_ble, time
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.const import CONF_ID, CONF_TIME_ID
from esphome.cpp_generator import MockObj
import esphome.final_validate as fv
from esphome.types import ConfigType
@@ -27,6 +27,7 @@ CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(Ecocomfort2Hub),
cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
}
)
.extend(ble_client.BLE_CLIENT_SCHEMA)
@@ -52,6 +53,8 @@ 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)
if (time_id := config.get(CONF_TIME_ID)) is not None:
cg.add(var.set_time(await cg.get_variable(time_id)))
def _final_validate(config: ConfigType) -> ConfigType:
@@ -0,0 +1,33 @@
import esphome.codegen as cg
from esphome.components import binary_sensor
from esphome.components.const import CONF_CONNECTED
import esphome.config_validation as cv
from esphome.const import DEVICE_CLASS_CONNECTIVITY, ENTITY_CATEGORY_DIAGNOSTIC
from esphome.types import ConfigType
from . import CONF_ECOCOMFORT2_ID, ECOCOMFORT2_CLIENT_SCHEMA
CODEOWNERS = ["@gledian"]
DEPENDENCIES = ["ecocomfort2"]
CONF_BOOST = "boost"
CONFIG_SCHEMA = cv.All(
ECOCOMFORT2_CLIENT_SCHEMA.extend(
{
cv.Optional(CONF_CONNECTED): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_CONNECTIVITY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_BOOST): binary_sensor.binary_sensor_schema(),
}
),
cv.has_at_least_one_key(CONF_CONNECTED, CONF_BOOST),
)
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_ECOCOMFORT2_ID])
sensors = binary_sensor.sub_binary_sensors(config)
await sensors(CONF_CONNECTED, hub.set_connected_binary_sensor)
await sensors(CONF_BOOST, hub.set_boost_binary_sensor)
@@ -3,22 +3,39 @@
#include "ecocomfort2_hub.h"
#include "esphome/core/log.h"
#include <cstdio>
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::setup() {
#ifdef USE_BINARY_SENSOR
if (this->connected_binary_sensor_ != nullptr)
this->connected_binary_sensor_->publish_initial_state(false);
#endif
#ifdef USE_TIME
// The clock is written after each time-source sync
if (this->time_ != nullptr)
this->time_->add_on_time_sync_callback([this]() { this->sync_clock_(); });
#endif
}
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);
// The GATT client runs the requests one after the other
this->read_char_(this->char_handle_state_);
this->read_char_(this->char_handle_oper_);
}
void Ecocomfort2Hub::read_char_(uint16_t handle) {
auto status = esp_ble_gattc_read_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), handle,
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);
ESP_LOGW(TAG, "[%s] Read request for handle 0x%x failed, status=%d", this->parent_->address_str(), handle, status);
}
}
@@ -28,6 +45,22 @@ void Ecocomfort2Hub::dump_config() {
" Address: %s",
this->parent_->address_str());
LOG_UPDATE_INTERVAL(this);
#ifdef USE_TIME
ESP_LOGCONFIG(TAG, " Clock sync: %s", YESNO(this->time_ != nullptr));
#endif
#ifdef USE_SENSOR
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
LOG_SENSOR(" ", "VOC", this->voc_sensor_);
LOG_SENSOR(" ", "Direction", this->direction_sensor_);
#endif
#ifdef USE_BINARY_SENSOR
LOG_BINARY_SENSOR(" ", "Connected", this->connected_binary_sensor_);
LOG_BINARY_SENSOR(" ", "Boost", this->boost_binary_sensor_);
#endif
#ifdef USE_TEXT_SENSOR
LOG_TEXT_SENSOR(" ", "Firmware", this->firmware_text_sensor_);
#endif
}
void Ecocomfort2Hub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
@@ -39,17 +72,34 @@ void Ecocomfort2Hub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if
this->cancel_timeout(READBACK_TIMEOUT_ID);
this->status_set_warning();
this->char_handle_oper_ = 0;
this->char_handle_state_ = 0;
this->char_handle_info_ = 0;
this->char_handle_clock_ = 0;
this->set_connected_(false);
this->publish_unknown_();
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());
const auto service_uuid = espbt::ESPBTUUID::from_raw("f4b827c3-e660-4bc8-bdf6-3c8e9b845e0d");
const auto oper_uuid = espbt::ESPBTUUID::from_raw("b9d6f678-bc0d-4a73-90c8-60b0f07301f1");
const auto state_uuid = espbt::ESPBTUUID::from_raw("438d3433-7e5a-459a-a8e4-66343fad2bb0");
const auto info_uuid = espbt::ESPBTUUID::from_raw("f5f56229-dd4f-480f-a829-9189269d8b37");
const auto clock_uuid = espbt::ESPBTUUID::from_raw("82788997-49e4-4533-b949-7ed433678044");
// Keep the handles, not the characteristics: ble_client frees its services once all nodes are established.
auto *oper = this->parent_->get_characteristic(service_uuid, oper_uuid);
auto *state = this->parent_->get_characteristic(service_uuid, state_uuid);
if (oper == nullptr || state == nullptr) {
ESP_LOGW(TAG, "[%s] C_OPER or C_STATE characteristic not found, disconnecting", this->parent_->address_str());
this->parent_->disconnect();
break;
}
this->char_handle_oper_ = chr->handle;
this->char_handle_oper_ = oper->handle;
this->char_handle_state_ = state->handle;
// The version and clock characteristics only feed optional features, so a unit without them still works
auto *info = this->parent_->get_characteristic(service_uuid, info_uuid);
this->char_handle_info_ = info != nullptr ? info->handle : 0;
auto *clock = this->parent_->get_characteristic(service_uuid, clock_uuid);
this->char_handle_clock_ = clock != nullptr ? clock->handle : 0;
this->node_state = espbt::ClientState::ESTABLISHED;
this->status_set_warning();
@@ -71,31 +121,22 @@ void Ecocomfort2Hub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if
break;
}
case ESP_GATTC_READ_CHAR_EVT: {
if (param->read.conn_id != this->parent_->get_conn_id() || param->read.handle != this->char_handle_oper_) {
if (param->read.conn_id != this->parent_->get_conn_id()) {
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();
const uint16_t handle = param->read.handle;
if (handle == this->char_handle_oper_) {
if (this->read_ok_(param))
this->on_oper_(param->read.value, param->read.value_len);
} else if (handle == this->char_handle_state_) {
if (this->read_ok_(param))
this->on_state_(param->read.value, param->read.value_len);
} else if (handle == this->char_handle_info_) {
if (this->read_ok_(param))
this->on_info_(param->read.value, param->read.value_len);
} else {
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:
@@ -103,6 +144,21 @@ void Ecocomfort2Hub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if
}
}
bool Ecocomfort2Hub::read_ok_(const esp_ble_gattc_cb_param_t *param) {
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();
return false;
}
if (param->read.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "[%s] Read of handle 0x%x failed, status=%d", this->parent_->address_str(), param->read.handle,
param->read.status);
return false;
}
return true;
}
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) {
@@ -122,9 +178,122 @@ 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->set_connected_(true);
#ifdef USE_TIME
if (this->time_ != nullptr)
this->sync_clock_();
#endif
#ifdef USE_TEXT_SENSOR
if (this->firmware_text_sensor_ != nullptr && this->char_handle_info_ != 0)
this->read_char_(this->char_handle_info_);
#endif
this->update();
}
void Ecocomfort2Hub::set_connected_(bool connected) {
#ifdef USE_BINARY_SENSOR
if (this->connected_binary_sensor_ != nullptr)
this->connected_binary_sensor_->publish_state(connected);
#endif
}
// Readings from a unit that is out of reach would otherwise look current for hours
void Ecocomfort2Hub::publish_unknown_() {
#ifdef USE_SENSOR
for (auto *sensor : {this->temperature_sensor_, this->humidity_sensor_, this->voc_sensor_, this->direction_sensor_}) {
if (sensor != nullptr)
sensor->publish_state(NAN);
}
#endif
}
// C_OPER: mode byte, speed/flags byte
void Ecocomfort2Hub::on_oper_(const uint8_t *data, uint16_t len) {
if (len < 2) {
ESP_LOGW(TAG, "[%s] C_OPER too short: %u bytes", this->parent_->address_str(), len);
return;
}
this->mode_ = data[0];
this->speed_ = data[1];
ESP_LOGD(TAG, "[%s] C_OPER: mode=0x%02x, speed=0x%02x", this->parent_->address_str(), this->mode_, this->speed_);
#ifdef USE_BINARY_SENSOR
if (this->boost_binary_sensor_ != nullptr)
this->boost_binary_sensor_->publish_state((this->speed_ & FLAG_BOOST) != 0);
#endif
#ifdef ECOCOMFORT2_CHILD_COUNT
for (auto *child : this->children_) {
child->on_status();
}
#endif
}
// C_STATE: bytes 3-4 temperature and 5-6 humidity in hundredths, 7-8 VOC in ppb, byte 2 the air flow direction
void Ecocomfort2Hub::on_state_(const uint8_t *data, uint16_t len) {
if (len < 9) {
ESP_LOGW(TAG, "[%s] C_STATE too short: %u bytes", this->parent_->address_str(), len);
return;
}
const float temperature = static_cast<int16_t>(encode_uint16(data[3], data[4])) / 100.0f;
const float humidity = encode_uint16(data[5], data[6]) / 100.0f;
const uint16_t voc = encode_uint16(data[7], data[8]);
ESP_LOGD(TAG, "[%s] C_STATE: %.2f °C, %.2f %%, %u ppb, direction=%u", this->parent_->address_str(), temperature,
humidity, voc, data[2]);
#ifdef USE_SENSOR
if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(temperature);
if (this->humidity_sensor_ != nullptr)
this->humidity_sensor_->publish_state(humidity);
if (this->voc_sensor_ != nullptr)
this->voc_sensor_->publish_state(voc);
if (this->direction_sensor_ != nullptr)
this->direction_sensor_->publish_state(data[2]);
#endif
}
// C_INFO: firmware version packed as 4-bit major, 6-bit minor and 6-bit patch
void Ecocomfort2Hub::on_info_(const uint8_t *data, uint16_t len) {
if (len < 2) {
ESP_LOGW(TAG, "[%s] C_INFO too short: %u bytes", this->parent_->address_str(), len);
return;
}
const uint16_t field = encode_uint16(data[0] & 0x0F, data[1]);
char version[12];
snprintf(version, sizeof(version), "%u.%u.%u", data[0] >> 4, field >> 6, field & 0x3F);
ESP_LOGD(TAG, "[%s] Firmware %s", this->parent_->address_str(), version);
#ifdef USE_TEXT_SENSOR
if (this->firmware_text_sensor_ != nullptr)
this->firmware_text_sensor_->publish_state(version);
#endif
}
#ifdef USE_TIME
// C_CLOCK: hour, minute, second, weekday with Monday as 0, day, month, year as two bytes
void Ecocomfort2Hub::sync_clock_() {
if (!this->is_ready() || this->char_handle_clock_ == 0)
return;
const ESPTime now = this->time_->now();
if (!now.is_valid())
return;
// ESPTime counts Sunday=1 .. Saturday=7, the unit Monday=0 .. Sunday=6
const uint8_t weekday = (now.day_of_week + 5) % 7;
uint8_t data[8]{now.hour,
now.minute,
now.second,
weekday,
now.day_of_month,
now.month,
static_cast<uint8_t>(now.year >> 8),
static_cast<uint8_t>(now.year)};
ESP_LOGD(TAG, "[%s] Syncing clock to %02u:%02u:%02u", this->parent_->address_str(), now.hour, now.minute, now.second);
auto status =
esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_clock_,
sizeof(data), data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status != ESP_GATT_OK) {
ESP_LOGW(TAG, "[%s] C_CLOCK write failed, status=%d", this->parent_->address_str(), status);
}
}
#endif
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());
@@ -139,7 +308,7 @@ void Ecocomfort2Hub::write_oper(uint8_t mode, uint8_t speed) {
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(); });
this->set_timeout(READBACK_TIMEOUT_ID, READBACK_DELAY_MS, [this]() { this->read_char_(this->char_handle_oper_); });
}
#ifdef ECOCOMFORT2_CHILD_COUNT
@@ -8,6 +8,19 @@
#include "esphome/core/helpers.h"
#include "ecocomfort2_child.h"
#ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h"
#endif
#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
#endif
#ifdef USE_TEXT_SENSOR
#include "esphome/components/text_sensor/text_sensor.h"
#endif
#ifdef USE_TIME
#include "esphome/components/time/real_time_clock.h"
#endif
namespace esphome::ecocomfort2 {
namespace espbt = esphome::esp32_ble_tracker;
@@ -33,6 +46,7 @@ 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 setup() override;
void update() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::BLUETOOTH; }
@@ -44,6 +58,23 @@ class Ecocomfort2Hub : public esphome::ble_client::BLEClientNode, public Polling
#ifdef ECOCOMFORT2_CHILD_COUNT
void register_child(Ecocomfort2Client *obj);
#endif
#ifdef USE_SENSOR
void set_temperature_sensor(sensor::Sensor *s) { this->temperature_sensor_ = s; }
void set_humidity_sensor(sensor::Sensor *s) { this->humidity_sensor_ = s; }
void set_voc_sensor(sensor::Sensor *s) { this->voc_sensor_ = s; }
void set_direction_sensor(sensor::Sensor *s) { this->direction_sensor_ = s; }
#endif
#ifdef USE_BINARY_SENSOR
void set_connected_binary_sensor(binary_sensor::BinarySensor *s) { this->connected_binary_sensor_ = s; }
void set_boost_binary_sensor(binary_sensor::BinarySensor *s) { this->boost_binary_sensor_ = s; }
#endif
#ifdef USE_TEXT_SENSOR
void set_firmware_text_sensor(text_sensor::TextSensor *s) { this->firmware_text_sensor_ = s; }
#endif
#ifdef USE_TIME
/// Keeps the unit's clock in step with this time source while connected.
void set_time(time::RealTimeClock *time) { this->time_ = time; }
#endif
/// True once the link is established and paired.
bool is_ready() const { return this->node_state == espbt::ClientState::ESTABLISHED && this->parent_->is_paired(); }
@@ -62,11 +93,40 @@ class Ecocomfort2Hub : public esphome::ble_client::BLEClientNode, public Polling
static constexpr uint32_t READBACK_DELAY_MS = 300;
void on_paired_();
void set_connected_(bool connected);
void publish_unknown_();
void read_char_(uint16_t handle);
bool read_ok_(const esp_ble_gattc_cb_param_t *param);
void on_oper_(const uint8_t *data, uint16_t len);
void on_state_(const uint8_t *data, uint16_t len);
void on_info_(const uint8_t *data, uint16_t len);
#ifdef USE_TIME
void sync_clock_();
#endif
#ifdef ECOCOMFORT2_CHILD_COUNT
StaticVector<Ecocomfort2Client *, ECOCOMFORT2_CHILD_COUNT> children_;
#endif
#ifdef USE_SENSOR
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
sensor::Sensor *voc_sensor_{nullptr};
sensor::Sensor *direction_sensor_{nullptr};
#endif
#ifdef USE_BINARY_SENSOR
binary_sensor::BinarySensor *connected_binary_sensor_{nullptr};
binary_sensor::BinarySensor *boost_binary_sensor_{nullptr};
#endif
#ifdef USE_TEXT_SENSOR
text_sensor::TextSensor *firmware_text_sensor_{nullptr};
#endif
#ifdef USE_TIME
time::RealTimeClock *time_{nullptr};
#endif
uint16_t char_handle_oper_{0};
uint16_t char_handle_state_{0};
uint16_t char_handle_info_{0};
uint16_t char_handle_clock_{0};
uint8_t mode_{OPER_OFF};
uint8_t speed_{SPEED_OFF};
};
+62
View File
@@ -0,0 +1,62 @@
import esphome.codegen as cg
from esphome.components import sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_DIRECTION,
CONF_HUMIDITY,
CONF_TEMPERATURE,
CONF_VOC,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PARTS_PER_BILLION,
UNIT_PERCENT,
)
from esphome.types import ConfigType
from . import CONF_ECOCOMFORT2_ID, ECOCOMFORT2_CLIENT_SCHEMA
CODEOWNERS = ["@gledian"]
DEPENDENCIES = ["ecocomfort2"]
CONFIG_SCHEMA = cv.All(
ECOCOMFORT2_CLIENT_SCHEMA.extend(
{
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_VOC): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_BILLION,
accuracy_decimals=0,
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_DIRECTION): sensor.sensor_schema(
accuracy_decimals=0,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
),
cv.has_at_least_one_key(CONF_TEMPERATURE, CONF_HUMIDITY, CONF_VOC, CONF_DIRECTION),
)
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_ECOCOMFORT2_ID])
sensors = sensor.sub_sensors(config)
await sensors(CONF_TEMPERATURE, hub.set_temperature_sensor)
await sensors(CONF_HUMIDITY, hub.set_humidity_sensor)
await sensors(CONF_VOC, hub.set_voc_sensor)
await sensors(CONF_DIRECTION, hub.set_direction_sensor)
@@ -0,0 +1,24 @@
import esphome.codegen as cg
from esphome.components import text_sensor
import esphome.config_validation as cv
from esphome.const import CONF_VERSION, ENTITY_CATEGORY_DIAGNOSTIC
from esphome.types import ConfigType
from . import CONF_ECOCOMFORT2_ID, ECOCOMFORT2_CLIENT_SCHEMA
CODEOWNERS = ["@gledian"]
DEPENDENCIES = ["ecocomfort2"]
CONFIG_SCHEMA = ECOCOMFORT2_CLIENT_SCHEMA.extend(
{
cv.Required(CONF_VERSION): text_sensor.text_sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_ECOCOMFORT2_ID])
sensors = text_sensor.sub_text_sensors(config)
await sensors(CONF_VERSION, hub.set_firmware_text_sensor)
+35
View File
@@ -3,6 +3,14 @@ esp32_ble:
auth_req_mode: sc_bond
max_key_size: 16
wifi:
ssid: MySSID
password: password1
time:
- platform: sntp
id: sntp_time
ble_client:
- mac_address: 01:02:03:04:05:06
id: ecocomfort2_ble_1
@@ -12,6 +20,7 @@ ble_client:
ecocomfort2:
- id: ecocomfort2_hub_1
ble_client_id: ecocomfort2_ble_1
time_id: sntp_time
- id: ecocomfort2_hub_2
ble_client_id: ecocomfort2_ble_2
@@ -22,3 +31,29 @@ fan:
- platform: ecocomfort2
name: Ecocomfort2 Fan 2
ecocomfort2_id: ecocomfort2_hub_2
sensor:
- platform: ecocomfort2
ecocomfort2_id: ecocomfort2_hub_1
temperature:
name: Ecocomfort2 Temperature
humidity:
name: Ecocomfort2 Humidity
voc:
name: Ecocomfort2 VOC
direction:
name: Ecocomfort2 Direction
binary_sensor:
- platform: ecocomfort2
ecocomfort2_id: ecocomfort2_hub_1
connected:
name: Ecocomfort2 Connected
boost:
name: Ecocomfort2 Boost
text_sensor:
- platform: ecocomfort2
ecocomfort2_id: ecocomfort2_hub_2
version:
name: Ecocomfort2 Firmware