#include "zehnder.h" #include "esphome/components/remote_base/remote_base.h" namespace { static const char* const TAG = __FILE__; static const uint8_t MIN_TEMPERATURE = 35; static const uint8_t MAX_TEMPERATURE = 70; static const uint8_t TEMPERATURE_LEVELS = 8; static void add_header_(esphome::remote_base::RemoteTransmitData* data) { data->item(30, 1000); } static void add_byte_(esphome::remote_base::RemoteTransmitData* data, uint8_t value) { for (int i = 0; i < 8; ++i) { data->item(30, value & 0b10000000 ? 830 : 650); value <<= 1; } } static void add_post_(esphome::remote_base::RemoteTransmitData* data) { data->item(30, 460); data->item(30, 650); } } // namespace namespace esphome { namespace zehnder { void ZehnderComponent::control(const climate::ClimateCall& call) { float target_temp = this->target_temperature; climate::ClimateMode mode = this->mode; if (call.get_target_temperature().has_value()) { target_temp = *call.get_target_temperature(); } if (call.get_mode().has_value()) { mode = *call.get_mode(); } if (mode == climate::CLIMATE_MODE_HEAT) { if (isnan(target_temp) || target_temp == 0) { // If we want to turn on heat, temperature must be set to a valid value. target_temp = MAX_TEMPERATURE; } if (!this->transmit_temperature_(&target_temp)) { return; } } else { // Keep target_temp unchanged, only transmit a zero value which corresponds // to mode=off. float unused_target_temp = 0; if (!this->transmit_temperature_(&unused_target_temp)) { return; } } this->target_temperature = target_temp; this->mode = mode; this->publish_state(); } void ZehnderComponent::setup() { this->target_temperature = 0; this->mode = climate::CLIMATE_MODE_OFF; } void ZehnderComponent::update() { float target_temp = this->mode == climate::CLIMATE_MODE_HEAT ? this->target_temperature : 0; this->transmit_temperature_(&target_temp); } climate::ClimateTraits ZehnderComponent::traits() { auto traits = climate::ClimateTraits(); traits.set_visual_min_temperature(MIN_TEMPERATURE); traits.set_visual_max_temperature(MAX_TEMPERATURE); // Must be // (MAX_TEMPERATURE - MIN_TEMPERATURE) / (TEMPERATURE_LEVELS - 1) // but ClimateTraits implementation infers precision from step value, // getting a value outside of HA supported range [0.1, 0.5, 1]. traits.set_visual_target_temperature_step(1); if (has_temperature_sensor_) { traits.add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE); traits.set_visual_current_temperature_step(0.1); } traits.set_supported_modes( {climate::CLIMATE_MODE_HEAT, climate::CLIMATE_MODE_OFF}); return traits; } void ZehnderComponent::dump_config() { ESP_LOGCONFIG(TAG, "Zehnder:"); ESP_LOGCONFIG(TAG, " has temperature sensor: %s", this->has_temperature_sensor_ ? "yes" : "no"); } bool ZehnderComponent::transmit_temperature_(float* temp) { uint8_t level; if (*temp == 0) { level = 0; } else { level = (*temp - MIN_TEMPERATURE) * (TEMPERATURE_LEVELS - 1) / (MAX_TEMPERATURE - MIN_TEMPERATURE) + 1; if (level > TEMPERATURE_LEVELS) { level = TEMPERATURE_LEVELS; } *temp = (MAX_TEMPERATURE - MIN_TEMPERATURE) / (TEMPERATURE_LEVELS - 1) * (level - 1) + MIN_TEMPERATURE; } return this->transmit_level_(level); } bool ZehnderComponent::transmit_level_(uint8_t level) { if (level > TEMPERATURE_LEVELS) { ESP_LOGE(TAG, "Unsupported heater level: %d. Ignoring command", level); return false; } if (this->transmitter_ == nullptr) { ESP_LOGE(TAG, "IR transmitter not set. Use set_transmitter."); return false; } ESP_LOGD(TAG, "Transmitting heater level: %d...", level); this->transmitter_->set_carrier_duty_percent(50); auto call = this->transmitter_->transmit(); call.set_send_times(10); call.set_send_wait(10); // milliseconds auto data = call.get_data(); data->reset(); data->set_carrier_frequency(455000); static uint8_t mode_bytes[TEMPERATURE_LEVELS + 1] = {0x0D, // OFF 0x86, 0x95, 0xA0, 0xB3, 0xCA, 0xD9, 0xEC, 0xFF}; add_header_(data); add_byte_(data, 0xB8); add_byte_(data, mode_bytes[level]); add_post_(data); call.perform(); return true; } } // namespace zehnder } // namespace esphome