Move heater implementation to canonical component

This commit is contained in:
2021-11-26 16:27:41 +01:00
parent f5b0c740e4
commit 9f9121cad3
2 changed files with 156 additions and 95 deletions
+23 -95
View File
@@ -2,118 +2,46 @@ esphome:
name: bathroomheater name: bathroomheater
platform: ESP8266 platform: ESP8266
board: nodemcuv2 board: nodemcuv2
includes:
- zehnder.h
logger: logger:
web_server:
port: 80
mqtt:
broker: mqtt.lan
switch: switch:
- platform: gpio - platform: gpio
pin: pin:
number: D4 number: D4
inverted: true inverted: true
restore_mode: ALWAYS_OFF restore_mode: ALWAYS_OFF
name: "Built-in LED" name: "Bathroom Heater LED"
web_server:
port: 80
remote_transmitter: remote_transmitter:
pin: D2 pin: D2
carrier_duty_percent: 50% carrier_duty_percent: 50%
id: heater_ir_transmitter
globals: climate:
- id: heater_code - platform: custom
type: int[38] lambda: |-
initial_value: | auto heater = new Zehnder();
{ App.register_component(heater);
// header heater->set_transmitter(id(heater_ir_transmitter));
30, -1000, return {heater};
// 0xB8 (0b10111000) climates:
30, -830, - name: "Bathroom Heater"
30, -650, id: bathroom_heater
30, -830,
30, -830,
30, -830,
30, -650,
30, -650,
30, -650,
// 0x0D (0b00001101)
30, -650,
30, -650,
30, -650,
30, -650,
30, -830,
30, -830,
30, -650,
30, -830,
// post
30, -460,
// zero
30, -650
}
- id: heater_level
type: int
initial_value: "0"
- id: heater_level_code_map
type: int[9]
initial_value: |
{
0x0D, // OFF
0x86,
0x95,
0xA0,
0xB3,
0xCA,
0xD9,
0xEC,
0xFF
}
script:
- id: transmit_heater_code
then:
# https://esphome.io/components/climate/custom.html
- remote_transmitter.transmit_raw:
carrier_frequency: 455000
repeat:
times: 10
wait_time: 10ms
code: !lambda |-
if (id(heater_level) > 8 || id(heater_level) < 0) {
id(heater_level) = 8;
}
int level_code = id(heater_level_code_map)[id(heater_level)];
for (int i = 0; i < 8; ++i) {
id(heater_code)[i*2 + 18] = 30;
id(heater_code)[i*2 + 19] =
level_code & 0b10000000 ? -830 : -650;
level_code <<= 1;
}
std::vector<int> codes = std::vector<int>(
id(heater_code),
id(heater_code) + 38);
for (int i = 0; i < codes.size(); ++i) {
ESP_LOGD("code", "%d: %d", i, codes[i]);
}
return codes;
- mqtt.publish:
topic: bathroomheater/heater/state
payload: !lambda "return to_string(id(heater_level));"
mqtt:
broker: mqtt.lan
on_json_message:
topic: bathroomheater/heater/command
then:
- lambda: |-
if (x.containsKey("level")) {
id(heater_level) = x["level"];
}
- script.execute: transmit_heater_code
interval: interval:
- interval: 1min - interval: 1min
then: then:
- script.execute: transmit_heater_code # Periodically remind heater about the current setting.
lambda: static_cast<Zehnder*>(id(bathroom_heater))->retransmit();
ota: ota:
password: "" password: ""
@@ -124,7 +52,7 @@ wifi:
# Enable fallback hotspot (captive portal) in case wifi connection fails # Enable fallback hotspot (captive portal) in case wifi connection fails
ap: ap:
ssid: "Bathroomheater Fallback Hotspot" ssid: "bathroomheater"
password: !secret wifi_fallback_hotspot_password password: !secret wifi_fallback_hotspot_password
captive_portal: captive_portal:
+133
View File
@@ -0,0 +1,133 @@
#include "esphome.h"
namespace {
static const char* const TAG = "zehnder";
static const uint8_t MIN_TEMPERATURE = 35;
static const uint8_t MAX_TEMPERATURE = 70;
static const uint8_t TEMPERATURE_LEVELS = 8;
}
class Zehnder : public Component, public Climate {
public:
void retransmit() {
this->transmit_temperature_(this->target_temperature);
}
void control(const ClimateCall &call) override {
if (call.get_mode().has_value()) {
ClimateMode mode = *call.get_mode();
if (mode == climate::CLIMATE_MODE_OFF) {
if (this->transmit_level_(0)) {
this->target_temperature = 0;
this->mode = mode;
}
} else {
if (this->transmit_temperature_(MAX_TEMPERATURE)) {
this->target_temperature = MAX_TEMPERATURE;
this->mode = mode;
}
}
}
if (call.get_target_temperature().has_value()) {
float temp = *call.get_target_temperature();
if (this->transmit_temperature_(temp)) {
this->target_temperature = temp;
}
}
this->publish_state();
}
ClimateTraits traits() override {
auto traits = climate::ClimateTraits();
traits.set_visual_min_temperature(MIN_TEMPERATURE);
traits.set_visual_max_temperature(MAX_TEMPERATURE);
traits.set_visual_temperature_step(
(MAX_TEMPERATURE - MIN_TEMPERATURE) / (TEMPERATURE_LEVELS - 1)
);
traits.set_supported_modes({
climate::CLIMATE_MODE_HEAT,
climate::CLIMATE_MODE_OFF
});
return traits;
}
void set_transmitter(remote_transmitter::RemoteTransmitterComponent *transmitter) {
this->transmitter_ = transmitter;
}
private:
remote_transmitter::RemoteTransmitterComponent *transmitter_;
bool transmit_temperature_(float temp) {
return this->transmit_level_(temp == 0
? 0
: (
(temp - MIN_TEMPERATURE) *
(TEMPERATURE_LEVELS - 1) /
(MAX_TEMPERATURE - MIN_TEMPERATURE) +
1
)
);
}
bool transmit_level_(uint8_t level) {
if (level > 8) {
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;
}
static void add_header_(remote_base::RemoteTransmitData *data) {
data->item(30, 1000);
}
static void add_byte_(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_(remote_base::RemoteTransmitData *data) {
data->item(30, 460);
data->item(30, 650);
}
};