diff --git a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp index 263e5547787..dc8f75d8f55 100644 --- a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp +++ b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp @@ -13,6 +13,33 @@ static const char *const TAG = "mqtt.alarm_control_panel"; using namespace esphome::alarm_control_panel; +static ProgmemStr alarm_state_to_mqtt_str(AlarmControlPanelState state) { + switch (state) { + case ACP_STATE_DISARMED: + return ESPHOME_F("disarmed"); + case ACP_STATE_ARMED_HOME: + return ESPHOME_F("armed_home"); + case ACP_STATE_ARMED_AWAY: + return ESPHOME_F("armed_away"); + case ACP_STATE_ARMED_NIGHT: + return ESPHOME_F("armed_night"); + case ACP_STATE_ARMED_VACATION: + return ESPHOME_F("armed_vacation"); + case ACP_STATE_ARMED_CUSTOM_BYPASS: + return ESPHOME_F("armed_custom_bypass"); + case ACP_STATE_PENDING: + return ESPHOME_F("pending"); + case ACP_STATE_ARMING: + return ESPHOME_F("arming"); + case ACP_STATE_DISARMING: + return ESPHOME_F("disarming"); + case ACP_STATE_TRIGGERED: + return ESPHOME_F("triggered"); + default: + return ESPHOME_F("unknown"); + } +} + MQTTAlarmControlPanelComponent::MQTTAlarmControlPanelComponent(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {} void MQTTAlarmControlPanelComponent::setup() { @@ -85,43 +112,9 @@ const EntityBase *MQTTAlarmControlPanelComponent::get_entity() const { return th bool MQTTAlarmControlPanelComponent::send_initial_state() { return this->publish_state(); } bool MQTTAlarmControlPanelComponent::publish_state() { - const char *state_s; - switch (this->alarm_control_panel_->get_state()) { - case ACP_STATE_DISARMED: - state_s = "disarmed"; - break; - case ACP_STATE_ARMED_HOME: - state_s = "armed_home"; - break; - case ACP_STATE_ARMED_AWAY: - state_s = "armed_away"; - break; - case ACP_STATE_ARMED_NIGHT: - state_s = "armed_night"; - break; - case ACP_STATE_ARMED_VACATION: - state_s = "armed_vacation"; - break; - case ACP_STATE_ARMED_CUSTOM_BYPASS: - state_s = "armed_custom_bypass"; - break; - case ACP_STATE_PENDING: - state_s = "pending"; - break; - case ACP_STATE_ARMING: - state_s = "arming"; - break; - case ACP_STATE_DISARMING: - state_s = "disarming"; - break; - case ACP_STATE_TRIGGERED: - state_s = "triggered"; - break; - default: - state_s = "unknown"; - } char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN]; - return this->publish(this->get_state_topic_to_(topic_buf), state_s); + return this->publish(this->get_state_topic_to_(topic_buf), + alarm_state_to_mqtt_str(this->alarm_control_panel_->get_state())); } } // namespace esphome::mqtt diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 37d643f9e71..673593ef842 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -1,5 +1,6 @@ #include "mqtt_climate.h" #include "esphome/core/log.h" +#include "esphome/core/progmem.h" #include "mqtt_const.h" @@ -12,6 +13,111 @@ static const char *const TAG = "mqtt.climate"; using namespace esphome::climate; +static ProgmemStr climate_mode_to_mqtt_str(ClimateMode mode) { + switch (mode) { + case CLIMATE_MODE_OFF: + return ESPHOME_F("off"); + case CLIMATE_MODE_HEAT_COOL: + return ESPHOME_F("heat_cool"); + case CLIMATE_MODE_AUTO: + return ESPHOME_F("auto"); + case CLIMATE_MODE_COOL: + return ESPHOME_F("cool"); + case CLIMATE_MODE_HEAT: + return ESPHOME_F("heat"); + case CLIMATE_MODE_FAN_ONLY: + return ESPHOME_F("fan_only"); + case CLIMATE_MODE_DRY: + return ESPHOME_F("dry"); + default: + return ESPHOME_F("unknown"); + } +} + +static ProgmemStr climate_action_to_mqtt_str(ClimateAction action) { + switch (action) { + case CLIMATE_ACTION_OFF: + return ESPHOME_F("off"); + case CLIMATE_ACTION_COOLING: + return ESPHOME_F("cooling"); + case CLIMATE_ACTION_HEATING: + return ESPHOME_F("heating"); + case CLIMATE_ACTION_IDLE: + return ESPHOME_F("idle"); + case CLIMATE_ACTION_DRYING: + return ESPHOME_F("drying"); + case CLIMATE_ACTION_FAN: + return ESPHOME_F("fan"); + default: + return ESPHOME_F("unknown"); + } +} + +static ProgmemStr climate_fan_mode_to_mqtt_str(ClimateFanMode fan_mode) { + switch (fan_mode) { + case CLIMATE_FAN_ON: + return ESPHOME_F("on"); + case CLIMATE_FAN_OFF: + return ESPHOME_F("off"); + case CLIMATE_FAN_AUTO: + return ESPHOME_F("auto"); + case CLIMATE_FAN_LOW: + return ESPHOME_F("low"); + case CLIMATE_FAN_MEDIUM: + return ESPHOME_F("medium"); + case CLIMATE_FAN_HIGH: + return ESPHOME_F("high"); + case CLIMATE_FAN_MIDDLE: + return ESPHOME_F("middle"); + case CLIMATE_FAN_FOCUS: + return ESPHOME_F("focus"); + case CLIMATE_FAN_DIFFUSE: + return ESPHOME_F("diffuse"); + case CLIMATE_FAN_QUIET: + return ESPHOME_F("quiet"); + default: + return ESPHOME_F("unknown"); + } +} + +static ProgmemStr climate_swing_mode_to_mqtt_str(ClimateSwingMode swing_mode) { + switch (swing_mode) { + case CLIMATE_SWING_OFF: + return ESPHOME_F("off"); + case CLIMATE_SWING_BOTH: + return ESPHOME_F("both"); + case CLIMATE_SWING_VERTICAL: + return ESPHOME_F("vertical"); + case CLIMATE_SWING_HORIZONTAL: + return ESPHOME_F("horizontal"); + default: + return ESPHOME_F("unknown"); + } +} + +static ProgmemStr climate_preset_to_mqtt_str(ClimatePreset preset) { + switch (preset) { + case CLIMATE_PRESET_NONE: + return ESPHOME_F("none"); + case CLIMATE_PRESET_HOME: + return ESPHOME_F("home"); + case CLIMATE_PRESET_ECO: + return ESPHOME_F("eco"); + case CLIMATE_PRESET_AWAY: + return ESPHOME_F("away"); + case CLIMATE_PRESET_BOOST: + return ESPHOME_F("boost"); + case CLIMATE_PRESET_COMFORT: + return ESPHOME_F("comfort"); + case CLIMATE_PRESET_SLEEP: + return ESPHOME_F("sleep"); + case CLIMATE_PRESET_ACTIVITY: + return ESPHOME_F("activity"); + default: + return ESPHOME_F("unknown"); + } +} + void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson auto traits = this->device_->get_traits(); @@ -260,34 +366,8 @@ const EntityBase *MQTTClimateComponent::get_entity() const { return this->device bool MQTTClimateComponent::publish_state_() { auto traits = this->device_->get_traits(); // mode - const char *mode_s; - switch (this->device_->mode) { - case CLIMATE_MODE_OFF: - mode_s = "off"; - break; - case CLIMATE_MODE_AUTO: - mode_s = "auto"; - break; - case CLIMATE_MODE_COOL: - mode_s = "cool"; - break; - case CLIMATE_MODE_HEAT: - mode_s = "heat"; - break; - case CLIMATE_MODE_FAN_ONLY: - mode_s = "fan_only"; - break; - case CLIMATE_MODE_DRY: - mode_s = "dry"; - break; - case CLIMATE_MODE_HEAT_COOL: - mode_s = "heat_cool"; - break; - default: - mode_s = "unknown"; - } bool success = true; - if (!this->publish(this->get_mode_state_topic(), mode_s)) + if (!this->publish(this->get_mode_state_topic(), climate_mode_to_mqtt_str(this->device_->mode))) success = false; int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals(); int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals(); @@ -327,134 +407,37 @@ bool MQTTClimateComponent::publish_state_() { } if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) { - std::string payload; - if (this->device_->preset.has_value()) { - switch (this->device_->preset.value()) { - case CLIMATE_PRESET_NONE: - payload = "none"; - break; - case CLIMATE_PRESET_HOME: - payload = "home"; - break; - case CLIMATE_PRESET_AWAY: - payload = "away"; - break; - case CLIMATE_PRESET_BOOST: - payload = "boost"; - break; - case CLIMATE_PRESET_COMFORT: - payload = "comfort"; - break; - case CLIMATE_PRESET_ECO: - payload = "eco"; - break; - case CLIMATE_PRESET_SLEEP: - payload = "sleep"; - break; - case CLIMATE_PRESET_ACTIVITY: - payload = "activity"; - break; - default: - payload = "unknown"; - } - } - if (this->device_->has_custom_preset()) - payload = this->device_->get_custom_preset().c_str(); - if (!this->publish(this->get_preset_state_topic(), payload)) + if (this->device_->has_custom_preset()) { + if (!this->publish(this->get_preset_state_topic(), this->device_->get_custom_preset())) + success = false; + } else if (this->device_->preset.has_value()) { + if (!this->publish(this->get_preset_state_topic(), climate_preset_to_mqtt_str(this->device_->preset.value()))) + success = false; + } else if (!this->publish(this->get_preset_state_topic(), "")) { success = false; + } } if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION)) { - const char *payload; - switch (this->device_->action) { - case CLIMATE_ACTION_OFF: - payload = "off"; - break; - case CLIMATE_ACTION_COOLING: - payload = "cooling"; - break; - case CLIMATE_ACTION_HEATING: - payload = "heating"; - break; - case CLIMATE_ACTION_IDLE: - payload = "idle"; - break; - case CLIMATE_ACTION_DRYING: - payload = "drying"; - break; - case CLIMATE_ACTION_FAN: - payload = "fan"; - break; - default: - payload = "unknown"; - } - if (!this->publish(this->get_action_state_topic(), payload)) + if (!this->publish(this->get_action_state_topic(), climate_action_to_mqtt_str(this->device_->action))) success = false; } if (traits.get_supports_fan_modes()) { - std::string payload; - if (this->device_->fan_mode.has_value()) { - switch (this->device_->fan_mode.value()) { - case CLIMATE_FAN_ON: - payload = "on"; - break; - case CLIMATE_FAN_OFF: - payload = "off"; - break; - case CLIMATE_FAN_AUTO: - payload = "auto"; - break; - case CLIMATE_FAN_LOW: - payload = "low"; - break; - case CLIMATE_FAN_MEDIUM: - payload = "medium"; - break; - case CLIMATE_FAN_HIGH: - payload = "high"; - break; - case CLIMATE_FAN_MIDDLE: - payload = "middle"; - break; - case CLIMATE_FAN_FOCUS: - payload = "focus"; - break; - case CLIMATE_FAN_DIFFUSE: - payload = "diffuse"; - break; - case CLIMATE_FAN_QUIET: - payload = "quiet"; - break; - default: - payload = "unknown"; - } - } - if (this->device_->has_custom_fan_mode()) - payload = this->device_->get_custom_fan_mode().c_str(); - if (!this->publish(this->get_fan_mode_state_topic(), payload)) + if (this->device_->has_custom_fan_mode()) { + if (!this->publish(this->get_fan_mode_state_topic(), this->device_->get_custom_fan_mode())) + success = false; + } else if (this->device_->fan_mode.has_value()) { + if (!this->publish(this->get_fan_mode_state_topic(), + climate_fan_mode_to_mqtt_str(this->device_->fan_mode.value()))) + success = false; + } else if (!this->publish(this->get_fan_mode_state_topic(), "")) { success = false; + } } if (traits.get_supports_swing_modes()) { - const char *payload; - switch (this->device_->swing_mode) { - case CLIMATE_SWING_OFF: - payload = "off"; - break; - case CLIMATE_SWING_BOTH: - payload = "both"; - break; - case CLIMATE_SWING_VERTICAL: - payload = "vertical"; - break; - case CLIMATE_SWING_HORIZONTAL: - payload = "horizontal"; - break; - default: - payload = "unknown"; - } - if (!this->publish(this->get_swing_mode_state_topic(), payload)) + if (!this->publish(this->get_swing_mode_state_topic(), climate_swing_mode_to_mqtt_str(this->device_->swing_mode))) success = false; } diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index aec6140e3f1..a77afd3f4ed 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -5,6 +5,7 @@ #include "esphome/core/application.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#include "esphome/core/progmem.h" #include "esphome/core/version.h" #include "mqtt_const.h" @@ -149,6 +150,22 @@ bool MQTTComponent::publish(const char *topic, const char *payload) { return this->publish(topic, payload, strlen(payload)); } +#ifdef USE_ESP8266 +bool MQTTComponent::publish(const std::string &topic, ProgmemStr payload) { + return this->publish(topic.c_str(), payload); +} + +bool MQTTComponent::publish(const char *topic, ProgmemStr payload) { + if (topic[0] == '\0') + return false; + // On ESP8266, ProgmemStr is __FlashStringHelper* - need to copy from flash + char buf[64]; + strncpy_P(buf, reinterpret_cast(payload), sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; + return global_mqtt_client->publish(topic, buf, strlen(buf), this->qos_, this->retain_); +} +#endif + bool MQTTComponent::publish_json(const std::string &topic, const json::json_build_t &f) { return this->publish_json(topic.c_str(), f); } diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index 304a2c0d0e4..2cec6fda7ed 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -9,6 +9,7 @@ #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/entity_base.h" +#include "esphome/core/progmem.h" #include "esphome/core/string_ref.h" #include "mqtt_client.h" @@ -157,6 +158,15 @@ class MQTTComponent : public Component { */ bool publish(const std::string &topic, const char *payload, size_t payload_length); + /** Send a MQTT message. + * + * @param topic The topic. + * @param payload The null-terminated payload. + */ + bool publish(const std::string &topic, const char *payload) { + return this->publish(topic.c_str(), payload, strlen(payload)); + } + /** Send a MQTT message (no heap allocation for topic). * * @param topic The topic as C string. @@ -189,6 +199,29 @@ class MQTTComponent : public Component { */ bool publish(StringRef topic, const char *payload) { return this->publish(topic.c_str(), payload); } +#ifdef USE_ESP8266 + /** Send a MQTT message with a PROGMEM string payload. + * + * @param topic The topic. + * @param payload The payload (ProgmemStr - stored in flash on ESP8266). + */ + bool publish(const std::string &topic, ProgmemStr payload); + + /** Send a MQTT message with a PROGMEM string payload (no heap allocation for topic). + * + * @param topic The topic as C string. + * @param payload The payload (ProgmemStr - stored in flash on ESP8266). + */ + bool publish(const char *topic, ProgmemStr payload); + + /** Send a MQTT message with a PROGMEM string payload (no heap allocation for topic). + * + * @param topic The topic as StringRef (for use with get_state_topic_to_()). + * @param payload The payload (ProgmemStr - stored in flash on ESP8266). + */ + bool publish(StringRef topic, ProgmemStr payload) { return this->publish(topic.c_str(), payload); } +#endif + /** Construct and send a JSON MQTT message. * * @param topic The topic. diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp index d5bd13869a6..50e68ecbcc9 100644 --- a/esphome/components/mqtt/mqtt_cover.cpp +++ b/esphome/components/mqtt/mqtt_cover.cpp @@ -1,5 +1,6 @@ #include "mqtt_cover.h" #include "esphome/core/log.h" +#include "esphome/core/progmem.h" #include "mqtt_const.h" @@ -12,6 +13,20 @@ static const char *const TAG = "mqtt.cover"; using namespace esphome::cover; +static ProgmemStr cover_state_to_mqtt_str(CoverOperation operation, float position, bool supports_position) { + if (operation == COVER_OPERATION_OPENING) + return ESPHOME_F("opening"); + if (operation == COVER_OPERATION_CLOSING) + return ESPHOME_F("closing"); + if (position == COVER_CLOSED) + return ESPHOME_F("closed"); + if (position == COVER_OPEN) + return ESPHOME_F("open"); + if (supports_position) + return ESPHOME_F("open"); + return ESPHOME_F("unknown"); +} + MQTTCoverComponent::MQTTCoverComponent(Cover *cover) : cover_(cover) {} void MQTTCoverComponent::setup() { auto traits = this->cover_->get_traits(); @@ -109,14 +124,10 @@ bool MQTTCoverComponent::publish_state() { if (!this->publish(this->get_tilt_state_topic(), pos, len)) success = false; } - const char *state_s = this->cover_->current_operation == COVER_OPERATION_OPENING ? "opening" - : this->cover_->current_operation == COVER_OPERATION_CLOSING ? "closing" - : this->cover_->position == COVER_CLOSED ? "closed" - : this->cover_->position == COVER_OPEN ? "open" - : traits.get_supports_position() ? "open" - : "unknown"; char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN]; - if (!this->publish(this->get_state_topic_to_(topic_buf), state_s)) + if (!this->publish(this->get_state_topic_to_(topic_buf), + cover_state_to_mqtt_str(this->cover_->current_operation, this->cover_->position, + traits.get_supports_position()))) success = false; return success; } diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index c9791fb0f1e..84d51895c50 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -1,5 +1,6 @@ #include "mqtt_fan.h" #include "esphome/core/log.h" +#include "esphome/core/progmem.h" #include "mqtt_const.h" @@ -12,6 +13,14 @@ static const char *const TAG = "mqtt.fan"; using namespace esphome::fan; +static ProgmemStr fan_direction_to_mqtt_str(FanDirection direction) { + return direction == FanDirection::FORWARD ? ESPHOME_F("forward") : ESPHOME_F("reverse"); +} + +static ProgmemStr fan_oscillation_to_mqtt_str(bool oscillating) { + return oscillating ? ESPHOME_F("oscillate_on") : ESPHOME_F("oscillate_off"); +} + MQTTFanComponent::MQTTFanComponent(Fan *state) : state_(state) {} Fan *MQTTFanComponent::get_state() const { return this->state_; } @@ -164,13 +173,12 @@ bool MQTTFanComponent::publish_state() { this->publish(this->get_state_topic_to_(topic_buf), state_s); bool failed = false; if (this->state_->get_traits().supports_direction()) { - bool success = this->publish(this->get_direction_state_topic(), - this->state_->direction == fan::FanDirection::FORWARD ? "forward" : "reverse"); + bool success = this->publish(this->get_direction_state_topic(), fan_direction_to_mqtt_str(this->state_->direction)); failed = failed || !success; } if (this->state_->get_traits().supports_oscillation()) { - bool success = this->publish(this->get_oscillation_state_topic(), - this->state_->oscillating ? "oscillate_on" : "oscillate_off"); + bool success = + this->publish(this->get_oscillation_state_topic(), fan_oscillation_to_mqtt_str(this->state_->oscillating)); failed = failed || !success; } auto traits = this->state_->get_traits(); diff --git a/esphome/components/mqtt/mqtt_valve.cpp b/esphome/components/mqtt/mqtt_valve.cpp index 2e100823bfd..16e25f6a8a1 100644 --- a/esphome/components/mqtt/mqtt_valve.cpp +++ b/esphome/components/mqtt/mqtt_valve.cpp @@ -1,5 +1,6 @@ #include "mqtt_valve.h" #include "esphome/core/log.h" +#include "esphome/core/progmem.h" #include "mqtt_const.h" @@ -12,6 +13,20 @@ static const char *const TAG = "mqtt.valve"; using namespace esphome::valve; +static ProgmemStr valve_state_to_mqtt_str(ValveOperation operation, float position, bool supports_position) { + if (operation == VALVE_OPERATION_OPENING) + return ESPHOME_F("opening"); + if (operation == VALVE_OPERATION_CLOSING) + return ESPHOME_F("closing"); + if (position == VALVE_CLOSED) + return ESPHOME_F("closed"); + if (position == VALVE_OPEN) + return ESPHOME_F("open"); + if (supports_position) + return ESPHOME_F("open"); + return ESPHOME_F("unknown"); +} + MQTTValveComponent::MQTTValveComponent(Valve *valve) : valve_(valve) {} void MQTTValveComponent::setup() { auto traits = this->valve_->get_traits(); @@ -78,14 +93,10 @@ bool MQTTValveComponent::publish_state() { if (!this->publish(this->get_position_state_topic(), pos, len)) success = false; } - const char *state_s = this->valve_->current_operation == VALVE_OPERATION_OPENING ? "opening" - : this->valve_->current_operation == VALVE_OPERATION_CLOSING ? "closing" - : this->valve_->position == VALVE_CLOSED ? "closed" - : this->valve_->position == VALVE_OPEN ? "open" - : traits.get_supports_position() ? "open" - : "unknown"; char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN]; - if (!this->publish(this->get_state_topic_to_(topic_buf), state_s)) + if (!this->publish(this->get_state_topic_to_(topic_buf), + valve_state_to_mqtt_str(this->valve_->current_operation, this->valve_->position, + traits.get_supports_position()))) success = false; return success; }