Skip to content

Instantly share code, notes, and snippets.

@MrHaroldA
Created January 10, 2026 21:44
Show Gist options
  • Select an option

  • Save MrHaroldA/654e7b0f5dec3cd866f3eba909036e78 to your computer and use it in GitHub Desktop.

Select an option

Save MrHaroldA/654e7b0f5dec3cd866f3eba909036e78 to your computer and use it in GitHub Desktop.
Eurom Designheat 2000 Wifi ESPHome
substitutions:
dev_name: Föhn logeerkamer
dev_machine_name: fohn-logeerkamer
comment: ""
fallback_hotspot_pass: !secret esphome_captive_password
ssid: !secret wifi_ssid
password: !secret wifi_password
ota_password: ""
encryption_key: ""
# Data to the MCU.
# 1 Bytes 0-1 - Direction - F1F1 from ESP, F2F2 from MCU
# 2 Bytes 2-3 - Padding as far as I can work out - always 02 10
# 3 Byte 4 - Power - 02=OFF, 01=ON
# 4 Byte 5 - Childlock - 02=OFF, 01=ON # Swing!
# 5 Bytes 6-9 - More padding - always 02 00 19 00
# 6 Byte 10 - Mode - 01=off, 02=low, 03=high, 04=anti-frost
# 7 Byte 11 - Setpoint temperature, converted to hex
# 8 Bytes 12-13 - Timer - 0100 = off, 0001 = on, 0002 = 1 hr, 0003 = 2 hrs (etc) # not supported on my EUROM: always 00 00.
# 9 Byte 14 - Ambient temperature, converted to hex
# 0 Bytes 15-18 - More padding - always 00 00 00 00
# A Byte 19 - Additive checksum of bytes 2-18
# B Byte 20 - Terminator - always 7E
#
# References:
# * https://templates.blakadder.com/devola_designer.html
# * https://api-docs.esphome.io/namespaceesphome_1_1climate
# * https://esphome.io/components/climate/thermostat/
# * https://community.home-assistant.io/t/eurom-sani-wall-heat-2000-wifi/375761
esp8266:
board: esp01_1m
logger:
ota:
- platform: esphome
password: ${ota_password}
wifi:
ssid: "${ssid}"
password: "${password}"
power_save_mode: none
min_auth_mode: WPA2
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: ${dev_name} Hotspot
password: ${fallback_hotspot_pass}
captive_portal:
# Enable Home Assistant API
api:
encryption:
key: ${encryption_key}
esphome:
name: ${dev_machine_name}
comment: ${comment}
friendly_name: ${dev_name}
on_boot:
then:
- delay: 5s
- uart.write: [0xF1,0xF1,0x01,0x00,0x01,0x7E]
# Ping the MCU to get status updates.
interval:
- interval: 60s
then:
- uart.write: [0xF1,0xF1,0x01,0x00,0x01,0x7E]
uart:
baud_rate: 9600
rx_pin: 13
tx_pin: 15
debug:
direction: BOTH
dummy_receiver: true
after:
delimiter: "0x7E"
sequence:
- lambda: |-
// UARTDebug::log_string(direction, bytes);
// Receive data.
if (bytes[0] == 0xF2) {
auto call = id(my_thermostat).make_call();
// ESP_LOGD("custom", "Swing: %d (1=on, 2=off)", bytes[5]);
call.set_swing_mode(bytes[5] == 0x01 ? CLIMATE_SWING_VERTICAL : CLIMATE_SWING_OFF);
// ESP_LOGD("custom", "Mode is %d! (1=off, 2=low, 3=high, 4=anti-frost)", bytes[10]);
// ESP_LOGD("custom", "Power: %d (1=on, 2=off)", bytes[4]);
switch (bytes[10]) {
case 0x01:
if (bytes[4] == 0x02) {
id(mode).publish_state("Off");
call.set_mode(CLIMATE_MODE_OFF);
}
else {
id(mode).publish_state("Fan only");
call.set_mode(CLIMATE_MODE_FAN_ONLY);
}
id(supplemental_heating).publish_state(false);
break;
case 0x02:
case 0x04:
id(mode).publish_state("Low");
call.set_mode(CLIMATE_MODE_HEAT);
id(supplemental_heating).publish_state(false);
break;
case 0x03:
id(mode).publish_state("High");
call.set_mode(CLIMATE_MODE_HEAT);
id(supplemental_heating).publish_state(true);
break;
}
// ESP_LOGD("custom", "Setpoint: %d", bytes[11]);
call.set_target_temperature(bytes[11]);
// ESP_LOGD("custom", "Current temp: %d!", bytes[14]);
id(temperature).publish_state(bytes[14]);
// Perform all thermostat calls.
call.perform();
}
climate:
- platform: thermostat
name: "Thermostat"
id: my_thermostat
# todo: check these values!
min_idle_time: 5s
min_heating_off_time: 5s
min_heating_run_time: 5s
min_fanning_off_time: 5s
min_fanning_run_time: 5s
# todo: check these values!
# todo: automatic supplemental heating
# max_heating_run_time: 30min
# supplemental_heating_delta: 5
sensor: temperature
visual:
temperature_step: 1
min_temperature: 10
max_temperature: 35
heat_action:
- switch.turn_off: supplemental_heating
# todo: automatic supplemental heating
# supplemental_heating_action:
# - switch.turn_on: supplemental_heating
heat_mode:
- logger.log: "Turn on, half power, with swing!"
# > ON SWING 50% CHK<
- uart.write: [0xF1,0xF1,0x02,0x10,0x01,0x01,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x18,0x7E]
idle_action:
- logger.log: "Fan mode (idle)"
# > ON FAN CHK<
- uart.write: [0xF1,0xF1,0x02,0x10,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x16,0x7E]
fan_only_action:
- logger.log: "Fan mode (fan only)"
# > ON FAN CHK<
- uart.write: [0xF1,0xF1,0x02,0x10,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x16,0x7E]
off_mode:
- logger.log: "Turn off"
# > OFF CHK<
- uart.write: [0xF1,0xF1,0x02,0x10,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x16,0x7E]
swing_vertical_action:
- logger.log: "Turn swing on!"
# SWING CHK
- uart.write: [0xF1,0xF1,0x02,0x10,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x15,0x7E]
swing_off_action:
- logger.log: "Turn swing off"
# SWING CHK
- uart.write: [0xF1,0xF1,0x02,0x10,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x16,0x7E]
target_temperature_change_action:
then:
- logger.log:
format: "Change target to %f"
args: ["id(my_thermostat).target_temperature"]
- uart.write: !lambda |-
auto target = id(my_thermostat).target_temperature;
// > SETPOINT CHK<
std::vector<uint8_t> data = {0xF1,0xF1,0x02,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,static_cast<uint8_t>(target),0x00,0x00,0x00,0x00,0x00,0x00,0x02,static_cast<uint8_t>(target + 20),0x7E};
return data;
text_sensor:
- platform: template
id: mode
name: "Current mode"
sensor:
- platform: template
internal: true
id: temperature
name: "Temperature"
unit_of_measurement: "°C"
icon: "mdi:thermometer"
device_class: "temperature"
state_class: "measurement"
accuracy_decimals: 0
# todo: does not work.
# - platform: template
# name: "Energy"
# id: "energy"
# unit_of_measurement: "W"
# icon: "mdi:power"
# device_class: "energy"
# state_class: measurement
# accuracy_decimals: 0
# update_interval: 5s
# lambda: |-
# std::string val = id(mode).state;
# if (val == "Low") return 1000;
# if (val == "High") return 2000;
# return 5;
switch:
- platform: template
name: "Supplemental heating"
id: "supplemental_heating"
turn_on_action:
- logger.log: "High"
# ON SWING HIGH CHK
- uart.write: [0xF1,0xF1,0x02,0x10,0x01,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x18,0x7E]
turn_off_action:
- logger.log: "Low"
# ON SWING LOW CHK
- uart.write: [0xF1,0xF1,0x02,0x10,0x01,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x17,0x7E]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment