Files
esphome/components/zehnder/zehnder.cpp
T
2026-07-12 12:56:02 +00:00

159 lines
4.4 KiB
C++

#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