mirror of https://github.com/esphome/esphome.git
Merge 276dfed871
into c7c0d97a5e
This commit is contained in:
commit
47448f1cf2
|
@ -135,6 +135,7 @@ esphome/components/fs3000/* @kahrendt
|
|||
esphome/components/ft5x06/* @clydebarrow
|
||||
esphome/components/ft63x6/* @gpambrozio
|
||||
esphome/components/gcja5/* @gcormier
|
||||
esphome/components/generic_humidifier/* @Jaco1990
|
||||
esphome/components/globals/* @esphome/core
|
||||
esphome/components/gp8403/* @jesserockz
|
||||
esphome/components/gpio/* @esphome/core
|
||||
|
@ -161,6 +162,7 @@ esphome/components/host/* @esphome/core
|
|||
esphome/components/hrxl_maxsonar_wr/* @netmikey
|
||||
esphome/components/hte501/* @Stock-M
|
||||
esphome/components/htu31d/* @betterengineering
|
||||
esphome/components/humidifier/* @Jaco1990
|
||||
esphome/components/hydreon_rgxx/* @functionpointer
|
||||
esphome/components/hyt271/* @Philippe12
|
||||
esphome/components/i2c/* @esphome/core
|
||||
|
|
|
@ -38,6 +38,7 @@ service APIConnection {
|
|||
rpc switch_command (SwitchCommandRequest) returns (void) {}
|
||||
rpc camera_image (CameraImageRequest) returns (void) {}
|
||||
rpc climate_command (ClimateCommandRequest) returns (void) {}
|
||||
rpc humidifier_command (HumidifierCommandRequest) returns (void) {}
|
||||
rpc number_command (NumberCommandRequest) returns (void) {}
|
||||
rpc text_command (TextCommandRequest) returns (void) {}
|
||||
rpc select_command (SelectCommandRequest) returns (void) {}
|
||||
|
@ -923,6 +924,80 @@ message ClimateCommandRequest {
|
|||
float target_humidity = 23;
|
||||
}
|
||||
|
||||
// ==================== HUMIDIFIER ====================
|
||||
enum HumidifierMode {
|
||||
HUMIDIFIER_MODE_OFF = 0;
|
||||
HUMIDIFIER_MODE_NORMAL = 1;
|
||||
HUMIDIFIER_MODE_ECO = 2;
|
||||
HUMIDIFIER_MODE_AWAY = 3;
|
||||
HUMIDIFIER_MODE_BOOST = 4;
|
||||
HUMIDIFIER_MODE_COMFORT = 5;
|
||||
HUMIDIFIER_MODE_HOME = 6;
|
||||
HUMIDIFIER_MODE_SLEEP = 7;
|
||||
HUMIDIFIER_MODE_AUTO = 8;
|
||||
HUMIDIFIER_MODE_BABY = 9;
|
||||
}
|
||||
enum HumidifierAction {
|
||||
HUMIDIFIER_ACTION_OFF = 0;
|
||||
// values same as mode for readability
|
||||
HUMIDIFIER_ACTION_NORMAL = 2;
|
||||
HUMIDIFIER_ACTION_ECO = 3;
|
||||
HUMIDIFIER_ACTION_AWAY = 4;
|
||||
HUMIDIFIER_ACTION_BOOST = 5;
|
||||
HUMIDIFIER_ACTION_COMFORT = 6;
|
||||
HUMIDIFIER_ACTION_HOME = 7;
|
||||
HUMIDIFIER_ACTION_SLEEP = 8;
|
||||
HUMIDIFIER_ACTION_AUTO = 9;
|
||||
HUMIDIFIER_ACTION_BABY = 10;
|
||||
}
|
||||
|
||||
message ListEntitiesHumidifierResponse {
|
||||
option (id) = 107;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_HUMIDIFIER";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
bool supports_current_humidity = 5;
|
||||
bool supports_target_humidity= 6;
|
||||
repeated HumidifierMode supported_modes = 7;
|
||||
float visual_min_humidity = 8;
|
||||
float visual_max_humidity = 9;
|
||||
float visual_target_humidity_step = 10;
|
||||
bool supports_action = 11;
|
||||
bool disabled_by_default = 13;
|
||||
string icon = 14;
|
||||
EntityCategory entity_category = 15;
|
||||
float visual_current_humidity_step = 16;
|
||||
}
|
||||
message HumidifierStateResponse {
|
||||
option (id) = 108;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_HUMIDIFIER";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
HumidifierMode mode = 2;
|
||||
float current_humidity = 3;
|
||||
float target_humidity = 4;
|
||||
HumidifierAction action = 5;
|
||||
}
|
||||
message HumidifierCommandRequest {
|
||||
option (id) = 109;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_HUMIDIFIER";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
bool has_mode = 2;
|
||||
HumidifierMode mode = 3;
|
||||
bool has_target_humidity = 4;
|
||||
float target_humidity = 5;
|
||||
}
|
||||
|
||||
// ==================== NUMBER ====================
|
||||
enum NumberMode {
|
||||
NUMBER_MODE_AUTO = 0;
|
||||
|
|
|
@ -656,6 +656,65 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_HUMIDIFIER
|
||||
bool APIConnection::send_humidifier_state(humidifier::Humidifier *humidifier) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto traits = humidifier->get_traits();
|
||||
HumidifierStateResponse resp{};
|
||||
resp.key = humidifier->get_object_id_hash();
|
||||
resp.mode = static_cast<enums::HumidifierMode>(humidifier->mode);
|
||||
resp.action = static_cast<enums::HumidifierAction>(humidifier->action);
|
||||
if (traits.get_supports_current_humidity())
|
||||
resp.current_humidity = humidifier->current_humidity;
|
||||
if (traits.get_supports_target_humidity()) {
|
||||
resp.target_humidity = humidifier->target_humidity;
|
||||
}
|
||||
return this->send_humidifier_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_humidifier_info(humidifier::Humidifier *humidifier) {
|
||||
auto traits = humidifier->get_traits();
|
||||
ListEntitiesHumidifierResponse msg;
|
||||
msg.key = humidifier->get_object_id_hash();
|
||||
msg.object_id = humidifier->get_object_id();
|
||||
if (humidifier->has_own_name())
|
||||
msg.name = humidifier->get_name();
|
||||
msg.unique_id = get_default_unique_id("humidifier", humidifier);
|
||||
|
||||
msg.disabled_by_default = humidifier->is_disabled_by_default();
|
||||
msg.icon = humidifier->get_icon();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(humidifier->get_entity_category());
|
||||
|
||||
msg.supports_current_humidity = traits.get_supports_current_humidity();
|
||||
msg.supports_target_humidity = traits.get_supports_target_humidity();
|
||||
|
||||
for (auto mode : traits.get_supported_modes())
|
||||
msg.supported_modes.push_back(static_cast<enums::HumidifierMode>(mode));
|
||||
|
||||
msg.visual_min_humidity = traits.get_visual_min_humidity();
|
||||
msg.visual_max_humidity = traits.get_visual_max_humidity();
|
||||
msg.visual_target_humidity_step = traits.get_visual_target_humidity_step();
|
||||
msg.visual_current_humidity_step = traits.get_visual_current_humidity_step();
|
||||
|
||||
msg.supports_action = traits.get_supports_action();
|
||||
|
||||
return this->send_list_entities_humidifier_response(msg);
|
||||
}
|
||||
void APIConnection::humidifier_command(const HumidifierCommandRequest &msg) {
|
||||
humidifier::Humidifier *humidifier = App.get_humidifier_by_key(msg.key);
|
||||
if (humidifier == nullptr)
|
||||
return;
|
||||
|
||||
auto call = humidifier->make_call();
|
||||
if (msg.has_mode)
|
||||
call.set_mode(static_cast<humidifier::HumidifierMode>(msg.mode));
|
||||
if (msg.has_target_humidity)
|
||||
call.set_target_humidity(msg.target_humidity);
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
bool APIConnection::send_number_state(number::Number *number, float state) {
|
||||
if (!this->state_subscription_)
|
||||
|
|
|
@ -67,6 +67,11 @@ class APIConnection : public APIServerConnection {
|
|||
bool send_climate_info(climate::Climate *climate);
|
||||
void climate_command(const ClimateCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_HUMIDIFIER
|
||||
bool send_humidifier_state(humidifier::Humidifier *humidifier);
|
||||
bool send_humidifier_info(humidifier::Humidifier *humidifier);
|
||||
void humidifier_command(const HumidifierCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
bool send_number_state(number::Number *number, float state);
|
||||
bool send_number_info(number::Number *number);
|
||||
|
|
|
@ -305,6 +305,62 @@ template<> const char *proto_enum_to_string<enums::ClimatePreset>(enums::Climate
|
|||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::HumidifierMode>(enums::HumidifierMode value) {
|
||||
switch (value) {
|
||||
case enums::HUMIDIFIER_MODE_OFF:
|
||||
return "HUMIDIFIER_MODE_OFF";
|
||||
case enums::HUMIDIFIER_MODE_NORMAL:
|
||||
return "HUMIDIFIER_MODE_NORMAL";
|
||||
case enums::HUMIDIFIER_MODE_ECO:
|
||||
return "HUMIDIFIER_MODE_ECO";
|
||||
case enums::HUMIDIFIER_MODE_AWAY:
|
||||
return "HUMIDIFIER_MODE_AWAY";
|
||||
case enums::HUMIDIFIER_MODE_BOOST:
|
||||
return "HUMIDIFIER_MODE_BOOST";
|
||||
case enums::HUMIDIFIER_MODE_COMFORT:
|
||||
return "HUMIDIFIER_MODE_COMFORT";
|
||||
case enums::HUMIDIFIER_MODE_HOME:
|
||||
return "HUMIDIFIER_MODE_HOME";
|
||||
case enums::HUMIDIFIER_MODE_SLEEP:
|
||||
return "HUMIDIFIER_MODE_SLEEP";
|
||||
case enums::HUMIDIFIER_MODE_AUTO:
|
||||
return "HUMIDIFIER_MODE_AUTO";
|
||||
case enums::HUMIDIFIER_MODE_BABY:
|
||||
return "HUMIDIFIER_MODE_BABY";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::HumidifierAction>(enums::HumidifierAction value) {
|
||||
switch (value) {
|
||||
case enums::HUMIDIFIER_ACTION_OFF:
|
||||
return "HUMIDIFIER_ACTION_OFF";
|
||||
case enums::HUMIDIFIER_ACTION_NORMAL:
|
||||
return "HUMIDIFIER_ACTION_NORMAL";
|
||||
case enums::HUMIDIFIER_ACTION_ECO:
|
||||
return "HUMIDIFIER_ACTION_ECO";
|
||||
case enums::HUMIDIFIER_ACTION_AWAY:
|
||||
return "HUMIDIFIER_ACTION_AWAY";
|
||||
case enums::HUMIDIFIER_ACTION_BOOST:
|
||||
return "HUMIDIFIER_ACTION_BOOST";
|
||||
case enums::HUMIDIFIER_ACTION_COMFORT:
|
||||
return "HUMIDIFIER_ACTION_COMFORT";
|
||||
case enums::HUMIDIFIER_ACTION_HOME:
|
||||
return "HUMIDIFIER_ACTION_HOME";
|
||||
case enums::HUMIDIFIER_ACTION_SLEEP:
|
||||
return "HUMIDIFIER_ACTION_SLEEP";
|
||||
case enums::HUMIDIFIER_ACTION_AUTO:
|
||||
return "HUMIDIFIER_ACTION_AUTO";
|
||||
case enums::HUMIDIFIER_ACTION_BABY:
|
||||
return "HUMIDIFIER_ACTION_BABY";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::NumberMode>(enums::NumberMode value) {
|
||||
switch (value) {
|
||||
case enums::NUMBER_MODE_AUTO:
|
||||
|
@ -4303,6 +4359,311 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
|
|||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool ListEntitiesHumidifierResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 5: {
|
||||
this->supports_current_humidity = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 6: {
|
||||
this->supports_target_humidity = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 7: {
|
||||
this->supported_modes.push_back(value.as_enum<enums::HumidifierMode>());
|
||||
return true;
|
||||
}
|
||||
case 11: {
|
||||
this->supports_action = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 13: {
|
||||
this->disabled_by_default = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 15: {
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool ListEntitiesHumidifierResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->object_id = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->name = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 4: {
|
||||
this->unique_id = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 14: {
|
||||
this->icon = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool ListEntitiesHumidifierResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->key = value.as_fixed32();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->visual_min_humidity = value.as_float();
|
||||
return true;
|
||||
}
|
||||
case 9: {
|
||||
this->visual_max_humidity = value.as_float();
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->visual_target_humidity_step = value.as_float();
|
||||
return true;
|
||||
}
|
||||
case 16: {
|
||||
this->visual_current_humidity_step = value.as_float();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void ListEntitiesHumidifierResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
buffer.encode_string(4, this->unique_id);
|
||||
buffer.encode_bool(5, this->supports_current_humidity);
|
||||
buffer.encode_bool(6, this->supports_target_humidity);
|
||||
for (auto &it : this->supported_modes) {
|
||||
buffer.encode_enum<enums::HumidifierMode>(7, it, true);
|
||||
}
|
||||
buffer.encode_float(8, this->visual_min_humidity);
|
||||
buffer.encode_float(9, this->visual_max_humidity);
|
||||
buffer.encode_float(10, this->visual_target_humidity_step);
|
||||
buffer.encode_bool(11, this->supports_action);
|
||||
buffer.encode_bool(13, this->disabled_by_default);
|
||||
buffer.encode_string(14, this->icon);
|
||||
buffer.encode_enum<enums::EntityCategory>(15, this->entity_category);
|
||||
buffer.encode_float(16, this->visual_current_humidity_step);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesHumidifierResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("ListEntitiesHumidifierResponse {\n");
|
||||
out.append(" object_id: ");
|
||||
out.append("'").append(this->object_id).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" key: ");
|
||||
sprintf(buffer, "%" PRIu32, this->key);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" name: ");
|
||||
out.append("'").append(this->name).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" unique_id: ");
|
||||
out.append("'").append(this->unique_id).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" supports_current_humidity: ");
|
||||
out.append(YESNO(this->supports_current_humidity));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" supports_target_humidity: ");
|
||||
out.append(YESNO(this->supports_target_humidity));
|
||||
out.append("\n");
|
||||
|
||||
for (const auto &it : this->supported_modes) {
|
||||
out.append(" supported_modes: ");
|
||||
out.append(proto_enum_to_string<enums::HumidifierMode>(it));
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
out.append(" visual_min_humidity: ");
|
||||
sprintf(buffer, "%g", this->visual_min_humidity);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" visual_max_humidity: ");
|
||||
sprintf(buffer, "%g", this->visual_max_humidity);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" visual_target_humidity_step: ");
|
||||
sprintf(buffer, "%g", this->visual_target_humidity_step);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" supports_action: ");
|
||||
out.append(YESNO(this->supports_action));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" disabled_by_default: ");
|
||||
out.append(YESNO(this->disabled_by_default));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" icon: ");
|
||||
out.append("'").append(this->icon).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" visual_current_humidity_step: ");
|
||||
sprintf(buffer, "%g", this->visual_current_humidity_step);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool HumidifierStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->mode = value.as_enum<enums::HumidifierMode>();
|
||||
return true;
|
||||
}
|
||||
case 5: {
|
||||
this->action = value.as_enum<enums::HumidifierAction>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool HumidifierStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->key = value.as_fixed32();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->current_humidity = value.as_float();
|
||||
return true;
|
||||
}
|
||||
case 4: {
|
||||
this->target_humidity = value.as_float();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void HumidifierStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_enum<enums::HumidifierMode>(2, this->mode);
|
||||
buffer.encode_float(3, this->current_humidity);
|
||||
buffer.encode_float(4, this->target_humidity);
|
||||
buffer.encode_enum<enums::HumidifierAction>(5, this->action);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void HumidifierStateResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("HumidifierStateResponse {\n");
|
||||
out.append(" key: ");
|
||||
sprintf(buffer, "%" PRIu32, this->key);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" mode: ");
|
||||
out.append(proto_enum_to_string<enums::HumidifierMode>(this->mode));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" current_humidity: ");
|
||||
sprintf(buffer, "%g", this->current_humidity);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" target_humidity: ");
|
||||
sprintf(buffer, "%g", this->target_humidity);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" action: ");
|
||||
out.append(proto_enum_to_string<enums::HumidifierAction>(this->action));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool HumidifierCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->has_mode = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->mode = value.as_enum<enums::HumidifierMode>();
|
||||
return true;
|
||||
}
|
||||
case 4: {
|
||||
this->has_target_humidity = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool HumidifierCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->key = value.as_fixed32();
|
||||
return true;
|
||||
}
|
||||
case 5: {
|
||||
this->target_humidity = value.as_float();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void HumidifierCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_bool(2, this->has_mode);
|
||||
buffer.encode_enum<enums::HumidifierMode>(3, this->mode);
|
||||
buffer.encode_bool(4, this->has_target_humidity);
|
||||
buffer.encode_float(5, this->target_humidity);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void HumidifierCommandRequest::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("HumidifierCommandRequest {\n");
|
||||
out.append(" key: ");
|
||||
sprintf(buffer, "%" PRIu32, this->key);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_mode: ");
|
||||
out.append(YESNO(this->has_mode));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" mode: ");
|
||||
out.append(proto_enum_to_string<enums::HumidifierMode>(this->mode));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_target_humidity: ");
|
||||
out.append(YESNO(this->has_target_humidity));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" target_humidity: ");
|
||||
sprintf(buffer, "%g", this->target_humidity);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 9: {
|
||||
|
|
|
@ -125,6 +125,30 @@ enum ClimatePreset : uint32_t {
|
|||
CLIMATE_PRESET_SLEEP = 6,
|
||||
CLIMATE_PRESET_ACTIVITY = 7,
|
||||
};
|
||||
enum HumidifierMode : uint32_t {
|
||||
HUMIDIFIER_MODE_OFF = 0,
|
||||
HUMIDIFIER_MODE_NORMAL = 1,
|
||||
HUMIDIFIER_MODE_ECO = 2,
|
||||
HUMIDIFIER_MODE_AWAY = 3,
|
||||
HUMIDIFIER_MODE_BOOST = 4,
|
||||
HUMIDIFIER_MODE_COMFORT = 5,
|
||||
HUMIDIFIER_MODE_HOME = 6,
|
||||
HUMIDIFIER_MODE_SLEEP = 7,
|
||||
HUMIDIFIER_MODE_AUTO = 8,
|
||||
HUMIDIFIER_MODE_BABY = 9,
|
||||
};
|
||||
enum HumidifierAction : uint32_t {
|
||||
HUMIDIFIER_ACTION_OFF = 0,
|
||||
HUMIDIFIER_ACTION_NORMAL = 2,
|
||||
HUMIDIFIER_ACTION_ECO = 3,
|
||||
HUMIDIFIER_ACTION_AWAY = 4,
|
||||
HUMIDIFIER_ACTION_BOOST = 5,
|
||||
HUMIDIFIER_ACTION_COMFORT = 6,
|
||||
HUMIDIFIER_ACTION_HOME = 7,
|
||||
HUMIDIFIER_ACTION_SLEEP = 8,
|
||||
HUMIDIFIER_ACTION_AUTO = 9,
|
||||
HUMIDIFIER_ACTION_BABY = 10,
|
||||
};
|
||||
enum NumberMode : uint32_t {
|
||||
NUMBER_MODE_AUTO = 0,
|
||||
NUMBER_MODE_BOX = 1,
|
||||
|
@ -1072,6 +1096,65 @@ class ClimateCommandRequest : public ProtoMessage {
|
|||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesHumidifierResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
bool supports_current_humidity{false};
|
||||
bool supports_target_humidity{false};
|
||||
std::vector<enums::HumidifierMode> supported_modes{};
|
||||
float visual_min_humidity{0.0f};
|
||||
float visual_max_humidity{0.0f};
|
||||
float visual_target_humidity_step{0.0f};
|
||||
bool supports_action{false};
|
||||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
enums::EntityCategory entity_category{};
|
||||
float visual_current_humidity_step{0.0f};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class HumidifierStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0};
|
||||
enums::HumidifierMode mode{};
|
||||
float current_humidity{0.0f};
|
||||
float target_humidity{0.0f};
|
||||
enums::HumidifierAction action{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class HumidifierCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0};
|
||||
bool has_mode{false};
|
||||
enums::HumidifierMode mode{};
|
||||
bool has_target_humidity{false};
|
||||
float target_humidity{0.0f};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesNumberResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{};
|
||||
|
|
|
@ -246,6 +246,24 @@ bool APIServerConnectionBase::send_climate_state_response(const ClimateStateResp
|
|||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
#endif
|
||||
#ifdef USE_HUMIDIFIER
|
||||
bool APIServerConnectionBase::send_list_entities_humidifier_response(const ListEntitiesHumidifierResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_list_entities_humidifier_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<ListEntitiesHumidifierResponse>(msg, 107);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_HUMIDIFIER
|
||||
bool APIServerConnectionBase::send_humidifier_state_response(const HumidifierStateResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_humidifier_state_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<HumidifierStateResponse>(msg, 108);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_HUMIDIFIER
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
bool APIServerConnectionBase::send_list_entities_number_response(const ListEntitiesNumberResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
|
@ -1071,6 +1089,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||
ESP_LOGVV(TAG, "on_voice_assistant_audio: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_voice_assistant_audio(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 109: {
|
||||
#ifdef USE_HUMIDIFIER
|
||||
HumidifierCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_humidifier_command_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_humidifier_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
@ -1291,6 +1320,19 @@ void APIServerConnection::on_climate_command_request(const ClimateCommandRequest
|
|||
this->climate_command(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_HUMIDIFIER
|
||||
void APIServerConnection::on_humidifier_command_request(const HumidifierCommandRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->humidifier_command(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
void APIServerConnection::on_number_command_request(const NumberCommandRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
|
|
|
@ -112,6 +112,15 @@ class APIServerConnectionBase : public ProtoService {
|
|||
#ifdef USE_CLIMATE
|
||||
virtual void on_climate_command_request(const ClimateCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_HUMIDIFIER
|
||||
bool send_list_entities_humidifier_response(const ListEntitiesHumidifierResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_HUMIDIFIER
|
||||
bool send_humidifier_state_response(const HumidifierStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_HUMIDIFIER
|
||||
virtual void on_humidifier_command_request(const HumidifierCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
bool send_list_entities_number_response(const ListEntitiesNumberResponse &msg);
|
||||
#endif
|
||||
|
@ -340,6 +349,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
|||
#ifdef USE_CLIMATE
|
||||
virtual void climate_command(const ClimateCommandRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_HUMIDIFIER
|
||||
virtual void humidifier_command(const HumidifierCommandRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
virtual void number_command(const NumberCommandRequest &msg) = 0;
|
||||
#endif
|
||||
|
@ -438,6 +450,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
|||
#ifdef USE_CLIMATE
|
||||
void on_climate_command_request(const ClimateCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_HUMIDIFIER
|
||||
void on_humidifier_command_request(const HumidifierCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
void on_number_command_request(const NumberCommandRequest &msg) override;
|
||||
#endif
|
||||
|
|
|
@ -246,6 +246,15 @@ void APIServer::on_climate_update(climate::Climate *obj) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_HUMIDIFIER
|
||||
void APIServer::on_humidifier_update(humidifier::Humidifier *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_humidifier_state(obj);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
void APIServer::on_number_update(number::Number *obj, float state) {
|
||||
if (obj->is_internal())
|
||||
|
|
|
@ -63,6 +63,9 @@ class APIServer : public Component, public Controller {
|
|||
#ifdef USE_CLIMATE
|
||||
void on_climate_update(climate::Climate *obj) override;
|
||||
#endif
|
||||
#ifdef USE_HUMIDIFIER
|
||||
void on_humidifier_update(humidifier::Humidifier *obj) override;
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
void on_number_update(number::Number *obj, float state) override;
|
||||
#endif
|
||||
|
|
|
@ -59,6 +59,12 @@ bool ListEntitiesIterator::on_camera(esp32_camera::ESP32Camera *camera) {
|
|||
bool ListEntitiesIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_info(climate); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_HUMIDIFIER
|
||||
bool ListEntitiesIterator::on_humidifier(humidifier::Humidifier *humidifier) {
|
||||
return this->client_->send_humidifier_info(humidifier);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
bool ListEntitiesIterator::on_number(number::Number *number) { return this->client_->send_number_info(number); }
|
||||
#endif
|
||||
|
|
|
@ -43,6 +43,9 @@ class ListEntitiesIterator : public ComponentIterator {
|
|||
#ifdef USE_CLIMATE
|
||||
bool on_climate(climate::Climate *climate) override;
|
||||
#endif
|
||||
#ifdef USE_HUMIDIFIER
|
||||
bool on_humidifier(humidifier::Humidifier *humidifier) override;
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
bool on_number(number::Number *number) override;
|
||||
#endif
|
||||
|
|
|
@ -37,6 +37,11 @@ bool InitialStateIterator::on_text_sensor(text_sensor::TextSensor *text_sensor)
|
|||
#ifdef USE_CLIMATE
|
||||
bool InitialStateIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_state(climate); }
|
||||
#endif
|
||||
#ifdef USE_HUMIDIFIER
|
||||
bool InitialStateIterator::on_humidifier(humidifier::Humidifier *humidifier) {
|
||||
return this->client_->send_humidifier_state(humidifier);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
bool InitialStateIterator::on_number(number::Number *number) {
|
||||
return this->client_->send_number_state(number, number->state);
|
||||
|
|
|
@ -40,6 +40,9 @@ class InitialStateIterator : public ComponentIterator {
|
|||
#ifdef USE_CLIMATE
|
||||
bool on_climate(climate::Climate *climate) override;
|
||||
#endif
|
||||
#ifdef USE_HUMIDIFIER
|
||||
bool on_humidifier(humidifier::Humidifier *humidifier) override;
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
bool on_number(number::Number *number) override;
|
||||
#endif
|
||||
|
|
|
@ -16,7 +16,9 @@ from esphome.const import (
|
|||
CONF_FAN_MODE_COMMAND_TOPIC,
|
||||
CONF_FAN_MODE_STATE_TOPIC,
|
||||
CONF_ID,
|
||||
CONF_MAX_HUMIDITY,
|
||||
CONF_MAX_TEMPERATURE,
|
||||
CONF_MIN_HUMIDITY,
|
||||
CONF_MIN_TEMPERATURE,
|
||||
CONF_MODE,
|
||||
CONF_MODE_COMMAND_TOPIC,
|
||||
|
@ -29,6 +31,7 @@ from esphome.const import (
|
|||
CONF_SWING_MODE,
|
||||
CONF_SWING_MODE_COMMAND_TOPIC,
|
||||
CONF_SWING_MODE_STATE_TOPIC,
|
||||
CONF_TARGET_HUMIDITY,
|
||||
CONF_TARGET_HUMIDITY_COMMAND_TOPIC,
|
||||
CONF_TARGET_HUMIDITY_STATE_TOPIC,
|
||||
CONF_TARGET_TEMPERATURE,
|
||||
|
@ -109,9 +112,6 @@ CLIMATE_SWING_MODES = {
|
|||
validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True)
|
||||
|
||||
CONF_CURRENT_TEMPERATURE = "current_temperature"
|
||||
CONF_MIN_HUMIDITY = "min_humidity"
|
||||
CONF_MAX_HUMIDITY = "max_humidity"
|
||||
CONF_TARGET_HUMIDITY = "target_humidity"
|
||||
|
||||
visual_temperature = cv.float_with_unit(
|
||||
"visual_temperature", "(°C|° C|°|C|° K|° K|K|°F|° F|F)?"
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ["@Jaco1990"]
|
|
@ -0,0 +1,173 @@
|
|||
#include "generic_humidifier.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace generic_humidifier {
|
||||
|
||||
static const char *const TAG = "generic_humdifier.humidifier";
|
||||
|
||||
void GenericHumidifier::setup() {
|
||||
this->sensor_->add_on_state_callback([this](float state) {
|
||||
this->current_humidity = state;
|
||||
// control may have changed, recompute
|
||||
this->compute_state_();
|
||||
// current humidity changed, publish state
|
||||
this->publish_state();
|
||||
});
|
||||
this->current_humidity = this->sensor_->state;
|
||||
|
||||
// restore set points
|
||||
auto restore = this->restore_state_();
|
||||
if (restore.has_value()) {
|
||||
restore->to_call(this).perform();
|
||||
} else {
|
||||
// restore from defaults,
|
||||
if (supports_normal_) {
|
||||
this->mode = humidifier::HUMIDIFIER_MODE_NORMAL;
|
||||
} else if (supports_eco_) {
|
||||
this->mode = humidifier::HUMIDIFIER_MODE_ECO;
|
||||
} else if (supports_boost_) {
|
||||
this->mode = humidifier::HUMIDIFIER_MODE_BOOST;
|
||||
} else if (supports_comfort_) {
|
||||
this->mode = humidifier::HUMIDIFIER_MODE_COMFORT;
|
||||
} else if (supports_sleep_) {
|
||||
this->mode = humidifier::HUMIDIFIER_MODE_SLEEP;
|
||||
} else if (supports_auto_) {
|
||||
this->mode = humidifier::HUMIDIFIER_MODE_AUTO;
|
||||
} else if (supports_baby_) {
|
||||
this->mode = humidifier::HUMIDIFIER_MODE_BABY;
|
||||
}
|
||||
}
|
||||
}
|
||||
void GenericHumidifier::control(const humidifier::HumidifierCall &call) {
|
||||
if (call.get_mode().has_value())
|
||||
this->mode = *call.get_mode();
|
||||
if (call.get_target_humidity().has_value())
|
||||
this->target_humidity = *call.get_target_humidity();
|
||||
|
||||
this->compute_state_();
|
||||
this->publish_state();
|
||||
}
|
||||
humidifier::HumidifierTraits GenericHumidifier::traits() {
|
||||
auto traits = humidifier::HumidifierTraits();
|
||||
traits.set_supports_current_humidity(true);
|
||||
traits.set_supported_modes({
|
||||
humidifier::HUMIDIFIER_MODE_OFF,
|
||||
});
|
||||
if (supports_normal_)
|
||||
traits.add_supported_mode(humidifier::HUMIDIFIER_MODE_NORMAL);
|
||||
if (supports_eco_)
|
||||
traits.add_supported_mode(humidifier::HUMIDIFIER_MODE_ECO);
|
||||
if (supports_boost_)
|
||||
traits.add_supported_mode(humidifier::HUMIDIFIER_MODE_BOOST);
|
||||
if (supports_comfort_)
|
||||
traits.add_supported_mode(humidifier::HUMIDIFIER_MODE_COMFORT);
|
||||
if (supports_sleep_)
|
||||
traits.add_supported_mode(humidifier::HUMIDIFIER_MODE_SLEEP);
|
||||
if (supports_auto_)
|
||||
traits.add_supported_mode(humidifier::HUMIDIFIER_MODE_AUTO);
|
||||
if (supports_baby_)
|
||||
traits.add_supported_mode(humidifier::HUMIDIFIER_MODE_BABY);
|
||||
traits.set_supports_action(true);
|
||||
return traits;
|
||||
}
|
||||
void GenericHumidifier::compute_state_() {
|
||||
if (this->mode == humidifier::HUMIDIFIER_MODE_OFF) {
|
||||
this->switch_to_action_(humidifier::HUMIDIFIER_ACTION_OFF);
|
||||
return;
|
||||
}
|
||||
if (std::isnan(this->current_humidity) || std::isnan(this->target_humidity)) {
|
||||
this->switch_to_action_(humidifier::HUMIDIFIER_ACTION_OFF);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void GenericHumidifier::switch_to_action_(humidifier::HumidifierAction action) {
|
||||
if (action == this->action) {
|
||||
// already in target mode
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->prev_trigger_ != nullptr) {
|
||||
this->prev_trigger_->stop_action();
|
||||
this->prev_trigger_ = nullptr;
|
||||
}
|
||||
Trigger<> *trig;
|
||||
switch (action) {
|
||||
case humidifier::HUMIDIFIER_ACTION_OFF:
|
||||
case humidifier::HUMIDIFIER_ACTION_NORMAL:
|
||||
trig = this->normal_trigger_;
|
||||
break;
|
||||
case humidifier::HUMIDIFIER_ACTION_ECO:
|
||||
trig = this->eco_trigger_;
|
||||
break;
|
||||
case humidifier::HUMIDIFIER_ACTION_BOOST:
|
||||
trig = this->boost_trigger_;
|
||||
break;
|
||||
case humidifier::HUMIDIFIER_ACTION_COMFORT:
|
||||
trig = this->comfort_trigger_;
|
||||
break;
|
||||
case humidifier::HUMIDIFIER_ACTION_SLEEP:
|
||||
trig = this->sleep_trigger_;
|
||||
break;
|
||||
case humidifier::HUMIDIFIER_ACTION_AUTO:
|
||||
trig = this->auto_trigger_;
|
||||
break;
|
||||
case humidifier::HUMIDIFIER_ACTION_BABY:
|
||||
trig = this->baby_trigger_;
|
||||
break;
|
||||
default:
|
||||
trig = nullptr;
|
||||
}
|
||||
assert(trig != nullptr);
|
||||
trig->trigger();
|
||||
this->action = action;
|
||||
this->prev_trigger_ = trig;
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
void GenericHumidifier::set_normal_config(const GenericHumidifierTargetHumidityConfig &normal_config) {
|
||||
this->normal_config_ = normal_config;
|
||||
}
|
||||
|
||||
GenericHumidifier::GenericHumidifier()
|
||||
: normal_trigger_(new Trigger<>()),
|
||||
eco_trigger_(new Trigger<>()),
|
||||
boost_trigger_(new Trigger<>()),
|
||||
comfort_trigger_(new Trigger<>()),
|
||||
sleep_trigger_(new Trigger<>()),
|
||||
auto_trigger_(new Trigger<>()),
|
||||
baby_trigger_(new Trigger<>()) {}
|
||||
void GenericHumidifier::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }
|
||||
Trigger<> *GenericHumidifier::get_normal_trigger() const { return this->normal_trigger_; }
|
||||
void GenericHumidifier::set_supports_normal(bool supports_normal) { this->supports_normal_ = supports_normal; }
|
||||
Trigger<> *GenericHumidifier::get_eco_trigger() const { return this->eco_trigger_; }
|
||||
void GenericHumidifier::set_supports_eco(bool supports_eco) { this->supports_eco_ = supports_eco; }
|
||||
Trigger<> *GenericHumidifier::get_boost_trigger() const { return this->boost_trigger_; }
|
||||
void GenericHumidifier::set_supports_boost(bool supports_boost) { this->supports_boost_ = supports_boost; }
|
||||
Trigger<> *GenericHumidifier::get_comfort_trigger() const { return this->comfort_trigger_; }
|
||||
void GenericHumidifier::set_supports_comfort(bool supports_comfort) { this->supports_comfort_ = supports_comfort; }
|
||||
Trigger<> *GenericHumidifier::get_sleep_trigger() const { return this->sleep_trigger_; }
|
||||
void GenericHumidifier::set_supports_sleep(bool supports_sleep) { this->supports_sleep_ = supports_sleep; }
|
||||
Trigger<> *GenericHumidifier::get_auto_trigger() const { return this->auto_trigger_; }
|
||||
void GenericHumidifier::set_supports_auto(bool supports_auto) { this->supports_auto_ = supports_auto; }
|
||||
Trigger<> *GenericHumidifier::get_baby_trigger() const { return this->baby_trigger_; }
|
||||
void GenericHumidifier::set_supports_baby(bool supports_baby) { this->supports_baby_ = supports_baby; }
|
||||
void GenericHumidifier::dump_config() {
|
||||
LOG_HUMIDIFIER("", "Generic Humidifier", this);
|
||||
ESP_LOGCONFIG(TAG, " Supports Normal Mode: %s", YESNO(this->supports_normal_));
|
||||
ESP_LOGCONFIG(TAG, " Supports Eco Mode: %s", YESNO(this->supports_eco_));
|
||||
ESP_LOGCONFIG(TAG, " Supports Boost Mode: %s", YESNO(this->supports_boost_));
|
||||
ESP_LOGCONFIG(TAG, " Supports Comfort Mode: %s", YESNO(this->supports_comfort_));
|
||||
ESP_LOGCONFIG(TAG, " Supports Sleep Mode: %s", YESNO(this->supports_sleep_));
|
||||
ESP_LOGCONFIG(TAG, " Supports Auto Mode: %s", YESNO(this->supports_auto_));
|
||||
ESP_LOGCONFIG(TAG, " Supports Baby Mode: %s", YESNO(this->supports_baby_));
|
||||
ESP_LOGCONFIG(TAG, " Default Target Humidity: %.2f%%", this->normal_config_.default_humidity);
|
||||
}
|
||||
|
||||
GenericHumidifierTargetHumidityConfig::GenericHumidifierTargetHumidityConfig() = default;
|
||||
GenericHumidifierTargetHumidityConfig::GenericHumidifierTargetHumidityConfig(float default_humidity)
|
||||
: default_humidity(default_humidity) {}
|
||||
|
||||
} // namespace generic_humidifier
|
||||
} // namespace esphome
|
|
@ -0,0 +1,123 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/humidifier/humidifier.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace generic_humidifier {
|
||||
|
||||
struct GenericHumidifierTargetHumidityConfig {
|
||||
public:
|
||||
GenericHumidifierTargetHumidityConfig();
|
||||
GenericHumidifierTargetHumidityConfig(float default_humidity);
|
||||
|
||||
float default_humidity{NAN};
|
||||
};
|
||||
|
||||
class GenericHumidifier : public humidifier::Humidifier, public Component {
|
||||
public:
|
||||
GenericHumidifier();
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
void set_sensor(sensor::Sensor *sensor);
|
||||
Trigger<> *get_normal_trigger() const;
|
||||
void set_supports_normal(bool supports_normal);
|
||||
Trigger<> *get_eco_trigger() const;
|
||||
void set_supports_eco(bool supports_eco);
|
||||
Trigger<> *get_boost_trigger() const;
|
||||
void set_supports_boost(bool supports_boost);
|
||||
Trigger<> *get_comfort_trigger() const;
|
||||
void set_supports_comfort(bool supports_comfort);
|
||||
Trigger<> *get_sleep_trigger() const;
|
||||
void set_supports_sleep(bool supports_sleep);
|
||||
Trigger<> *get_auto_trigger() const;
|
||||
void set_supports_auto(bool supports_auto);
|
||||
Trigger<> *get_baby_trigger() const;
|
||||
void set_supports_baby(bool supports_baby);
|
||||
void set_normal_config(const GenericHumidifierTargetHumidityConfig &normal_config);
|
||||
|
||||
protected:
|
||||
/// Override control to change settings of the climate device.
|
||||
void control(const humidifier::HumidifierCall &call) override;
|
||||
/// Return the traits of this controller.
|
||||
humidifier::HumidifierTraits traits() override;
|
||||
|
||||
/// Re-compute the state of this climate controller.
|
||||
void compute_state_();
|
||||
|
||||
/// Switch the climate device to the given climate mode.
|
||||
void switch_to_action_(humidifier::HumidifierAction action);
|
||||
|
||||
/// The sensor used for getting the current temperature
|
||||
sensor::Sensor *sensor_{nullptr};
|
||||
|
||||
/** The trigger to call when the controller should switch to normal mode.
|
||||
*/
|
||||
Trigger<> *normal_trigger_;
|
||||
/** Whether the controller supports normal mode.
|
||||
* A false value for this attribute means that the controller has no normal mode action
|
||||
*/
|
||||
bool supports_normal_{false};
|
||||
|
||||
/** The trigger to call when the controller should switch to eco mode.
|
||||
*/
|
||||
Trigger<> *eco_trigger_;
|
||||
/** Whether the controller supports eco mode.
|
||||
* A false value for this attribute means that the controller has no eco mode action.
|
||||
*/
|
||||
bool supports_eco_{false};
|
||||
|
||||
/** The trigger to call when the controller should switch to boost mode.
|
||||
*/
|
||||
Trigger<> *boost_trigger_;
|
||||
/** Whether the controller supports boost mode.
|
||||
* A false value for this attribute means that the controller has no boost mode action
|
||||
*/
|
||||
bool supports_boost_{false};
|
||||
|
||||
/** The trigger to call when the controller should switch to comfort mode.
|
||||
*/
|
||||
Trigger<> *comfort_trigger_;
|
||||
/** Whether the controller supports comfort mode.
|
||||
* A false value for this attribute means that the controller has no comfort mode action.
|
||||
*/
|
||||
bool supports_comfort_{false};
|
||||
|
||||
/** The trigger to call when the controller should switch to sleep mode.
|
||||
*/
|
||||
Trigger<> *sleep_trigger_;
|
||||
/** Whether the controller supports sleep mode.
|
||||
* A false value for this attribute means that the controller has no sleep mode action
|
||||
*/
|
||||
bool supports_sleep_{false};
|
||||
|
||||
/** The trigger to call when the controller should switch to auto mode.
|
||||
*/
|
||||
Trigger<> *auto_trigger_;
|
||||
/** Whether the controller supports auto mode.
|
||||
* A false value for this attribute means that the controller has no auto mode action.
|
||||
*/
|
||||
bool supports_auto_{false};
|
||||
|
||||
/** The trigger to call when the controller should switch to baby mode.
|
||||
*/
|
||||
Trigger<> *baby_trigger_;
|
||||
/** Whether the controller supports baby mode.
|
||||
* A false value for this attribute means that the controller has no baby mode action
|
||||
*/
|
||||
bool supports_baby_{false};
|
||||
|
||||
/** A reference to the trigger that was previously active.
|
||||
*
|
||||
* This is so that the previous trigger can be stopped before enabling a new one.
|
||||
*/
|
||||
Trigger<> *prev_trigger_{nullptr};
|
||||
|
||||
GenericHumidifierTargetHumidityConfig normal_config_{};
|
||||
};
|
||||
|
||||
} // namespace generic_humidifier
|
||||
} // namespace esphome
|
|
@ -0,0 +1,103 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.components import humidifier, sensor
|
||||
from esphome.const import (
|
||||
CONF_DEFAULT_TARGET_HUMIDITY,
|
||||
CONF_ID,
|
||||
CONF_SENSOR,
|
||||
)
|
||||
|
||||
generic_humidifier_ns = cg.esphome_ns.namespace("generic_humidifier")
|
||||
GenericHumidifier = generic_humidifier_ns.class_(
|
||||
"GenericHumidifier", humidifier.Humidifier, cg.Component
|
||||
)
|
||||
GenericHumidifierTargetHumidityConfig = generic_humidifier_ns.struct(
|
||||
"GenericHumidifierTargetHumidityConfig"
|
||||
)
|
||||
|
||||
CONF_NORMAL_ACTION = "normal_action"
|
||||
CONF_ECO_ACTION = "eco_action"
|
||||
CONF_BOOST_ACTION = "boost_action"
|
||||
CONF_COMFORT_ACTION = "comfort_action"
|
||||
CONF_SLEEP_ACTION = "sleep_action"
|
||||
CONF_AUTO_ACTION = "auto_action"
|
||||
CONF_BABY_ACTION = "baby_action"
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
humidifier.HUMIDIFIER_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(GenericHumidifier),
|
||||
cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor),
|
||||
cv.Required(CONF_DEFAULT_TARGET_HUMIDITY): cv.temperature,
|
||||
cv.Required(CONF_NORMAL_ACTION): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
cv.Optional(CONF_ECO_ACTION): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_BOOST_ACTION): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_COMFORT_ACTION): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
cv.Optional(CONF_SLEEP_ACTION): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_AUTO_ACTION): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_BABY_ACTION): automation.validate_automation(single=True),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.has_at_least_one_key(
|
||||
CONF_NORMAL_ACTION,
|
||||
CONF_ECO_ACTION,
|
||||
CONF_BOOST_ACTION,
|
||||
CONF_COMFORT_ACTION,
|
||||
CONF_SLEEP_ACTION,
|
||||
CONF_AUTO_ACTION,
|
||||
CONF_BABY_ACTION,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await humidifier.register_humidifier(var, config)
|
||||
|
||||
sens = await cg.get_variable(config[CONF_SENSOR])
|
||||
cg.add(var.set_sensor(sens))
|
||||
|
||||
normal_config = GenericHumidifierTargetHumidityConfig(
|
||||
config[CONF_DEFAULT_TARGET_HUMIDITY],
|
||||
)
|
||||
cg.add(var.set_normal_config(normal_config))
|
||||
|
||||
if normal_action_config := config.get(CONF_NORMAL_ACTION):
|
||||
await automation.build_automation(
|
||||
var.get_normal_trigger(), [], normal_action_config
|
||||
)
|
||||
cg.add(var.set_supports_normal(True))
|
||||
if eco_action_config := config.get(CONF_ECO_ACTION):
|
||||
await automation.build_automation(var.get_eco_trigger(), [], eco_action_config)
|
||||
cg.add(var.set_supports_eco(True))
|
||||
if boost_action_config := config.get(CONF_BOOST_ACTION):
|
||||
await automation.build_automation(
|
||||
var.get_boost_trigger(), [], boost_action_config
|
||||
)
|
||||
cg.add(var.set_supports_boost(True))
|
||||
if comfort_action_config := config.get(CONF_COMFORT_ACTION):
|
||||
await automation.build_automation(
|
||||
var.get_comfort_trigger(), [], comfort_action_config
|
||||
)
|
||||
cg.add(var.set_supports_comfort(True))
|
||||
if sleep_action_config := config.get(CONF_SLEEP_ACTION):
|
||||
await automation.build_automation(
|
||||
var.get_sleep_trigger(), [], sleep_action_config
|
||||
)
|
||||
cg.add(var.set_supports_sleep(True))
|
||||
if auto_action_config := config.get(CONF_AUTO_ACTION):
|
||||
await automation.build_automation(
|
||||
var.get_auto_trigger(), [], auto_action_config
|
||||
)
|
||||
cg.add(var.set_supports_auto(True))
|
||||
if baby_action_config := config.get(CONF_BABY_ACTION):
|
||||
await automation.build_automation(
|
||||
var.get_baby_trigger(), [], baby_action_config
|
||||
)
|
||||
cg.add(var.set_supports_baby(True))
|
|
@ -0,0 +1,224 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
from esphome import automation
|
||||
from esphome.components import mqtt
|
||||
from esphome.const import (
|
||||
CONF_ACTION_STATE_TOPIC,
|
||||
CONF_CURRENT_HUMIDITY_STATE_TOPIC,
|
||||
CONF_ID,
|
||||
CONF_MAX_HUMIDITY,
|
||||
CONF_MIN_HUMIDITY,
|
||||
CONF_MODE,
|
||||
CONF_MODE_COMMAND_TOPIC,
|
||||
CONF_MODE_STATE_TOPIC,
|
||||
CONF_ON_CONTROL,
|
||||
CONF_ON_STATE,
|
||||
CONF_TARGET_HUMIDITY,
|
||||
CONF_TARGET_HUMIDITY_COMMAND_TOPIC,
|
||||
CONF_TARGET_HUMIDITY_STATE_TOPIC,
|
||||
CONF_HUMIDITY_STEP,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_VISUAL,
|
||||
CONF_MQTT_ID,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
CODEOWNERS = ["@Jaco1990"]
|
||||
humidifier_ns = cg.esphome_ns.namespace("humidifier")
|
||||
|
||||
Humidifier = humidifier_ns.class_("Humidifier", cg.EntityBase)
|
||||
HumidifierCall = humidifier_ns.class_("HumidifierCall")
|
||||
HumidifierTraits = humidifier_ns.class_("HumidifierTraits")
|
||||
|
||||
HumidifierMode = humidifier_ns.enum("HumidifierMode")
|
||||
HUMIDIFIER_MODES = {
|
||||
"OFF": HumidifierMode.HUMIDIFIER_MODE_OFF,
|
||||
"NORMAL": HumidifierMode.HUMIDIFIER_MODE_NORMAL,
|
||||
"ECO": HumidifierMode.HUMIDIFIER_MODE_ECO,
|
||||
"AWAY": HumidifierMode.HUMIDIFIER_MODE_AWAY,
|
||||
"BOOST": HumidifierMode.HUMIDIFIER_MODE_BOOST,
|
||||
"COMFORT": HumidifierMode.HUMIDIFIER_MODE_COMFORT,
|
||||
"HOME": HumidifierMode.HUMIDIFIER_MODE_HOME,
|
||||
"SLEEP": HumidifierMode.HUMIDIFIER_MODE_SLEEP,
|
||||
"AUTO": HumidifierMode.HUMIDIFIER_MODE_AUTO,
|
||||
"BABY": HumidifierMode.HUMIDIFIER_MODE_BABY,
|
||||
}
|
||||
|
||||
validate_humidifier_mode = cv.enum(HUMIDIFIER_MODES, upper=True)
|
||||
|
||||
CONF_CURRENT_HUMIDITY = "current_humidity"
|
||||
|
||||
visual_humidity = cv.float_with_unit("visual_humidity", "(%)?")
|
||||
|
||||
|
||||
def single_visual_humidity(value):
|
||||
if isinstance(value, dict):
|
||||
return value
|
||||
|
||||
value = visual_humidity(value)
|
||||
return VISUAL_HUMIDITY_STEP_SCHEMA(
|
||||
{
|
||||
CONF_TARGET_HUMIDITY: value,
|
||||
CONF_CURRENT_HUMIDITY: value,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
# Actions
|
||||
ControlAction = humidifier_ns.class_("ControlAction", automation.Action)
|
||||
StateTrigger = humidifier_ns.class_(
|
||||
"StateTrigger", automation.Trigger.template(Humidifier.operator("ref"))
|
||||
)
|
||||
ControlTrigger = humidifier_ns.class_(
|
||||
"ControlTrigger", automation.Trigger.template(HumidifierCall.operator("ref"))
|
||||
)
|
||||
|
||||
VISUAL_HUMIDITY_STEP_SCHEMA = cv.Any(
|
||||
single_visual_humidity,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_TARGET_HUMIDITY): visual_humidity,
|
||||
cv.Required(CONF_CURRENT_HUMIDITY): visual_humidity,
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
HUMIDIFIER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
|
||||
cv.MQTT_COMMAND_COMPONENT_SCHEMA
|
||||
).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Humidifier),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTHumidifierComponent),
|
||||
cv.Optional(CONF_VISUAL, default={}): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_HUMIDITY_STEP): VISUAL_HUMIDITY_STEP_SCHEMA,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ACTION_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_CURRENT_HUMIDITY_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_MODE_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_MODE_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_HUMIDITY_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_HUMIDITY_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_ON_CONTROL): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ControlTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_STATE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def setup_humidifier_core_(var, config):
|
||||
await setup_entity(var, config)
|
||||
|
||||
visual = config[CONF_VISUAL]
|
||||
if CONF_MIN_HUMIDITY in visual:
|
||||
cg.add(var.set_visual_min_humidity_override(visual[CONF_MIN_HUMIDITY]))
|
||||
if CONF_MAX_HUMIDITY in visual:
|
||||
cg.add(var.set_visual_max_humidity_override(visual[CONF_MAX_HUMIDITY]))
|
||||
if CONF_HUMIDITY_STEP in visual:
|
||||
cg.add(
|
||||
var.set_visual_humidity_step_override(
|
||||
visual[CONF_HUMIDITY_STEP][CONF_TARGET_HUMIDITY],
|
||||
visual[CONF_HUMIDITY_STEP][CONF_CURRENT_HUMIDITY],
|
||||
)
|
||||
)
|
||||
|
||||
if CONF_MQTT_ID in config:
|
||||
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
|
||||
await mqtt.register_mqtt_component(mqtt_, config)
|
||||
|
||||
if CONF_ACTION_STATE_TOPIC in config:
|
||||
cg.add(mqtt_.set_custom_action_state_topic(config[CONF_ACTION_STATE_TOPIC]))
|
||||
if CONF_CURRENT_HUMIDITY_STATE_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_current_humidity_state_topic(
|
||||
config[CONF_CURRENT_HUMIDITY_STATE_TOPIC]
|
||||
)
|
||||
)
|
||||
if CONF_MODE_COMMAND_TOPIC in config:
|
||||
cg.add(mqtt_.set_custom_mode_command_topic(config[CONF_MODE_COMMAND_TOPIC]))
|
||||
if CONF_MODE_STATE_TOPIC in config:
|
||||
cg.add(mqtt_.set_custom_mode_state_topic(config[CONF_MODE_STATE_TOPIC]))
|
||||
if CONF_TARGET_HUMIDITY_COMMAND_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_target_humidity_command_topic(
|
||||
config[CONF_TARGET_HUMIDITY_COMMAND_TOPIC]
|
||||
)
|
||||
)
|
||||
if CONF_TARGET_HUMIDITY_STATE_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_target_humidity_state_topic(
|
||||
config[CONF_TARGET_HUMIDITY_STATE_TOPIC]
|
||||
)
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_ON_STATE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(
|
||||
trigger, [(Humidifier.operator("ref"), "x")], conf
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_ON_CONTROL, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(
|
||||
trigger, [(HumidifierCall.operator("ref"), "x")], conf
|
||||
)
|
||||
|
||||
|
||||
async def register_humidifier(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_humidifier(var))
|
||||
await setup_humidifier_core_(var, config)
|
||||
|
||||
|
||||
HUMIDIFIER_CONTROL_ACTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(Humidifier),
|
||||
cv.Optional(CONF_MODE): cv.templatable(validate_humidifier_mode),
|
||||
cv.Optional(CONF_TARGET_HUMIDITY): cv.templatable(cv.percentage_int),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"humidifier.control", ControlAction, HUMIDIFIER_CONTROL_ACTION_SCHEMA
|
||||
)
|
||||
async def humidifier_control_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
if CONF_MODE in config:
|
||||
template_ = await cg.templatable(config[CONF_MODE], args, HumidifierMode)
|
||||
cg.add(var.set_mode(template_))
|
||||
if CONF_TARGET_HUMIDITY in config:
|
||||
template_ = await cg.templatable(config[CONF_TARGET_HUMIDITY], args, float)
|
||||
cg.add(var.set_target_temperature(template_))
|
||||
return var
|
||||
|
||||
|
||||
@coroutine_with_priority(100.0)
|
||||
async def to_code(config):
|
||||
cg.add_define("USE_HUMIDIFIER")
|
||||
cg.add_global(humidifier_ns.using)
|
|
@ -0,0 +1,42 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "humidifier.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace humidifier {
|
||||
|
||||
template<typename... Ts> class ControlAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit ControlAction(Humidifier *humidifier) : humidifier_(humidifier) {}
|
||||
|
||||
TEMPLATABLE_VALUE(HumidifierMode, mode)
|
||||
TEMPLATABLE_VALUE(float, target_humidity)
|
||||
|
||||
void play(Ts... x) override {
|
||||
auto call = this->humidifier_->make_call();
|
||||
call.set_mode(this->mode_.optional_value(x...));
|
||||
call.set_target_humidity(this->target_humidity_.optional_value(x...));
|
||||
call.perform();
|
||||
}
|
||||
|
||||
protected:
|
||||
Humidifier *humidifier_;
|
||||
};
|
||||
|
||||
class ControlTrigger : public Trigger<HumidifierCall &> {
|
||||
public:
|
||||
ControlTrigger(Humidifier *humidifier) {
|
||||
humidifier->add_on_control_callback([this](HumidifierCall &x) { this->trigger(x); });
|
||||
}
|
||||
};
|
||||
|
||||
class StateTrigger : public Trigger<Humidifier &> {
|
||||
public:
|
||||
StateTrigger(Humidifier *humidifier) {
|
||||
humidifier->add_on_state_callback([this](Humidifier &x) { this->trigger(x); });
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace humidifier
|
||||
} // namespace esphome
|
|
@ -0,0 +1,229 @@
|
|||
#include "humidifier.h"
|
||||
#include "esphome/core/macros.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace humidifier {
|
||||
|
||||
static const char *const TAG = "humidifier";
|
||||
|
||||
void HumidifierCall::perform() {
|
||||
this->parent_->control_callback_.call(*this);
|
||||
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
||||
this->validate_();
|
||||
if (this->mode_.has_value()) {
|
||||
const LogString *mode_s = humidifier_mode_to_string(*this->mode_);
|
||||
ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(mode_s));
|
||||
}
|
||||
if (this->target_humidity_.has_value()) {
|
||||
ESP_LOGD(TAG, " Target Humidity: %.0f", *this->target_humidity_);
|
||||
}
|
||||
this->parent_->control(*this);
|
||||
}
|
||||
void HumidifierCall::validate_() {
|
||||
auto traits = this->parent_->get_traits();
|
||||
if (this->mode_.has_value()) {
|
||||
auto mode = *this->mode_;
|
||||
if (!traits.supports_mode(mode)) {
|
||||
ESP_LOGW(TAG, " Mode %s is not supported by this device!", LOG_STR_ARG(humidifier_mode_to_string(mode)));
|
||||
this->mode_.reset();
|
||||
}
|
||||
}
|
||||
if (this->target_humidity_.has_value()) {
|
||||
auto target = *this->target_humidity_;
|
||||
// if (traits.get_supports_target_humidity()) {
|
||||
if (std::isnan(target)) {
|
||||
ESP_LOGW(TAG, " Target humidity must not be NAN!");
|
||||
this->target_humidity_.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
HumidifierCall &HumidifierCall::set_mode(HumidifierMode mode) {
|
||||
this->mode_ = mode;
|
||||
return *this;
|
||||
}
|
||||
HumidifierCall &HumidifierCall::set_mode(const std::string &mode) {
|
||||
if (str_equals_case_insensitive(mode, "OFF")) {
|
||||
this->set_mode(HUMIDIFIER_MODE_OFF);
|
||||
} else if (str_equals_case_insensitive(mode, "NORMAL")) {
|
||||
this->set_mode(HUMIDIFIER_MODE_NORMAL);
|
||||
} else if (str_equals_case_insensitive(mode, "ECO")) {
|
||||
this->set_mode(HUMIDIFIER_MODE_ECO);
|
||||
} else if (str_equals_case_insensitive(mode, "AWAY")) {
|
||||
this->set_mode(HUMIDIFIER_MODE_AWAY);
|
||||
} else if (str_equals_case_insensitive(mode, "BOOST")) {
|
||||
this->set_mode(HUMIDIFIER_MODE_BOOST);
|
||||
} else if (str_equals_case_insensitive(mode, "COMFORT")) {
|
||||
this->set_mode(HUMIDIFIER_MODE_COMFORT);
|
||||
} else if (str_equals_case_insensitive(mode, "HOME")) {
|
||||
this->set_mode(HUMIDIFIER_MODE_HOME);
|
||||
} else if (str_equals_case_insensitive(mode, "SLEEP")) {
|
||||
this->set_mode(HUMIDIFIER_MODE_SLEEP);
|
||||
} else if (str_equals_case_insensitive(mode, "AUTO")) {
|
||||
this->set_mode(HUMIDIFIER_MODE_AUTO);
|
||||
} else if (str_equals_case_insensitive(mode, "BABY")) {
|
||||
this->set_mode(HUMIDIFIER_MODE_BABY);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "'%s' - Unrecognized mode %s", this->parent_->get_name().c_str(), mode.c_str());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
HumidifierCall &HumidifierCall::set_target_humidity(float target_humidity) {
|
||||
this->target_humidity_ = target_humidity;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const optional<HumidifierMode> &HumidifierCall::get_mode() const { return this->mode_; }
|
||||
const optional<float> &HumidifierCall::get_target_humidity() const { return this->target_humidity_; }
|
||||
|
||||
HumidifierCall &HumidifierCall::set_target_humidity(optional<float> target_humidity) {
|
||||
this->target_humidity_ = target_humidity;
|
||||
return *this;
|
||||
}
|
||||
HumidifierCall &HumidifierCall::set_mode(optional<HumidifierMode> mode) {
|
||||
this->mode_ = mode;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Humidifier::add_on_state_callback(std::function<void(Humidifier &)> &&callback) {
|
||||
this->state_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
void Humidifier::add_on_control_callback(std::function<void(HumidifierCall &)> &&callback) {
|
||||
this->control_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
// Random 32bit value; If this changes existing restore preferences are invalidated
|
||||
static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL;
|
||||
|
||||
optional<HumidifierDeviceRestoreState> Humidifier::restore_state_() {
|
||||
this->rtc_ = global_preferences->make_preference<HumidifierDeviceRestoreState>(this->get_object_id_hash() ^
|
||||
RESTORE_STATE_VERSION);
|
||||
HumidifierDeviceRestoreState recovered{};
|
||||
if (!this->rtc_.load(&recovered))
|
||||
return {};
|
||||
return recovered;
|
||||
}
|
||||
void Humidifier::save_state_() {
|
||||
#if (defined(USE_ESP_IDF) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0))) && \
|
||||
!defined(CLANG_TIDY)
|
||||
#pragma GCC diagnostic ignored "-Wclass-memaccess"
|
||||
#define TEMP_IGNORE_MEMACCESS
|
||||
#endif
|
||||
HumidifierDeviceRestoreState state{};
|
||||
// initialize as zero to prevent random data on stack triggering erase
|
||||
memset(&state, 0, sizeof(HumidifierDeviceRestoreState));
|
||||
#ifdef TEMP_IGNORE_MEMACCESS
|
||||
#pragma GCC diagnostic pop
|
||||
#undef TEMP_IGNORE_MEMACCESS
|
||||
#endif
|
||||
state.mode = this->mode;
|
||||
auto traits = this->get_traits();
|
||||
if (traits.get_supports_target_humidity()) {
|
||||
state.target_humidity = this->target_humidity;
|
||||
}
|
||||
|
||||
this->rtc_.save(&state);
|
||||
}
|
||||
|
||||
void Humidifier::publish_state() {
|
||||
ESP_LOGD(TAG, "'%s' - Sending state:", this->name_.c_str());
|
||||
auto traits = this->get_traits();
|
||||
|
||||
ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(humidifier_mode_to_string(this->mode)));
|
||||
if (traits.get_supports_action()) {
|
||||
ESP_LOGD(TAG, " Action: %s", LOG_STR_ARG(humidifier_action_to_string(this->action)));
|
||||
}
|
||||
if (traits.get_supports_current_humidity()) {
|
||||
ESP_LOGD(TAG, " Current Humidity: %.0f%%", this->current_humidity);
|
||||
}
|
||||
if (traits.get_supports_target_humidity()) {
|
||||
ESP_LOGD(TAG, " Target Humidity: %.0f%%", this->target_humidity);
|
||||
}
|
||||
// Send state to frontend
|
||||
this->state_callback_.call(*this);
|
||||
// Save state
|
||||
this->save_state_();
|
||||
}
|
||||
HumidifierTraits Humidifier::get_traits() {
|
||||
auto traits = this->traits();
|
||||
if (this->visual_min_humidity_override_.has_value()) {
|
||||
traits.set_visual_min_humidity(*this->visual_min_humidity_override_);
|
||||
}
|
||||
if (this->visual_max_humidity_override_.has_value()) {
|
||||
traits.set_visual_max_humidity(*this->visual_max_humidity_override_);
|
||||
}
|
||||
if (this->visual_target_humidity_step_override_.has_value()) {
|
||||
traits.set_visual_target_humidity_step(*this->visual_target_humidity_step_override_);
|
||||
traits.set_visual_current_humidity_step(*this->visual_current_humidity_step_override_);
|
||||
}
|
||||
|
||||
return traits;
|
||||
}
|
||||
|
||||
void Humidifier::set_visual_min_humidity_override(float visual_min_humidity_override) {
|
||||
this->visual_min_humidity_override_ = visual_min_humidity_override;
|
||||
}
|
||||
void Humidifier::set_visual_max_humidity_override(float visual_max_humidity_override) {
|
||||
this->visual_max_humidity_override_ = visual_max_humidity_override;
|
||||
}
|
||||
void Humidifier::set_visual_humidity_step_override(float target, float current) {
|
||||
this->visual_target_humidity_step_override_ = target;
|
||||
this->visual_current_humidity_step_override_ = current;
|
||||
}
|
||||
|
||||
HumidifierCall Humidifier::make_call() { return HumidifierCall(this); }
|
||||
|
||||
HumidifierCall HumidifierDeviceRestoreState::to_call(Humidifier *humidifier) {
|
||||
auto call = humidifier->make_call();
|
||||
auto traits = humidifier->get_traits();
|
||||
call.set_mode(this->mode);
|
||||
if (traits.get_supports_target_humidity()) {
|
||||
call.set_target_humidity(this->target_humidity);
|
||||
}
|
||||
return call;
|
||||
}
|
||||
|
||||
void HumidifierDeviceRestoreState::apply(Humidifier *humidifier) {
|
||||
auto traits = humidifier->get_traits();
|
||||
humidifier->mode = this->mode;
|
||||
if (traits.get_supports_target_humidity()) {
|
||||
humidifier->target_humidity = this->target_humidity;
|
||||
}
|
||||
humidifier->publish_state();
|
||||
}
|
||||
|
||||
template<typename T1, typename T2> bool set_alternative(optional<T1> &dst, optional<T2> &alt, const T1 &src) {
|
||||
bool is_changed = alt.has_value();
|
||||
alt.reset();
|
||||
if (is_changed || dst != src) {
|
||||
dst = src;
|
||||
is_changed = true;
|
||||
}
|
||||
return is_changed;
|
||||
}
|
||||
|
||||
void Humidifier::dump_traits_(const char *tag) {
|
||||
auto traits = this->get_traits();
|
||||
ESP_LOGCONFIG(tag, "HumidifierTraits:");
|
||||
ESP_LOGCONFIG(tag, " [x] Visual settings:");
|
||||
ESP_LOGCONFIG(tag, " - Min humidity: %.1f", traits.get_visual_min_humidity());
|
||||
ESP_LOGCONFIG(tag, " - Max humidity: %.1f", traits.get_visual_max_humidity());
|
||||
ESP_LOGCONFIG(tag, " - Humidity step:");
|
||||
ESP_LOGCONFIG(tag, " Target: %.0f%%", traits.get_visual_target_humidity_step());
|
||||
ESP_LOGCONFIG(tag, " Current: %.1F%%", traits.get_visual_current_humidity_step());
|
||||
if (traits.get_supports_current_humidity()) {
|
||||
ESP_LOGCONFIG(tag, " [x] Supports current humidity");
|
||||
}
|
||||
if (traits.get_supports_action()) {
|
||||
ESP_LOGCONFIG(tag, " [x] Supports action");
|
||||
}
|
||||
if (!traits.get_supported_modes().empty()) {
|
||||
ESP_LOGCONFIG(tag, " [x] Supported modes:");
|
||||
for (HumidifierMode m : traits.get_supported_modes())
|
||||
ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(humidifier_mode_to_string(m)));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace humidifier
|
||||
} // namespace esphome
|
|
@ -0,0 +1,185 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "humidifier_mode.h"
|
||||
#include "humidifier_traits.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace humidifier {
|
||||
|
||||
#define LOG_HUMIDIFIER(prefix, type, obj) \
|
||||
if ((obj) != nullptr) { \
|
||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||
}
|
||||
|
||||
class Humidifier;
|
||||
|
||||
/** This class is used to encode all control actions on a Humidifier device.
|
||||
*
|
||||
* It is supposed to be used by all code that wishes to control a humidifier device (mqtt, api, lambda etc).
|
||||
* Create an instance of this class by calling `id(humidifier_device).make_call();`. Then set all attributes
|
||||
* with the `set_x` methods. Finally, to apply the changes call `.perform();`.
|
||||
*
|
||||
* The integration that implements the humidifier device receives this instance with the `control` method.
|
||||
* It should check all the properties it implements and apply them as needed. It should do so by
|
||||
* getting all properties it controls with the getter methods in this class. If the optional value is
|
||||
* set (check with `.has_value()`) that means the user wants to control this property. Get the value
|
||||
* of the optional with the star operator (`*call.get_mode()`) and apply it.
|
||||
*/
|
||||
class HumidifierCall {
|
||||
public:
|
||||
explicit HumidifierCall(Humidifier *parent) : parent_(parent) {}
|
||||
|
||||
/// Set the mode of the humidifier device.
|
||||
HumidifierCall &set_mode(HumidifierMode mode);
|
||||
/// Set the mode of the humidifier device.
|
||||
HumidifierCall &set_mode(optional<HumidifierMode> mode);
|
||||
/// Set the mode of the humidifier device based on a string.
|
||||
HumidifierCall &set_mode(const std::string &mode);
|
||||
/// Set the target humidity of the humidifier device.
|
||||
HumidifierCall &set_target_humidity(float target_humidity);
|
||||
/// Set the target humidity of the humidifier device.
|
||||
HumidifierCall &set_target_humidity(optional<float> target_humidity);
|
||||
|
||||
void perform();
|
||||
|
||||
const optional<HumidifierMode> &get_mode() const;
|
||||
const optional<float> &get_target_humidity() const;
|
||||
|
||||
protected:
|
||||
void validate_();
|
||||
|
||||
Humidifier *const parent_;
|
||||
optional<HumidifierMode> mode_;
|
||||
optional<float> target_humidity_;
|
||||
};
|
||||
|
||||
/// Struct used to save the state of the humidifier device in restore memory.
|
||||
/// Make sure to update RESTORE_STATE_VERSION when changing the struct entries.
|
||||
struct HumidifierDeviceRestoreState {
|
||||
HumidifierMode mode;
|
||||
float target_humidity;
|
||||
|
||||
/// Convert this struct to a humidifier call that can be performed.
|
||||
HumidifierCall to_call(Humidifier *humidifier);
|
||||
/// Apply these settings to the humidifier device.
|
||||
void apply(Humidifier *humidifier);
|
||||
} __attribute__((packed));
|
||||
|
||||
/**
|
||||
* HumidifierDevice - This is the base class for all humidifier integrations. Each integration
|
||||
* needs to extend this class and implement two functions:
|
||||
*
|
||||
* - get_traits() - return the static traits of the humidifier device
|
||||
* - control(HumidifierDeviceCall call) - Apply the given changes from call.
|
||||
*
|
||||
* To write data to the frontend, the integration must first set the properties using
|
||||
* this->property = value; (for example this->current_humidity = 55;); then the integration
|
||||
* must call this->publish_state(); to send the entire state to the frontend.
|
||||
*
|
||||
* The entire state of the humifier device is encoded in public properties of the base class (current_humidity,
|
||||
* mode etc). These are read-only for the user and rw for integrations. The reason these are public
|
||||
* is for simple access to them from lambdas `if (id(my_humifier).mode == humidifier::HUMIDIFIER_MODE_ON) ...`
|
||||
*/
|
||||
class Humidifier : public EntityBase {
|
||||
public:
|
||||
Humidifier() {}
|
||||
|
||||
/// The active mode of the humidifier device.
|
||||
HumidifierMode mode{HUMIDIFIER_MODE_OFF};
|
||||
|
||||
/// The active state of the humidifier device.
|
||||
HumidifierAction action{HUMIDIFIER_ACTION_OFF};
|
||||
|
||||
/// The current humidity of the humidifier device, as reported from the integration.
|
||||
float current_humidity{NAN};
|
||||
|
||||
union {
|
||||
/// The target humidity of the humidifier device.
|
||||
float target_humidity;
|
||||
};
|
||||
|
||||
/** Add a callback for the humidifier device state, each time the state of the humidifier device is updated
|
||||
* (using publish_state), this callback will be called.
|
||||
*
|
||||
* @param callback The callback to call.
|
||||
*/
|
||||
void add_on_state_callback(std::function<void(Humidifier &)> &&callback);
|
||||
|
||||
/**
|
||||
* Add a callback for the humidifier device configuration; each time the configuration parameters of a humidifier
|
||||
* device is updated (using perform() of a HumidifierCall), this callback will be called, before any on_state
|
||||
* callback.
|
||||
*
|
||||
* @param callback The callback to call.
|
||||
*/
|
||||
void add_on_control_callback(std::function<void(HumidifierCall &)> &&callback);
|
||||
|
||||
/** Make a humidifier device control call, this is used to control the humidifier device, see the HumidifierCall
|
||||
* description for more info.
|
||||
* @return A new HumidifierCall instance targeting this humidifier device.
|
||||
*/
|
||||
HumidifierCall make_call();
|
||||
|
||||
/** Publish the state of the humidifier device, to be called from integrations.
|
||||
*
|
||||
* This will schedule the humidifier device to publish its state to all listeners and save the current state
|
||||
* to recover memory.
|
||||
*/
|
||||
void publish_state();
|
||||
|
||||
/** Get the traits of this humidifier device with all overrides applied.
|
||||
*
|
||||
* Traits are static data that encode the capabilities and static data for a humidifier device such as supported
|
||||
* modes,humidity range etc.
|
||||
*/
|
||||
HumidifierTraits get_traits();
|
||||
|
||||
void set_visual_min_humidity_override(float visual_min_humidity_override);
|
||||
void set_visual_max_humidity_override(float visual_max_humidity_override);
|
||||
void set_visual_humidity_step_override(float target, float current);
|
||||
|
||||
protected:
|
||||
friend HumidifierCall;
|
||||
|
||||
/** Get the default traits of this humidifier device.
|
||||
*
|
||||
* Traits are static data that encode the capabilities and static data for a humidifier device such as supported
|
||||
* modes, temperature range etc. Each integration must implement this method and the return value must
|
||||
* be constant during all of execution time.
|
||||
*/
|
||||
virtual HumidifierTraits traits() = 0;
|
||||
|
||||
/** Control the humidifier device, this is a virtual method that each humidifier integration must implement.
|
||||
*
|
||||
* See more info in HummidifierCall. The integration should check all of its values in this method and
|
||||
* set them accordingly. At the end of the call, the integration must call `publish_state()` to
|
||||
* notify the frontend of a changed state.
|
||||
*
|
||||
* @param call The HumidifierCall instance encoding all attribute changes.
|
||||
*/
|
||||
virtual void control(const HumidifierCall &call) = 0;
|
||||
/// Restore the state of the humidifier device, call this from your setup() method.
|
||||
optional<HumidifierDeviceRestoreState> restore_state_();
|
||||
/** Internal method to save the state of the humidifier device to recover memory. This is automatically
|
||||
* called from publish_state()
|
||||
*/
|
||||
void save_state_();
|
||||
|
||||
void dump_traits_(const char *tag);
|
||||
|
||||
CallbackManager<void(Humidifier &)> state_callback_{};
|
||||
CallbackManager<void(HumidifierCall &)> control_callback_{};
|
||||
ESPPreferenceObject rtc_;
|
||||
optional<float> visual_min_humidity_override_{};
|
||||
optional<float> visual_max_humidity_override_{};
|
||||
optional<float> visual_target_humidity_step_override_{};
|
||||
optional<float> visual_current_humidity_step_override_{};
|
||||
};
|
||||
|
||||
} // namespace humidifier
|
||||
} // namespace esphome
|
|
@ -0,0 +1,60 @@
|
|||
#include "humidifier_mode.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace humidifier {
|
||||
|
||||
const LogString *humidifier_mode_to_string(HumidifierMode mode) {
|
||||
switch (mode) {
|
||||
case HUMIDIFIER_MODE_OFF:
|
||||
return LOG_STR("OFF");
|
||||
case HUMIDIFIER_MODE_NORMAL:
|
||||
return LOG_STR("NORMAL");
|
||||
case HUMIDIFIER_MODE_ECO:
|
||||
return LOG_STR("ECO");
|
||||
case HUMIDIFIER_MODE_AWAY:
|
||||
return LOG_STR("AWAY");
|
||||
case HUMIDIFIER_MODE_BOOST:
|
||||
return LOG_STR("BOOST");
|
||||
case HUMIDIFIER_MODE_COMFORT:
|
||||
return LOG_STR("COMFORT");
|
||||
case HUMIDIFIER_MODE_HOME:
|
||||
return LOG_STR("HOME");
|
||||
case HUMIDIFIER_MODE_SLEEP:
|
||||
return LOG_STR("SLEEP");
|
||||
case HUMIDIFIER_MODE_AUTO:
|
||||
return LOG_STR("AUTO");
|
||||
case HUMIDIFIER_MODE_BABY:
|
||||
return LOG_STR("BABY");
|
||||
default:
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
const LogString *humidifier_action_to_string(HumidifierAction action) {
|
||||
switch (action) {
|
||||
case HUMIDIFIER_ACTION_OFF:
|
||||
return LOG_STR("OFF");
|
||||
case HUMIDIFIER_ACTION_NORMAL:
|
||||
return LOG_STR("NORMAL");
|
||||
case HUMIDIFIER_ACTION_ECO:
|
||||
return LOG_STR("ECO");
|
||||
case HUMIDIFIER_ACTION_AWAY:
|
||||
return LOG_STR("AWAY");
|
||||
case HUMIDIFIER_ACTION_BOOST:
|
||||
return LOG_STR("BOOST");
|
||||
case HUMIDIFIER_ACTION_COMFORT:
|
||||
return LOG_STR("COMFORT");
|
||||
case HUMIDIFIER_ACTION_HOME:
|
||||
return LOG_STR("HOME");
|
||||
case HUMIDIFIER_ACTION_SLEEP:
|
||||
return LOG_STR("SLEEP");
|
||||
case HUMIDIFIER_ACTION_AUTO:
|
||||
return LOG_STR("AUTO");
|
||||
case HUMIDIFIER_ACTION_BABY:
|
||||
return LOG_STR("BABY");
|
||||
default:
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace humidifier
|
||||
} // namespace esphome
|
|
@ -0,0 +1,64 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace humidifier {
|
||||
|
||||
/// Enum for all modes a humidifier device can be in.
|
||||
enum HumidifierMode : uint8_t {
|
||||
/// The humidifier device is off
|
||||
HUMIDIFIER_MODE_OFF = 0,
|
||||
/// The MODE mode is set to Normal
|
||||
HUMIDIFIER_MODE_NORMAL = 1,
|
||||
/// The MODE mode is set to Eco
|
||||
HUMIDIFIER_MODE_ECO = 2,
|
||||
/// The MODE mode is set to Away
|
||||
HUMIDIFIER_MODE_AWAY = 3,
|
||||
/// The MODE mode is set to Boost
|
||||
HUMIDIFIER_MODE_BOOST = 4,
|
||||
/// The MODE mode is set to Comfort
|
||||
HUMIDIFIER_MODE_COMFORT = 5,
|
||||
/// The MODE mode is set to Home
|
||||
HUMIDIFIER_MODE_HOME = 6,
|
||||
/// The MODE mode is set to Sleep
|
||||
HUMIDIFIER_MODE_SLEEP = 7,
|
||||
/// The MODE mode is set to Auto
|
||||
HUMIDIFIER_MODE_AUTO = 8,
|
||||
/// The MODE mode is set to Baby
|
||||
HUMIDIFIER_MODE_BABY = 9,
|
||||
};
|
||||
|
||||
/// Enum for the current action of the humidifier device. Values match those of Humidifier Mode.
|
||||
enum HumidifierAction : uint8_t {
|
||||
/// The humidifier device is off (inactive or no power)
|
||||
HUMIDIFIER_ACTION_OFF = 0,
|
||||
/// The ACTION mode is set to Normal
|
||||
HUMIDIFIER_ACTION_NORMAL = 1,
|
||||
/// The ACTION mode is set to Eco
|
||||
HUMIDIFIER_ACTION_ECO = 2,
|
||||
/// The ACTION mode is set to Away
|
||||
HUMIDIFIER_ACTION_AWAY = 3,
|
||||
/// The ACTION mode is set to Boost
|
||||
HUMIDIFIER_ACTION_BOOST = 4,
|
||||
/// The ACTION mode is set to Comfort
|
||||
HUMIDIFIER_ACTION_COMFORT = 5,
|
||||
/// The ACTION mode is set to Home
|
||||
HUMIDIFIER_ACTION_HOME = 6,
|
||||
/// The ACTION mode is set to Sleep
|
||||
HUMIDIFIER_ACTION_SLEEP = 7,
|
||||
/// The ACTION mode is set to Auto
|
||||
HUMIDIFIER_ACTION_AUTO = 8,
|
||||
/// The ACTION mode is set to Baby
|
||||
HUMIDIFIER_ACTION_BABY = 9,
|
||||
};
|
||||
|
||||
/// Convert the given HumidifierMode to a human-readable string.
|
||||
const LogString *humidifier_mode_to_string(HumidifierMode mode);
|
||||
|
||||
/// Convert the given HumidifierAction to a human-readable string.
|
||||
const LogString *humidifier_action_to_string(HumidifierAction action);
|
||||
|
||||
} // namespace humidifier
|
||||
} // namespace esphome
|
|
@ -0,0 +1,15 @@
|
|||
#include "humidifier_traits.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace humidifier {
|
||||
|
||||
int8_t HumidifierTraits::get_target_humidity_accuracy_decimals() const {
|
||||
return step_to_accuracy_decimals(this->visual_target_humidity_step_);
|
||||
}
|
||||
|
||||
int8_t HumidifierTraits::get_current_humidity_accuracy_decimals() const {
|
||||
return step_to_accuracy_decimals(this->visual_current_humidity_step_);
|
||||
}
|
||||
|
||||
} // namespace humidifier
|
||||
} // namespace esphome
|
|
@ -0,0 +1,114 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "humidifier_mode.h"
|
||||
#include <set>
|
||||
|
||||
namespace esphome {
|
||||
namespace humidifier {
|
||||
|
||||
/** This class contains all static data for humidifier devices.
|
||||
*
|
||||
* All humidifier devices must support these features:
|
||||
* - OFF mode
|
||||
* - Target Humidity
|
||||
*
|
||||
* All other properties and modes are optional and the integration must mark
|
||||
* each of them as supported by setting the appropriate flag here.
|
||||
*
|
||||
* - supports current humidity - if the humidifier supports reporting a current humidity
|
||||
* - supports modes:
|
||||
* - on (turned on)
|
||||
* - normal
|
||||
* - eco
|
||||
* - away
|
||||
* - boost
|
||||
* - comfort
|
||||
* - home
|
||||
* - sleep
|
||||
* - auto
|
||||
* - baby
|
||||
* - supports action - if the humidifier supports reporting the active
|
||||
* current action of the device with the action property.
|
||||
* - off
|
||||
* - normal
|
||||
* - eco
|
||||
* - away
|
||||
* - boost
|
||||
* - comfort
|
||||
* - home
|
||||
* - sleep
|
||||
* - auto
|
||||
* - baby
|
||||
*
|
||||
* This class also contains static data for the humidifier device display:
|
||||
* - visual min/max humidity - tells the frontend what range of temperatures the humidifier
|
||||
* should display (gauge min/max values)
|
||||
* - humidity step - the step with which to increase/decrease target humidity.
|
||||
* This also affects with how many decimal places the humidity is shown
|
||||
*/
|
||||
|
||||
class HumidifierTraits {
|
||||
public:
|
||||
bool get_supports_current_humidity() const { return supports_current_humidity_; }
|
||||
void set_supports_current_humidity(bool supports_current_humidity) {
|
||||
supports_current_humidity_ = supports_current_humidity;
|
||||
}
|
||||
bool get_supports_target_humidity() const { return supports_target_humidity_; }
|
||||
void set_supports_target_humidity(bool supports_target_humidity) {
|
||||
supports_target_humidity_ = supports_target_humidity;
|
||||
}
|
||||
void set_supported_modes(std::set<HumidifierMode> modes) { supported_modes_ = std::move(modes); }
|
||||
void add_supported_mode(HumidifierMode mode) { supported_modes_.insert(mode); }
|
||||
void set_supports_normal(bool supports_normal) { set_mode_support_(HUMIDIFIER_MODE_NORMAL, supports_normal); }
|
||||
void set_supports_eco(bool supports_eco) { set_mode_support_(HUMIDIFIER_MODE_ECO, supports_eco); }
|
||||
void set_supports_away(bool supports_away) { set_mode_support_(HUMIDIFIER_MODE_AWAY, supports_away); }
|
||||
void set_supports_boost(bool supports_boost) { set_mode_support_(HUMIDIFIER_MODE_BOOST, supports_boost); }
|
||||
void set_supports_comfort(bool supports_comfort) { set_mode_support_(HUMIDIFIER_MODE_COMFORT, supports_comfort); }
|
||||
void set_supports_home(bool supports_home) { set_mode_support_(HUMIDIFIER_MODE_HOME, supports_home); }
|
||||
void set_supports_sleep(bool supports_sleep) { set_mode_support_(HUMIDIFIER_MODE_SLEEP, supports_sleep); }
|
||||
void set_supports_auto(bool supports_auto) { set_mode_support_(HUMIDIFIER_MODE_AUTO, supports_auto); }
|
||||
void set_supports_baby(bool supports_baby) { set_mode_support_(HUMIDIFIER_MODE_BABY, supports_baby); }
|
||||
bool supports_mode(HumidifierMode mode) const { return supported_modes_.count(mode); }
|
||||
std::set<HumidifierMode> get_supported_modes() const { return supported_modes_; }
|
||||
|
||||
void set_supports_action(bool supports_action) { supports_action_ = supports_action; }
|
||||
bool get_supports_action() const { return supports_action_; }
|
||||
|
||||
float get_visual_min_humidity() const { return visual_min_humidity_; }
|
||||
void set_visual_min_humidity(float visual_min_humidity) { visual_min_humidity_ = visual_min_humidity; }
|
||||
float get_visual_max_humidity() const { return visual_max_humidity_; }
|
||||
void set_visual_max_humidity(float visual_max_humidity) { visual_max_humidity_ = visual_max_humidity; }
|
||||
float get_visual_target_humidity_step() const { return visual_target_humidity_step_; }
|
||||
float get_visual_current_humidity_step() const { return visual_current_humidity_step_; }
|
||||
void set_visual_target_humidity_step(float humidity_step) { visual_target_humidity_step_ = humidity_step; }
|
||||
void set_visual_current_humidity_step(float humidity_step) { visual_current_humidity_step_ = humidity_step; }
|
||||
void set_visual_humidity_step(float humidity_step) {
|
||||
visual_target_humidity_step_ = humidity_step;
|
||||
visual_current_humidity_step_ = humidity_step;
|
||||
}
|
||||
int8_t get_target_humidity_accuracy_decimals() const;
|
||||
int8_t get_current_humidity_accuracy_decimals() const;
|
||||
|
||||
protected:
|
||||
void set_mode_support_(humidifier::HumidifierMode mode, bool supported) {
|
||||
if (supported) {
|
||||
supported_modes_.insert(mode);
|
||||
} else {
|
||||
supported_modes_.erase(mode);
|
||||
}
|
||||
}
|
||||
|
||||
bool supports_current_humidity_{false};
|
||||
bool supports_target_humidity_{false};
|
||||
std::set<humidifier::HumidifierMode> supported_modes_ = {humidifier::HUMIDIFIER_MODE_OFF};
|
||||
bool supports_action_{false};
|
||||
|
||||
float visual_min_humidity_{40};
|
||||
float visual_max_humidity_{80};
|
||||
float visual_target_humidity_step_{1};
|
||||
float visual_current_humidity_step_{1};
|
||||
};
|
||||
|
||||
} // namespace humidifier
|
||||
} // namespace esphome
|
|
@ -113,6 +113,7 @@ MQTTBinarySensorComponent = mqtt_ns.class_("MQTTBinarySensorComponent", MQTTComp
|
|||
MQTTClimateComponent = mqtt_ns.class_("MQTTClimateComponent", MQTTComponent)
|
||||
MQTTCoverComponent = mqtt_ns.class_("MQTTCoverComponent", MQTTComponent)
|
||||
MQTTFanComponent = mqtt_ns.class_("MQTTFanComponent", MQTTComponent)
|
||||
MQTTHumidifierComponent = mqtt_ns.class_("MQTTHumidifierComponent", MQTTComponent)
|
||||
MQTTJSONLightComponent = mqtt_ns.class_("MQTTJSONLightComponent", MQTTComponent)
|
||||
MQTTSensorComponent = mqtt_ns.class_("MQTTSensorComponent", MQTTComponent)
|
||||
MQTTSwitchComponent = mqtt_ns.class_("MQTTSwitchComponent", MQTTComponent)
|
||||
|
|
|
@ -0,0 +1,199 @@
|
|||
#include "mqtt_humidifier.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "mqtt_const.h"
|
||||
|
||||
#ifdef USE_MQTT
|
||||
#ifdef USE_HUMIDIFIER
|
||||
|
||||
namespace esphome {
|
||||
namespace mqtt {
|
||||
|
||||
static const char *const TAG = "mqtt.humidifier";
|
||||
|
||||
using namespace esphome::humidifier;
|
||||
|
||||
void MQTTHumidifierComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
|
||||
auto traits = this->device_->get_traits();
|
||||
// current_humidity_topic
|
||||
if (traits.get_supports_current_humidity()) {
|
||||
root[MQTT_CURRENT_HUMIDITY_TOPIC] = this->get_current_humidity_state_topic();
|
||||
}
|
||||
// mode_command_topic
|
||||
root[MQTT_MODE_COMMAND_TOPIC] = this->get_mode_command_topic();
|
||||
// mode_state_topic
|
||||
root[MQTT_MODE_STATE_TOPIC] = this->get_mode_state_topic();
|
||||
// modes
|
||||
JsonArray modes = root.createNestedArray(MQTT_MODES);
|
||||
// sort array for nice UI in HA
|
||||
if (traits.supports_mode(HUMIDIFIER_MODE_NORMAL))
|
||||
modes.add("normal");
|
||||
modes.add("off");
|
||||
if (traits.supports_mode(HUMIDIFIER_MODE_ECO))
|
||||
modes.add("eco");
|
||||
if (traits.supports_mode(HUMIDIFIER_MODE_AWAY))
|
||||
modes.add("away");
|
||||
if (traits.supports_mode(HUMIDIFIER_MODE_BOOST))
|
||||
modes.add("boost");
|
||||
if (traits.supports_mode(HUMIDIFIER_MODE_COMFORT))
|
||||
modes.add("comfort");
|
||||
if (traits.supports_mode(HUMIDIFIER_MODE_HOME))
|
||||
modes.add("home");
|
||||
if (traits.supports_mode(HUMIDIFIER_MODE_SLEEP))
|
||||
modes.add("sleep");
|
||||
if (traits.supports_mode(HUMIDIFIER_MODE_AUTO))
|
||||
modes.add("auto");
|
||||
if (traits.supports_mode(HUMIDIFIER_MODE_BOOST))
|
||||
modes.add("baby");
|
||||
|
||||
if (traits.get_supports_target_humidity()) {
|
||||
// humidity_command_topic
|
||||
root[MQTT_TARGET_HUMIDITY_COMMAND_TOPIC] = this->get_target_humidity_command_topic();
|
||||
// humidity_state_topic
|
||||
root[MQTT_TARGET_HUMIDITY_STATE_TOPIC] = this->get_target_humidity_state_topic();
|
||||
}
|
||||
|
||||
// min_humidity
|
||||
root[MQTT_MIN_HUMIDITY] = traits.get_visual_min_humidity();
|
||||
// max_humidity
|
||||
root[MQTT_MAX_HUMIDITY] = traits.get_visual_max_humidity();
|
||||
// humidity_step
|
||||
root["humi_step"] = traits.get_visual_target_humidity_step();
|
||||
// // humidity units are always coerced to percentage internally
|
||||
// root[MQTT_HUMIDITY_UNIT] = "%";
|
||||
|
||||
if (traits.get_supports_action()) {
|
||||
// action_topic
|
||||
root[MQTT_ACTION_TOPIC] = this->get_action_state_topic();
|
||||
}
|
||||
|
||||
config.state_topic = false;
|
||||
config.command_topic = false;
|
||||
}
|
||||
void MQTTHumidifierComponent::setup() {
|
||||
auto traits = this->device_->get_traits();
|
||||
this->subscribe(this->get_mode_command_topic(), [this](const std::string &topic, const std::string &payload) {
|
||||
auto call = this->device_->make_call();
|
||||
call.set_mode(payload);
|
||||
call.perform();
|
||||
});
|
||||
|
||||
if (traits.get_supports_target_humidity()) {
|
||||
this->subscribe(this->get_target_humidity_command_topic(),
|
||||
[this](const std::string &topic, const std::string &payload) {
|
||||
auto val = parse_number<float>(payload);
|
||||
if (!val.has_value()) {
|
||||
ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str());
|
||||
return;
|
||||
}
|
||||
auto call = this->device_->make_call();
|
||||
call.set_target_humidity(*val);
|
||||
call.perform();
|
||||
});
|
||||
}
|
||||
|
||||
this->device_->add_on_state_callback([this](Humidifier & /*unused*/) { this->publish_state_(); });
|
||||
}
|
||||
MQTTHumidifierComponent::MQTTHumidifierComponent(Humidifier *device) : device_(device) {}
|
||||
bool MQTTHumidifierComponent::send_initial_state() { return this->publish_state_(); }
|
||||
std::string MQTTHumidifierComponent::component_type() const { return "humidifier"; }
|
||||
const EntityBase *MQTTHumidifierComponent::get_entity() const { return this->device_; }
|
||||
|
||||
bool MQTTHumidifierComponent::publish_state_() {
|
||||
auto traits = this->device_->get_traits();
|
||||
// mode
|
||||
const char *mode_s = "";
|
||||
switch (this->device_->mode) {
|
||||
case HUMIDIFIER_MODE_OFF:
|
||||
mode_s = "off";
|
||||
break;
|
||||
case HUMIDIFIER_MODE_NORMAL:
|
||||
mode_s = "normal";
|
||||
break;
|
||||
case HUMIDIFIER_MODE_ECO:
|
||||
mode_s = "eco";
|
||||
break;
|
||||
case HUMIDIFIER_MODE_AWAY:
|
||||
mode_s = "away";
|
||||
break;
|
||||
case HUMIDIFIER_MODE_BOOST:
|
||||
mode_s = "boost";
|
||||
break;
|
||||
case HUMIDIFIER_MODE_COMFORT:
|
||||
mode_s = "comfort";
|
||||
break;
|
||||
case HUMIDIFIER_MODE_HOME:
|
||||
mode_s = "home";
|
||||
break;
|
||||
case HUMIDIFIER_MODE_SLEEP:
|
||||
mode_s = "sleep";
|
||||
break;
|
||||
case HUMIDIFIER_MODE_AUTO:
|
||||
mode_s = "auto";
|
||||
break;
|
||||
case HUMIDIFIER_MODE_BABY:
|
||||
mode_s = "baby";
|
||||
break;
|
||||
}
|
||||
bool success = true;
|
||||
if (!this->publish(this->get_mode_state_topic(), mode_s))
|
||||
success = false;
|
||||
int8_t target_accuracy = traits.get_target_humidity_accuracy_decimals();
|
||||
int8_t current_accuracy = traits.get_current_humidity_accuracy_decimals();
|
||||
if (traits.get_supports_current_humidity() && !std::isnan(this->device_->current_humidity)) {
|
||||
std::string payload = value_accuracy_to_string(this->device_->current_humidity, current_accuracy);
|
||||
if (!this->publish(this->get_current_humidity_state_topic(), payload))
|
||||
success = false;
|
||||
}
|
||||
if (traits.get_supports_target_humidity()) {
|
||||
std::string payload = value_accuracy_to_string(this->device_->target_humidity, target_accuracy);
|
||||
if (!this->publish(this->get_target_humidity_state_topic(), payload))
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (traits.get_supports_action()) {
|
||||
const char *payload = "unknown";
|
||||
switch (this->device_->action) {
|
||||
case HUMIDIFIER_ACTION_OFF:
|
||||
payload = "off";
|
||||
break;
|
||||
case HUMIDIFIER_ACTION_NORMAL:
|
||||
payload = "normal";
|
||||
break;
|
||||
case HUMIDIFIER_ACTION_ECO:
|
||||
payload = "eco";
|
||||
break;
|
||||
case HUMIDIFIER_ACTION_AWAY:
|
||||
payload = "away";
|
||||
break;
|
||||
case HUMIDIFIER_ACTION_BOOST:
|
||||
payload = "boost";
|
||||
break;
|
||||
case HUMIDIFIER_ACTION_COMFORT:
|
||||
payload = "comfort";
|
||||
break;
|
||||
case HUMIDIFIER_ACTION_HOME:
|
||||
payload = "home";
|
||||
break;
|
||||
case HUMIDIFIER_ACTION_SLEEP:
|
||||
payload = "sleep";
|
||||
break;
|
||||
case HUMIDIFIER_ACTION_AUTO:
|
||||
payload = "auto";
|
||||
break;
|
||||
case HUMIDIFIER_ACTION_BABY:
|
||||
payload = "baby";
|
||||
break;
|
||||
}
|
||||
if (!this->publish(this->get_action_state_topic(), payload))
|
||||
success = false;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
} // namespace mqtt
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
#endif // USE_MQTT
|
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_MQTT
|
||||
#ifdef USE_HUMIDIFIER
|
||||
|
||||
#include "esphome/components/humidifier/humidifier.h"
|
||||
#include "mqtt_component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mqtt {
|
||||
|
||||
class MQTTHumidifierComponent : public mqtt::MQTTComponent {
|
||||
public:
|
||||
MQTTHumidifierComponent(humidifier::Humidifier *device);
|
||||
void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override;
|
||||
bool send_initial_state() override;
|
||||
std::string component_type() const override;
|
||||
void setup() override;
|
||||
|
||||
MQTT_COMPONENT_CUSTOM_TOPIC(current_humidity, state)
|
||||
MQTT_COMPONENT_CUSTOM_TOPIC(mode, state)
|
||||
MQTT_COMPONENT_CUSTOM_TOPIC(mode, command)
|
||||
MQTT_COMPONENT_CUSTOM_TOPIC(target_humidity, state)
|
||||
MQTT_COMPONENT_CUSTOM_TOPIC(target_humidity, command)
|
||||
MQTT_COMPONENT_CUSTOM_TOPIC(action, state)
|
||||
|
||||
protected:
|
||||
const EntityBase *get_entity() const override;
|
||||
|
||||
bool publish_state_();
|
||||
|
||||
humidifier::Humidifier *device_;
|
||||
};
|
||||
|
||||
} // namespace mqtt
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
#endif // USE_MQTT
|
|
@ -104,6 +104,15 @@ bool ListEntitiesIterator::on_climate(climate::Climate *climate) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_HUMIDIFIER
|
||||
bool ListEntitiesIterator::on_humidifier(humidifier::Humidifier *humidifier) {
|
||||
if (this->web_server_->events_.count() == 0)
|
||||
return true;
|
||||
this->web_server_->events_.send(this->web_server_->humidifier_json(humidifier, DETAIL_ALL).c_str(), "state");
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
bool ListEntitiesIterator::on_number(number::Number *number) {
|
||||
if (this->web_server_->events_.count() == 0)
|
||||
|
|
|
@ -38,6 +38,9 @@ class ListEntitiesIterator : public ComponentIterator {
|
|||
#ifdef USE_CLIMATE
|
||||
bool on_climate(climate::Climate *climate) override;
|
||||
#endif
|
||||
#ifdef USE_HUMIDIFIER
|
||||
bool on_humidifier(humidifier::Humidifier *humidifier) override;
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
bool on_number(number::Number *number) override;
|
||||
#endif
|
||||
|
|
|
@ -26,6 +26,10 @@
|
|||
#include "esphome/components/climate/climate.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_HUMIDIFIER
|
||||
#include "esphome/components/humidifier/humidifier.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_WEBSERVER_LOCAL
|
||||
#if USE_WEBSERVER_VERSION == 2
|
||||
#include "server_index_v2.h"
|
||||
|
@ -341,6 +345,13 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_HUMIDIFIER
|
||||
for (auto *obj : App.get_humidifiers()) {
|
||||
if (this->include_internal_ || !obj->is_internal())
|
||||
write_row(stream, obj, "humidifier", "");
|
||||
}
|
||||
#endif
|
||||
|
||||
stream->print(F("</tbody></table><p>See <a href=\"https://esphome.io/web-api/index.html\">ESPHome Web API</a> for "
|
||||
"REST API documentation.</p>"));
|
||||
if (this->allow_ota_) {
|
||||
|
@ -1277,6 +1288,90 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_HUMIDIFIER
|
||||
void WebServer::on_humidifier_update(humidifier::Humidifier *obj) {
|
||||
if (this->events_.count() == 0)
|
||||
return;
|
||||
this->events_.send(this->humidifier_json(obj, DETAIL_STATE).c_str(), "state");
|
||||
}
|
||||
|
||||
void WebServer::handle_humidifier_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
for (auto *obj : App.get_humidifiers()) {
|
||||
if (obj->get_object_id() != match.id)
|
||||
continue;
|
||||
|
||||
if (request->method() == HTTP_GET && match.method.empty()) {
|
||||
std::string data = this->humidifier_json(obj, DETAIL_STATE);
|
||||
request->send(200, "application/json", data.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (match.method != "set") {
|
||||
request->send(404);
|
||||
return;
|
||||
}
|
||||
|
||||
auto call = obj->make_call();
|
||||
|
||||
if (request->hasParam("mode")) {
|
||||
auto mode = request->getParam("mode")->value();
|
||||
call.set_mode(mode.c_str());
|
||||
}
|
||||
|
||||
if (request->hasParam("target_humidity")) {
|
||||
auto target_humidity = parse_number<float>(request->getParam("target_humidity")->value().c_str());
|
||||
if (target_humidity.has_value())
|
||||
call.set_target_humidity(*target_humidity);
|
||||
}
|
||||
|
||||
this->schedule_([call]() mutable { call.perform(); });
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
request->send(404);
|
||||
}
|
||||
|
||||
std::string WebServer::humidifier_json(humidifier::Humidifier *obj, JsonDetail start_config) {
|
||||
return json::build_json([obj, start_config](JsonObject root) {
|
||||
set_json_id(root, obj, "humidifier-" + obj->get_object_id(), start_config);
|
||||
const auto traits = obj->get_traits();
|
||||
int8_t target_accuracy = traits.get_target_humidity_accuracy_decimals();
|
||||
int8_t current_accuracy = traits.get_current_humidity_accuracy_decimals();
|
||||
char buf[16];
|
||||
|
||||
if (start_config == DETAIL_ALL) {
|
||||
JsonArray opt = root.createNestedArray("modes");
|
||||
for (humidifier::HumidifierMode m : traits.get_supported_modes())
|
||||
opt.add(PSTR_LOCAL(humidifier::humidifier_mode_to_string(m)));
|
||||
}
|
||||
|
||||
bool has_state = false;
|
||||
root["mode"] = PSTR_LOCAL(humidifier_mode_to_string(obj->mode));
|
||||
root["max_humidity"] = value_accuracy_to_string(traits.get_visual_max_humidity(), target_accuracy);
|
||||
root["min_humidity"] = value_accuracy_to_string(traits.get_visual_min_humidity(), target_accuracy);
|
||||
root["step"] = traits.get_visual_target_humidity_step();
|
||||
if (traits.get_supports_action()) {
|
||||
root["action"] = PSTR_LOCAL(humidifier_action_to_string(obj->action));
|
||||
root["state"] = root["action"];
|
||||
has_state = true;
|
||||
}
|
||||
|
||||
if (traits.get_supports_current_humidity()) {
|
||||
if (!std::isnan(obj->current_humidity)) {
|
||||
root["current_humidity"] = value_accuracy_to_string(obj->current_humidity, current_accuracy);
|
||||
} else {
|
||||
root["current_humidity"] = "NA";
|
||||
}
|
||||
}
|
||||
if (traits.get_supports_target_humidity()) {
|
||||
root["target_humidity"] = value_accuracy_to_string(obj->target_humidity, target_accuracy);
|
||||
if (!has_state)
|
||||
root["state"] = root["target_humidity"];
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
void WebServer::on_lock_update(lock::Lock *obj) {
|
||||
if (this->events_.count() == 0)
|
||||
|
@ -1533,6 +1628,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) {
|
|||
return true;
|
||||
#endif
|
||||
|
||||
#ifdef USE_HUMIDIFIER
|
||||
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "humidifier")
|
||||
return true;
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "lock")
|
||||
return true;
|
||||
|
@ -1683,6 +1783,13 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_HUMIDIFIER
|
||||
if (match.domain == "humidifier") {
|
||||
this->handle_humidifier_request(request, match);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
if (match.domain == "lock") {
|
||||
this->handle_lock_request(request, match);
|
||||
|
|
|
@ -275,6 +275,15 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
|||
std::string climate_json(climate::Climate *obj, JsonDetail start_config);
|
||||
#endif
|
||||
|
||||
#ifdef USE_HUMIDIFIER
|
||||
void on_humidifier_update(humidifier::Humidifier *obj) override;
|
||||
/// Handle a humidifier request under '/humidifier/<id>'.
|
||||
void handle_humidifier_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
|
||||
/// Dump the humidifier details
|
||||
std::string humidifier_json(humidifier::Humidifier *obj, JsonDetail start_config);
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
void on_lock_update(lock::Lock *obj) override;
|
||||
|
||||
|
|
|
@ -190,6 +190,7 @@ CONF_DEBUG = "debug"
|
|||
CONF_DECAY_MODE = "decay_mode"
|
||||
CONF_DECELERATION = "deceleration"
|
||||
CONF_DEFAULT_MODE = "default_mode"
|
||||
CONF_DEFAULT_TARGET_HUMIDITY = "default_target_humidity"
|
||||
CONF_DEFAULT_TARGET_TEMPERATURE_HIGH = "default_target_temperature_high"
|
||||
CONF_DEFAULT_TARGET_TEMPERATURE_LOW = "default_target_temperature_low"
|
||||
CONF_DEFAULT_TRANSITION_LENGTH = "default_transition_length"
|
||||
|
@ -339,6 +340,7 @@ CONF_HOUR = "hour"
|
|||
CONF_HOURS = "hours"
|
||||
CONF_HUMIDITY = "humidity"
|
||||
CONF_HUMIDITY_SENSOR = "humidity_sensor"
|
||||
CONF_HUMIDITY_STEP = "humidity_step"
|
||||
CONF_HYSTERESIS = "hysteresis"
|
||||
CONF_I2C = "i2c"
|
||||
CONF_I2C_ID = "i2c_id"
|
||||
|
@ -431,6 +433,7 @@ CONF_MAX_COOLING_RUN_TIME = "max_cooling_run_time"
|
|||
CONF_MAX_CURRENT = "max_current"
|
||||
CONF_MAX_DURATION = "max_duration"
|
||||
CONF_MAX_HEATING_RUN_TIME = "max_heating_run_time"
|
||||
CONF_MAX_HUMIDITY = "max_humidity"
|
||||
CONF_MAX_LENGTH = "max_length"
|
||||
CONF_MAX_LEVEL = "max_level"
|
||||
CONF_MAX_POWER = "max_power"
|
||||
|
@ -456,6 +459,7 @@ CONF_MIN_FANNING_OFF_TIME = "min_fanning_off_time"
|
|||
CONF_MIN_FANNING_RUN_TIME = "min_fanning_run_time"
|
||||
CONF_MIN_HEATING_OFF_TIME = "min_heating_off_time"
|
||||
CONF_MIN_HEATING_RUN_TIME = "min_heating_run_time"
|
||||
CONF_MIN_HUMIDITY = "min_humidity"
|
||||
CONF_MIN_IDLE_TIME = "min_idle_time"
|
||||
CONF_MIN_IPV6_ADDR_COUNT = "min_ipv6_addr_count"
|
||||
CONF_MIN_LENGTH = "min_length"
|
||||
|
@ -791,6 +795,7 @@ CONF_SYNC = "sync"
|
|||
CONF_TABLET = "tablet"
|
||||
CONF_TAG = "tag"
|
||||
CONF_TARGET = "target"
|
||||
CONF_TARGET_HUMIDITY = "target_humidity"
|
||||
CONF_TARGET_HUMIDITY_COMMAND_TOPIC = "target_humidity_command_topic"
|
||||
CONF_TARGET_HUMIDITY_STATE_TOPIC = "target_humidity_state_topic"
|
||||
CONF_TARGET_TEMPERATURE = "target_temperature"
|
||||
|
|
|
@ -30,6 +30,9 @@
|
|||
#ifdef USE_CLIMATE
|
||||
#include "esphome/components/climate/climate.h"
|
||||
#endif
|
||||
#ifdef USE_HUMIDIFIER
|
||||
#include "esphome/components/humidifier/humidifier.h"
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
#include "esphome/components/light/light_state.h"
|
||||
#endif
|
||||
|
@ -128,6 +131,10 @@ class Application {
|
|||
void register_climate(climate::Climate *climate) { this->climates_.push_back(climate); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_HUMIDIFIER
|
||||
void register_humidifier(humidifier::Humidifier *humidifier) { this->humidifiers_.push_back(humidifier); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
void register_light(light::LightState *light) { this->lights_.push_back(light); }
|
||||
#endif
|
||||
|
@ -317,6 +324,15 @@ class Application {
|
|||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_HUMIDIFIER
|
||||
const std::vector<humidifier::Humidifier *> &get_humidifiers() { return this->humidifiers_; }
|
||||
humidifier::Humidifier *get_humidifier_by_key(uint32_t key, bool include_internal = false) {
|
||||
for (auto *obj : this->humidifiers_)
|
||||
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
|
||||
return obj;
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
const std::vector<number::Number *> &get_numbers() { return this->numbers_; }
|
||||
number::Number *get_number_by_key(uint32_t key, bool include_internal = false) {
|
||||
|
@ -462,6 +478,9 @@ class Application {
|
|||
#ifdef USE_CLIMATE
|
||||
std::vector<climate::Climate *> climates_{};
|
||||
#endif
|
||||
#ifdef USE_HUMIDIFIER
|
||||
std::vector<humidifier::Humidifier *> humidifiers_{};
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
std::vector<light::LightState *> lights_{};
|
||||
#endif
|
||||
|
|
|
@ -187,6 +187,21 @@ void ComponentIterator::advance() {
|
|||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_HUMIDIFIER
|
||||
case IteratorState::HUMIDIFIER:
|
||||
if (this->at_ >= App.get_humidifiers().size()) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *humidifier = App.get_humidifiers()[this->at_];
|
||||
if (humidifier->is_internal() && !this->include_internal_) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
success = this->on_humidifier(humidifier);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
case IteratorState::NUMBER:
|
||||
if (this->at_ >= App.get_numbers().size()) {
|
||||
|
|
|
@ -54,6 +54,9 @@ class ComponentIterator {
|
|||
#ifdef USE_CLIMATE
|
||||
virtual bool on_climate(climate::Climate *climate) = 0;
|
||||
#endif
|
||||
#ifdef USE_HUMIDIFIER
|
||||
virtual bool on_humidifier(humidifier::Humidifier *humidifier) = 0;
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
virtual bool on_number(number::Number *number) = 0;
|
||||
#endif
|
||||
|
@ -126,6 +129,9 @@ class ComponentIterator {
|
|||
#ifdef USE_CLIMATE
|
||||
CLIMATE,
|
||||
#endif
|
||||
#ifdef USE_HUMIDIFIER
|
||||
HUMIDIFIER,
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
NUMBER,
|
||||
#endif
|
||||
|
|
|
@ -53,6 +53,12 @@ void Controller::setup_controller(bool include_internal) {
|
|||
obj->add_on_state_callback([this, obj](climate::Climate & /*unused*/) { this->on_climate_update(obj); });
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_HUMIDIFIER
|
||||
for (auto *obj : App.get_humidifiers()) {
|
||||
if (include_internal || !obj->is_internal())
|
||||
obj->add_on_state_callback([this, obj](humidifier::Humidifier & /*unused*/) { this->on_humidifier_update(obj); });
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
for (auto *obj : App.get_numbers()) {
|
||||
if (include_internal || !obj->is_internal())
|
||||
|
|
|
@ -28,6 +28,9 @@
|
|||
#ifdef USE_CLIMATE
|
||||
#include "esphome/components/climate/climate.h"
|
||||
#endif
|
||||
#ifdef USE_HUMIDIFIER
|
||||
#include "esphome/components/humidifier/humidifier.h"
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
#include "esphome/components/number/number.h"
|
||||
#endif
|
||||
|
@ -91,6 +94,9 @@ class Controller {
|
|||
#ifdef USE_CLIMATE
|
||||
virtual void on_climate_update(climate::Climate *obj){};
|
||||
#endif
|
||||
#ifdef USE_HUMIDIFIER
|
||||
virtual void on_humidifier_update(humidifier::Humidifier *obj){};
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
virtual void on_number_update(number::Number *obj, float state){};
|
||||
#endif
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#define USE_FAN
|
||||
#define USE_GRAPH
|
||||
#define USE_HOMEASSISTANT_TIME
|
||||
#define USE_HUMIDIFIER
|
||||
#define USE_JSON
|
||||
#define USE_LIGHT
|
||||
#define USE_LOCK
|
||||
|
|
|
@ -627,6 +627,7 @@ def lint_trailing_whitespace(fname, match):
|
|||
"esphome/components/display/display.h",
|
||||
"esphome/components/event/event.h",
|
||||
"esphome/components/fan/fan.h",
|
||||
"esphome/components/humidifier/humidifier.h",
|
||||
"esphome/components/i2c/i2c.h",
|
||||
"esphome/components/lock/lock.h",
|
||||
"esphome/components/mqtt/mqtt_component.h",
|
||||
|
|
|
@ -1427,3 +1427,13 @@ alarm_control_panel:
|
|||
on_cleared:
|
||||
then:
|
||||
- logger.log: "### CLEARED ###"
|
||||
|
||||
humidifier:
|
||||
- platform: generic_humidifier
|
||||
name: "HA Livingroom Humidifier"
|
||||
sensor: ha_hello_world
|
||||
default_target_humidity: 65
|
||||
normal_action:
|
||||
- output.turn_on: out
|
||||
eco_action:
|
||||
- output.turn_off: out
|
||||
|
|
Loading…
Reference in New Issue