This commit is contained in:
Jaco Malan 2024-05-02 13:54:25 +12:00 committed by GitHub
commit 47448f1cf2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
43 changed files with 2445 additions and 3 deletions

View File

@ -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

View File

@ -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;

View File

@ -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_)

View File

@ -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);

View File

@ -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: {

View File

@ -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{};

View File

@ -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()) {

View File

@ -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

View File

@ -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())

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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)?"

View File

@ -0,0 +1 @@
CODEOWNERS = ["@Jaco1990"]

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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"

View File

@ -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

View File

@ -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()) {

View File

@ -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

View File

@ -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())

View File

@ -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

View File

@ -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

View File

@ -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",

View File

@ -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