Created
January 10, 2026 21:44
-
-
Save MrHaroldA/654e7b0f5dec3cd866f3eba909036e78 to your computer and use it in GitHub Desktop.
Eurom Designheat 2000 Wifi ESPHome
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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