Midea branded AC’s with ESPhome (no cloud)

Nothing, I abandoned the project for now due lack of time.

any ideas why my esp01 is dropping from the network nearly ever minute or so and then back on?

That would be the part with follow me.
According to the documentation, u either have to set up a led sender in front of the unit or u wire a lead from your gpioxyz to the the signal leg of the tsop inside.
I am at that spot right now but as soon i connect the lead to the signal leg, remote is frozen/unresponsive to my esp commands and the original remote…
So i am prob missing something but…
If you figure it out, let me know

I just realized what exactly the follow me is supposed to do, and realized I have something similar. Basically if I understand it right there’s a temp sensor in the remote, and it replaces the temp sensor in the heat pump.

Instead of the remote though, I’m using an ESP8266 with a Dallas temp sensor. The basic idea is when the dallas sensor gets a new value, connect to the heatpump esp chip and set a template number to have that value. Then in an automation treat the template number as a thermometer and handle it like you like.

Here’s an example:

In the heatpump ESP chip yaml add this:

globals:
   - id: auto_mode
     type: bool
     restore_value: false
     initial_value: 'false'


switch:
  - platform: template
    name: ${friendly_node_name} Auto Mode
    id: ${node_id}_auto_mode
    icon: mdi:car-turbocharger
    optimistic: true
    lambda: return id(auto_mode);  
    turn_on_action:
        - lambda: |-
            id(auto_mode) = true;
            id(${node_id}_automation_lambda).press();
    turn_off_action:
        - lambda: |-
            id(auto_mode) = false;
button:
  - platform: template
    internal: true
    id: ${node_id}_automation_lambda
    on_press:
        - lambda: |-
                  if (id(auto_mode)) {
                       float tempDifference = id(${node_id}_my_climate).target_temperature - id(${node_id}_room_temperature_c).state;
                      ESP_LOGW(
                              "climate_temp", "difference: %f°F,  target: %f°F, room: %f°F", 
                              (tempDifference * 1.8), 
                              (id(${node_id}_my_climate).target_temperature * 1.8 + 32),
                              (id(${node_id}_room_temperature_c).state * 1.8 + 32)
                      );
                       if(tempDifference > $heat_on_below_set_temp) {
                          ESP_LOGW("climate_mode", "Mode: [%s]","heat");
                           if (ClimateMode::CLIMATE_MODE_HEAT != id(${node_id}_my_climate).mode) {
                              ESP_LOGW("climate_mode", "Mode: [%s]","setting mode heat");
                              id(${node_id}_my_climate).make_call().set_mode(ClimateMode::CLIMATE_MODE_HEAT).perform();
                           }
                           delay(2000);
                           if(tempDifference > $heat_turbo_on_below_set_temp) {
                              ESP_LOGW("climate_preset",    "Preset: [%s]", "boost");
                               if (ClimatePreset::CLIMATE_PRESET_BOOST !=  id(${node_id}_my_climate).preset.value()) {
                                  ESP_LOGW("climate_preset",    "Preset: [%s]", " setting boost heat");
                                  id(${node_id}_my_climate).make_call().set_preset(ClimatePreset::CLIMATE_PRESET_BOOST).perform();
                               } else if (tempDifference < -$heat_turbo_off_above_set_temp) {
                              ESP_LOGW("climate_preset",    "Preset: [%s]", "none");
                               if (ClimatePreset::CLIMATE_PRESET_NONE !=   id(${node_id}_my_climate).preset.value()) {
                                  ESP_LOGW("climate_preset",    "Preset: [%s]", " setting none heat");
                                  id(${node_id}_my_climate).make_call().set_preset(ClimatePreset::CLIMATE_PRESET_NONE).perform();
                               }
                              }
                           }
                      } else if(tempDifference < -$cool_on_above_set_temp) {
                          ESP_LOGW("climate_mode", "Mode: [%s]", "cool");
                           if (ClimateMode::CLIMATE_MODE_COOL != id(${node_id}_my_climate).mode) {
                              ESP_LOGW("climate_mode", "Mode: [%s]", "setting mode cool");
                              id(${node_id}_my_climate).make_call().set_mode(ClimateMode::CLIMATE_MODE_COOL).perform();
                           }
                           delay(2000);
                           if (tempDifference < -$cool_turbo_on_above_set_temp) {
                              ESP_LOGW("climate_preset",    "Preset: [%s]", "boost");
                               if (ClimatePreset::CLIMATE_PRESET_BOOST !=  id(${node_id}_my_climate).preset.value()) {
                                  ESP_LOGW("climate_preset",    "Preset: [%s]", " setting boost cool");
                                  id(${node_id}_my_climate).make_call().set_preset(ClimatePreset::CLIMATE_PRESET_BOOST).perform();
                               } else if  (tempDifference > $cool_turbo_off_below_set_temp) {
                              ESP_LOGW("climate_preset",    "Preset: [%s]", "none");
                               if (ClimatePreset::CLIMATE_PRESET_NONE !=   id(${node_id}_my_climate).preset.value()) {
                                  ESP_LOGW("climate_preset",    "Preset: [%s]", " setting none cool");
                                  id(${node_id}_my_climate).make_call().set_preset(ClimatePreset::CLIMATE_PRESET_NONE).perform();
                               }
                              }
                           }
                       }
                  }
number:
  - platform: template
    name: ${friendly_node_name} Room Temperature °C
    id: ${node_id}_room_temperature_c
    optimistic: true
    min_value: -40
    max_value: 100
    restore_value: true
    step: 0.1
    on_value:
      then:
        - lambda: |-
            id(${node_id}_automation_lambda).press();

in the Dallas Esp chip yaml have it include this:


http_request:
  id: http_request_data
  useragent: esphome/device
  timeout: 10s
  esp8266_disable_ssl_support: true

dallas:
  - pin: ${dallas_pin}
    id: ${device_name}_pin
    update_interval: ${dallas_update}

sensor:
  - platform: dallas
    address: ${dallas_address}
    name: ${friendly_name}
    id: ${device_name}
    dallas_id: ${device_name}_pin
    on_value:
      then:
      - http_request.post: !lambda |- 
              return "http://some-heatpump.lan/number/some_heatpump_room_tempreture_c/set?value=" + std::to_string(x);

Net result is a pure wifi and ESP replacement for follow me.

1 Like

Hi!

Did you find a solution? Did you setup the USB connector and integrate it into HA?

Maybe someone has experience with this type of fan-coil unit?

Thanks in advance
Balazs

Hello Balázs!

I haven’t got enough time for this topic. My workaround will be (at least for now) is an ESP8285 based IR module. As far as I know there is Midea compatible IR module in ESPHome.
But the last weeks I’ve checked this topic and searched in Google and found this. My fan-coil have XYE port and it looks like it is usable. Sadly I’m far from expert, so I need much more time to find something that will be able to use this RS485 port.

I hope this will help!
My units XYE port:

Anyone following this with a heat pump might be interested in the good work going on on this other forum:

1 Like

interesting!
I have also found ready HA integration based on similar idea:

Surprisingly, worked for me out of the box
unfortunately same limited data as on mobile app

Hi all,

I got quite some progress on our Airwell (Midea clone) heat pump with modbus. I am able to read all the modbus registers and also am able to write (I changed the value of the temperature of the DHW tank).

My current setup is as follow: I have a raspberry pi with a USB/RS485 adapter that is attached to the internal unit of the heat pump. It took me some time to understand that I had to use the H1/H2 connection on the back of the display :slight_smile: On the rpi I have a small python script that I wrote that reads all the registers and uploads that info into MQTT, where Home Assistant reads it from.

Btw, I had to place a 120 ohm resistor between H1 and H2 before I was able to read info.

The next steps I want to take is to create a ESPHome solution for it. Thus a more complete solution for others to use with a decent manual that describes how to attach it to the heat pump.

And since pictures are always more worth then words, two screenshots of my current heat pump dashboard


Most is in Dutch, so some translations:

  • warmtepomp = heat pump
  • binnen = internal
  • buiten = external
  • buffervat = water tank

On the screenshot you also see the energy usage stats and some thermostats. That info is coming from two Shelly devices (both the internal and external unit are attached to a Shelly to measure the energy usage) and my Tado thermostats.

Fun fact, with the combination of the energy info from the Shelly devices and the state of the heat pump, I am now also able to split the energy consumption based on the status. In other words, I can see how many energy is used for heating, cooling and dhw.

4 Likes

wow!
I was trying all weekend to connect my heat pump, even started receving some garbage and plenty of CRC errors but no real data… very likely termination resistor was missing (using rpi, xy-017 and mbpoll application)
image
EDIT: according to diagram the xy-017 RS485 module already have 120om resistor
image

I have verified first with simple power meter modbus enabled to make sure hardware is set up correctly SDM120M

Can you share some more details?
I know it’s relevant, how did you connect H1H2 to AB? did you connect GND?
can you tell communication settings? baud, parity, bits

I hope these Midea clones works the same way…

Hi,

I used the following connections:

  • B- == H1
  • A+ == H2
  • Ground == E

I started with the following code to test the connectivity, my current code is of course more complicated. Here you can also see the parameters I use for the connection.

#!/usr/bin/env python3

import minimalmodbus
import serial

instrument = minimalmodbus.Instrument('/dev/ttyUSB0', 1)  # port name, slave address (in decimal)
instrument.serial.baudrate = 9600
instrument.serial.bytesize = 8
instrument.serial.parity   = serial.PARITY_NONE
instrument.serial.stopbits = 1
instrument.serial.timeout  = 1.00          # seconds

instrument.mode = minimalmodbus.MODE_RTU  
instrument.clear_buffers_before_each_transaction = True

## Read Water Tank Temperature
value = instrument.read_register(115, 0)  # Registernumber, number of decimals
print("Value: {}".format(value))

instrument.serial.close()

And these pictures should make it completely clear :slight_smile:

( small typo on the picture above, B1 should be B- )

great, thank you Mosibi.

pretty basic settings, I’m 100% sure was trying these.

But you say you have connected to display while I was trying directly to mainboard:
image
perhaps this is the difference, I will try asap.

That is also what I tried as first with exactly the same result, garbage :grinning_face_with_smiling_eyes: Those, as I learned later, are the modbus connectors to link to other heat pump units. So you should really have the H1 and H2 on the display, which on our unit is in the front panel of the internal unit

Btw, be gentle with the screws that are used for the display bracket, on our unit they are not of the highest quality.

1 Like

glad to report full success :slight_smile: even with esphome:

thanks again @Mosibi!

temporarirly used Raspberry Pico W, but probably ESP8266 would work fine

need to work on few values calculation, some are coded in two registers (low/high byte)

next step: put esp8266 and RS485 into display box
found already 3.3V on the board (CN6 or IC8), enough to power ESP8266

relevant esphome code, if somebody interested:

substitutions:
 heat_pump_name: rotenso

uart:
  rx_pin: GPIO5
  tx_pin: GPIO4
  baud_rate: 9600
  #these are defaults, just show for clarity
  stop_bits: 1
  parity: NONE
  data_bits: 8

modbus:
  id: modbus_heatpump

modbus_controller:
  - id: modbus_1
    ## the Modbus device addr
    address: 0x1
    modbus_id: modbus_heatpump
    setup_priority: -10
    update_interval: 15s

sensor:  
#by registry number, like : _100
  - platform: modbus_controller
    id: ${heat_pump_name}_100
    name: "hp_compressor_frequency"
    state_class: measurement
    register_type: holding
    address: 0x64 #dec 100
    unit_of_measurement: "Hz"
    value_type: S_WORD

  - platform: modbus_controller
    id: ${heat_pump_name}_102
    name: "hp_fan_speed_pwm"
    state_class: measurement
    register_type: holding
    address: 0x66
    unit_of_measurement: "pwm"
    value_type: S_WORD

  - platform: modbus_controller
    id: ${heat_pump_name}_104
    name: "hp_TW_in_Entering_Water_Temperature"
    state_class: measurement
    register_type: holding
    address: 0x68
    unit_of_measurement: "°C"
    value_type: S_WORD
1 Like

@netsplit64
Hello…

Where did you specify the variables?
Do you have variable definitioon table somewhere tyhat you missed to post?
Or is this extra held variable for the purpose of being a universal example?

hi, not sure which variables you have in mind
if modbus registers list then these are available in some other Midea clones manuals, like here:

I have just posted few examples but there are many more, like this one:
name: "hp_compressor_frequency"
state_class: measurement
register_type: holding
address: 0x64 #dec 100
unit_of_measurement: “Hz”
value_type: U_WORD

Hello and thx alot for your reply, but the variable question was meant for @netsplit64, since his code uses a good amount.
I am pretty good with c and c++ but i havent seen so far how to combine yaml with it so i am unaware where he did define them.
I did play with substitutes for friendly_node_name and node_id which seemed to work, but the variable id alone in the on press clause is still a mystery for me.

Thx anyway.

So basically what my post described is using a Dallas temperature sensor on a different esp chip (Dallas Temperature Sensor — ESPHome) to connect to my heatpump controller and update a number template value for an automation. The connection happens over wifi. My temp sensor is plugged into a usb wall outlet by the bed. You could power one by battery to make it portable. If I were to do that, I’d find/male a suitable looking, suitably sized box, drill a hole just big enough to mount the temp probe, and use a small usb power bank.

The automation simply demonstrates setting the temperature mode based on the input number which the separate esp/dallas unit keeps updated.

as for substitutions this might help: Configuration Types — ESPHome

Substitutions are ultimately plain text that gets replaced with a specified value when the firmware is compiled.

and for lambdas (c code) : Automations and Templates — ESPHome

It can be a bit of a learning curve, but it’s worth it.

Hello, thx for answering…

I added the substitution for “friendly_node_name” and “node_id” already and got the point where he is complaining about the variable contents of " id(${node_id}_my_climate).target_temperature - id(${node_id}_room_temperature_c).state;".
Debugging it shows that he tries to build a string “Button1_my_climate” and “Button1__room_temperature_c” that he cannot find anywhere defined…
My code is down below…
So what i would like to know is what you are using as substituitions and globals so that i can change it to my xiaomi blue tooth temp sensor as the temp source…which is known in HASS as “sensor.ble_temperature_schlafzimmer_a4c13888d685”, so no second esphome code for that possible.
Hope you can help me out, cause my hard ware dongle does not work and this ukrainin gentleman does have other isues right now than sending me a pre made one…
By the way…“node_id” became value Button1 without reason, just to fill it since i did not know what it expects in there…

globals:
   - id: auto_mode
     type: bool
     restore_value: false
     initial_value: 'false'

substitutions:
  friendly_node_name: BED ROOM AC
  node_id: Button1

#test ac from here down
switch:
  - platform: template
    name: ${friendly_node_name} Auto Mode
    id: ${node_id}_auto_mode
    icon: mdi:car-turbocharger
    optimistic: true
    lambda: return id(auto_mode);  
    turn_on_action:
        - lambda: |-
            id(auto_mode) = true;
            id(${node_id}_automation_lambda).press();
    turn_off_action:
        - lambda: |-
            id(auto_mode) = false;

button:
  - platform: template
    internal: true
    id: ${node_id}_automation_lambda
    on_press:
        - lambda: |-
                  if (id(auto_mode)) {
                       float tempDifference = id(${node_id}_my_climate).target_temperature - id(${node_id}_room_temperature_c).state;
                      ESP_LOGW(
                              "climate_temp", "difference: %f°C,  target: %f°C, room: %f°C", 
                              (tempDifference * 1.8), 
                              (id(${node_id}_my_climate).target_temperature),
                              (id(${node_id}_room_temperature_c).state)
                      );
                       if(tempDifference > $heat_on_below_set_temp) {
                          ESP_LOGW("climate_mode", "Mode: [%s]","heat");
                           if (ClimateMode::CLIMATE_MODE_HEAT != id(${node_id}_my_climate).mode) {
                              ESP_LOGW("climate_mode", "Mode: [%s]","setting mode heat");
                              id(${node_id}_my_climate).make_call().set_mode(ClimateMode::CLIMATE_MODE_HEAT).perform();
                           }
                           delay(2000);
                           if(tempDifference > $heat_turbo_on_below_set_temp) {
                              ESP_LOGW("climate_preset",    "Preset: [%s]", "boost");
                               if (ClimatePreset::CLIMATE_PRESET_BOOST !=  id(${node_id}_my_climate).preset.value()) {
                                  ESP_LOGW("climate_preset",    "Preset: [%s]", " setting boost heat");
                                  id(${node_id}_my_climate).make_call().set_preset(ClimatePreset::CLIMATE_PRESET_BOOST).perform();
                               } else if (tempDifference < -$heat_turbo_off_above_set_temp) {
                              ESP_LOGW("climate_preset",    "Preset: [%s]", "none");
                               if (ClimatePreset::CLIMATE_PRESET_NONE !=   id(${node_id}_my_climate).preset.value()) {
                                  ESP_LOGW("climate_preset",    "Preset: [%s]", " setting none heat");
                                  id(${node_id}_my_climate).make_call().set_preset(ClimatePreset::CLIMATE_PRESET_NONE).perform();
                               }
                              }
                           }
                      } else if(tempDifference < -$cool_on_above_set_temp) {
                          ESP_LOGW("climate_mode", "Mode: [%s]", "cool");
                           if (ClimateMode::CLIMATE_MODE_COOL != id(${node_id}_my_climate).mode) {
                              ESP_LOGW("climate_mode", "Mode: [%s]", "setting mode cool");
                              id(${node_id}_my_climate).make_call().set_mode(ClimateMode::CLIMATE_MODE_COOL).perform();
                           }
                           delay(2000);
                           if (tempDifference < -$cool_turbo_on_above_set_temp) {
                              ESP_LOGW("climate_preset",    "Preset: [%s]", "boost");
                               if (ClimatePreset::CLIMATE_PRESET_BOOST !=  id(${node_id}_my_climate).preset.value()) {
                                  ESP_LOGW("climate_preset",    "Preset: [%s]", " setting boost cool");
                                  id(${node_id}_my_climate).make_call().set_preset(ClimatePreset::CLIMATE_PRESET_BOOST).perform();
                               } else if  (tempDifference > $cool_turbo_off_below_set_temp) {
                              ESP_LOGW("climate_preset",    "Preset: [%s]", "none");
                               if (ClimatePreset::CLIMATE_PRESET_NONE !=   id(${node_id}_my_climate).preset.value()) {
                                  ESP_LOGW("climate_preset",    "Preset: [%s]", " setting none cool");
                                  id(${node_id}_my_climate).make_call().set_preset(ClimatePreset::CLIMATE_PRESET_NONE).perform();
                               }
                              }
                           }
                       }
                  }

number:
  - platform: template
    name: ${friendly_node_name} Room Temperature °C
    id: ${node_id}_room_temperature_c
    optimistic: true
    min_value: -40
    max_value: 100
    restore_value: true
    step: 0.1
    on_value:
      then:
        - lambda: |-
            id(${node_id}_automation_lambda).press();