mirror of
https://github.com/esphome/esphome.git
synced 2026-10-14 02:02:51 +00:00
[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:
co-authored by
gledian
Claude Opus 4.6
pre-commit-ci-lite[bot]
J. Nick Koston
parent
8aab968136
commit
4cae63bbaa
@@ -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};
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user