ESP Haier: Haier Air Conditioner + ESP Home + Wemos D1 mini

Mabe is a Mexican division of Haier, and from pictures I saw of the “Mabe” wifi card, I realized they were both using the same KZW-W001. I just used your code and was able to add my Mabe branded mini split to Home Assistant with little issue. The only thing is when I select Cool in the Home Assistant app, the mini split display shows a green snowflake/green star of some sort, and not the blue snowflake for cooling mode. Everything else works perfect.

Maybe enable “eco mode”?

If you set the cool mode from device remote, with show Home Assistant?

I ordered JST XH connectors but the won’t fit.
They are way to big!

So I went through and reflashed the Wemos and saw all the Deprecated comments on the swing and modes, so I went through the forks of your code.

I found someone, Okison, that had revised your code to incorporate swing and the new Home Assistant code.

Revised Haier.h code incorporating Swing by Okison

The previous issue with the green snowflake disappeared with flashing Okison’s version, but now, the Home Assistant climate page for the mini split shows “Off” regardless of mode. Everything works, but the persistent “Off” is confusing.

1 Like

Because I still doesn’t have the right connector i put the Wemos D1 at the ESP32.
With the Haierv2.h file from albetaCOM GitHub - albetaCOM/esp-haier: ESP8266 code to connect to Haier Air Conditioner ((firmware version I can communicate with the unit.

It can turn it on and off. When I set temperature it goes to “Health” mode (the same as fresh or purified?). When I set another mode it also goes to health mode.
It turns on when I select a mode like “cool or heat” but it goes on “Health”

I don’t have the knowhow from here. But changing the ESP32 for a ESP8266 gives opportunities.

A little late to the party, but did you check, if your unit is a Midea one? There would be a solution ready to use. It looks a lot like the Midea PCBs…

Check here: Midea branded AC’s with ESPhome (no cloud)

Try to delete or comment row 123:

traits.set_supports_action(true);

I’m using the Haierv2.h file from albetaCOM GitHub - albetaCOM/esp-haier:

I’ve modified Haierv2.h in order to eliminate deprecated warnings and I had your same problem.
For me solution has been to delete

traits.set_supports_action(true);

I’ve a workaround for the “Health” problem.
Below is a new version of Haierv2.h file.
Now is Haierv3.h
The Health problem seems to be related to the Purify flag.

If PurifyControl is set to false (row 471 of Haierv3.h ) the Purify status is toggle at every command
(don’t ask why)
So the solution is to send the message two times with an interval of 1 sec.
Look at row 759

  • Result is that the Purify status after the second message is maintained (if was ON remain ON, if was off remain OFF)

  • You can change the Purify using the remote.

  • Not a perfect solution, just a workaround :slight_smile:

I’ve also removed all the ‘deprecated’ messages.

Here is the modified file

/**
* Create by Miguel Ángel López on 20/07/19
* Modified by Alba Prades on 21/07/20
* Modified by Alba Prades on 13/08/20 
* Modified by Alba Prades on 25/08/20: Added fan, dry and swing features
*      Added modes
* Modified by Carlo Pinasco on 25/08/21: Workaround to fix Purify problem and fix deprecated warning messages.
*   
*   If PurifyControl is set to false (row 471) the Purify status is toggle at every command
*   (don't ask why)
*   So the solution is to send the message two times with an interval of 1 sec.
*   Look at row 759
*   Result is that the Purify status after the second message is maintened (if was ON remain ON, if was off remain OFF)
*   You can change the Purify using the remote.
*   Not a perfect solution, just a workaround :-)
*
*
**/

#ifndef HAIER_ESP_HAIER_H
#define HAIER_ESP_HAIER_H

#include "esphome.h"
#include <string>

using namespace esphome;
using namespace esphome::climate;

// Updated read offset

#define MODE_OFFSET 			14
#define MODE_MSK				0xF0
	#define MODE_AUTO       	0x00
	#define MODE_DRY			0x40
	#define MODE_COOL			0x20
	#define MODE_HEAT			0x80
	#define MODE_FAN			0xC0
#define FAN_MSK					0x0F
	#define FAN_LOW	    		0x03
	#define FAN_MID		  		0x02
	#define FAN_HIGH	     	0x01
	#define FAN_AUTO	   		0x05
	
#define HORIZONTAL_SWING_OFFSET		19
	#define HORIZONTAL_SWING_CENTER 		0x00
	#define HORIZONTAL_SWING_MAX_LEFT 		0x03
	#define HORIZONTAL_SWING_LEFT 			0x04
	#define HORIZONTAL_SWING_MAX_RIGHT 		0x06
	#define HORIZONTAL_SWING_RIGHT 			0x05
	#define HORIZONTAL_SWING_AUTO 			0x07
	
#define VERTICAL_SWING_OFFSET			13
	#define VERTICAL_SWING_MAX_UP			0x02
	#define VERTICAL_SWING_UP				0x04
	#define VERTICAL_SWING_CENTER				0x06
	#define VERTICAL_SWING_DOWN				0x08
	#define VERTICAL_SWING_HEALTH_UP			0x01
	#define VERTICAL_SWING_HEALTH_DOWN		0x03
	#define VERTICAL_SWING_AUTO 				0x0C

#define TEMPERATURE_OFFSET   	22

#define STATUS_DATA_OFFSET			17 // Purify/Quiet mode/OnOff/...
	#define POWER_BIT				(0)	
	#define PURIFY_BIT				(1)	
	#define QUIET_BIT				(3)	
	#define AUTO_FAN_MAX_BIT		(4)
	
#define SET_POINT_OFFSET 		12	

// Another byte
	#define SWING        		27
	#define SWING_OFF          	0
	#define SWING_VERTICAL     	1
	#define SWING_HORIZONTAL   	2
	#define SWING_BOTH

	#define LOCK        		28
	#define LOCK_ON     		80
	#define LOCK_OFF    		00

// Updated read offset



#define FRESH       			31
	#define FRESH_ON    		1
	#define FRESH_OFF   		0

// Updated read offset


#define COMMAND_OFFSET			9
	#define RESPONSE_POLL		2

#define CRC_OFFSET(message)		(2 + message[2])

// Control commands
#define CTR_POWER_OFFSET		13
	#define CTR_POWER_ON		0x01
	#define CTR_POWER_OFF		0x00
	
#define POLY 0xa001


// temperatures supported by AC system
#define MIN_SET_TEMPERATURE 16
#define MAX_SET_TEMPERATURE 30

//if internal temperature is outside of those boundaries, message will be discarded
#define MIN_VALID_INTERNAL_TEMP 10
#define MAX_VALID_INTERNAL_TEMP 50

class Haier : public Climate, public PollingComponent {

private:

    byte lastCRC;
    byte status[47];
	
	byte initialization_1[13] = {0xFF,0xFF,0x0A,0x0,0x0,0x0,0x0,0x0,0x00,0x61,0x00,0x07,0x72};
	byte initialization_2[13] = {0xFF,0xFF,0x08,0x40,0x0,0x0,0x0,0x0,0x0,0x70,0xB8,0x86,0x41};
 	byte poll[15] = {0xFF,0xFF,0x0A,0x40,0x00,0x00,0x00,0x00,0x00,0x01,0x4D,0x01,0x99,0xB3,0xB4};
    byte power_command[17]     = {0xFF,0xFF,0x0C,0x40,0x00,0x00,0x00,0x00,0x00,0x01,0x5D,0x01,0x00,0x01,0xAC,0xBD,0xFB};
	byte control_command[25] = {0xFF,0xFF,0x14,0x40,0x00,0x00,0x00,0x00,0x00,0x01,0x60,0x01,0x09,0x08,0x25,0x00,0x02,0x03,0x00,0x06,0x00,0x0C,0x03,0x0B,0x70};

	byte climate_mode_fan_speed = FAN_AUTO;
	byte climate_mode_setpoint = 0x0A;
	
	byte fan_mode_fan_speed = FAN_HIGH;
	byte fan_mode_setpoint = 0x08;
	
	bool first_status_received = false;
	
	// Some vars for debuging purposes	
	byte previous_status[47];
	bool previous_status_init = false;
	
	
	// Functions

	void SetHvacModeControl(byte mode)
	{
		control_command[MODE_OFFSET] &= ~MODE_MSK;
		control_command[MODE_OFFSET] |= mode;
	}	
	
	byte GetHvacModeStatus()
	{
		return status[MODE_OFFSET] & MODE_MSK;
	}	
	
	void SetTemperatureSetpointControl(byte temp)
	{ 
		control_command[SET_POINT_OFFSET] = temp;
	}	
	
	byte GetTemperatureSetpointStatus()
	{
		return status[SET_POINT_OFFSET];
	}	
	
	void SetFanSpeedControl(byte fan_mode)
	{
		control_command[MODE_OFFSET] &= ~FAN_MSK;
		control_command[MODE_OFFSET] |= fan_mode;
	}
	
	byte GetFanSpeedStatus()
	{
		return status[MODE_OFFSET] & FAN_MSK;
	}		
	
	void SetHorizontalSwingControl(byte swing_mode)
	{
		control_command[HORIZONTAL_SWING_OFFSET] = swing_mode;
	}
	
	byte GetHorizontalSwingStatus()
	{
		return status[HORIZONTAL_SWING_OFFSET];
	}	
	
	void SetVerticalSwingControl(byte swing_mode)
	{
		control_command[VERTICAL_SWING_OFFSET] = swing_mode;
	}
	
	byte GetVerticalSwingStatus()
	{
		return status[VERTICAL_SWING_OFFSET];
	}	
	
	void SetQuietModeControl(bool quiet_mode)
	{
		byte tmp;
		byte msk;
		
		msk = (0x01 << QUIET_BIT);		
		
		if(quiet_mode == true){
			control_command[STATUS_DATA_OFFSET] |= msk;
		}
		else{
			msk = ~msk;
			control_command[STATUS_DATA_OFFSET] &= msk;
		}
	}
	
	bool GetQuietModeStatus( void )
	{
		bool ret = false;		
		byte tmp;
		byte msk;
		
		msk = (0x01 << QUIET_BIT);
		tmp = status[STATUS_DATA_OFFSET] & msk;
		
		if(tmp != 0) ret = true;
		
		return ret;
	}
	
	
	void SetPurifyControl(bool purify_mode)
	{
		byte tmp;
		byte msk;
		
		msk = (0x01 << PURIFY_BIT);		
		
		if(purify_mode == true){
			control_command[STATUS_DATA_OFFSET] |= msk;
		}
		else{
			msk = ~msk;
			control_command[STATUS_DATA_OFFSET] &= msk;
		}
	}
	
	bool GetPurifyStatus( void )
	{
		bool ret = false;		
		byte tmp;
		byte msk;
		
		msk = (0x01 << PURIFY_BIT);
		tmp = status[STATUS_DATA_OFFSET] & msk;
		
		if(tmp != 0) ret = true;
		
		return ret;
	}
	
	void SetPowerControl(bool power_mode)
	{
		byte tmp;
		byte msk;
		
		msk = (0x01 << POWER_BIT);		
		
		if(power_mode == true){
			control_command[STATUS_DATA_OFFSET] |= msk;
		}
		else{
			msk = ~msk;
			control_command[STATUS_DATA_OFFSET] &= msk;
		}
	}
	
	bool GetPowerStatus( void )
	{
		bool ret = false;		
		byte tmp;
		byte msk;
		
		msk = (0x01 << POWER_BIT);
		tmp = status[STATUS_DATA_OFFSET] & msk;
		
		if(tmp != 0) ret = true;
		
		return ret;
	}
	
	
	bool GetFastModeStatus( void )
	{
		bool ret = false;		
		byte tmp;
		byte msk;
		
		msk = (0x01 << AUTO_FAN_MAX_BIT);
		tmp = status[STATUS_DATA_OFFSET] & msk;
		
		if(tmp != 0) ret = true;
		
		return ret;
	}
	
	void SetFastModeControl(bool fast_mode)
	{
		byte tmp;
		byte msk;
		
		msk = (0x01 << AUTO_FAN_MAX_BIT);		
		
		if(fast_mode == true){
			control_command[STATUS_DATA_OFFSET] |= msk;
		}
		else{
			msk = ~msk;
			control_command[STATUS_DATA_OFFSET] &= msk;
		}
	}
	
	
	void CompareStatusByte()
	{
		int i;
		
		if(previous_status_init == false){
			for (i=0;i<sizeof(status);i++){
				previous_status[i] = status[i];
			}
			previous_status_init = true;
		}
		
		for (i=0;i<sizeof(status);i++)
		{
			if(status[i] != previous_status[i]){
				ESP_LOGD("Debug", "Status byte %d: 0x%X --> 0x%X ", i, previous_status[i],status[i]);
			}
			previous_status[i] = status[i];
		}
	}


public:

    Haier() : PollingComponent(5 * 1000) {
        lastCRC = 0;
    }


    
    void setup() override {
        
        Serial.begin(9600);
		delay(1000);
		Serial.write(initialization_1, sizeof(initialization_1));
        auto raw = getHex(initialization_1, sizeof(initialization_1));
        ESP_LOGD("Haier", "initialization_1: %s ", raw.c_str());
		delay(1000);
		Serial.write(initialization_2, sizeof(initialization_2));
        raw = getHex(initialization_2, sizeof(initialization_2));
        ESP_LOGD("Haier", "initialization_2: %s ", raw.c_str());
    }

    void loop() override  {
		byte data[47];
        if (Serial.available() > 0) {
			if (Serial.read() != 255) return;
			if (Serial.read() != 255) return;
			
			data[0] = 255;
			data[1] = 255;

            Serial.readBytes(data+2, sizeof(data)-2);
			
			// If is a status response
			if (data[COMMAND_OFFSET] == RESPONSE_POLL) {
				// Update the status frame
				memcpy(status, data, sizeof(status));
				parseStatus();
			}
		}
    }

    void update() override {
        
        Serial.write(poll, sizeof(poll));
        auto raw = getHex(poll, sizeof(poll));
        ESP_LOGD("Haier", "POLL: %s ", raw.c_str());
    }

protected:
    ClimateTraits traits() override {
        auto traits = climate::ClimateTraits();
        //traits.set_supports_away(false);
        //traits.set_supports_auto_mode(true);
        //traits.set_supports_heat_mode(true);
        //traits.set_supports_cool_mode(true);
        //traits.set_supports_dry_mode(true);
        //traits.set_supports_fan_only_mode(true);
        //traits.set_supports_fan_mode_on(false);
        //traits.set_supports_fan_mode_off(false);
        //traits.set_supports_fan_mode_auto(true);
        //traits.set_supports_fan_mode_low(true);
        //traits.set_supports_fan_mode_medium(true);
        //traits.set_supports_fan_mode_middle(true);
        //traits.set_supports_fan_mode_high(true);
       // traits.set_supports_fan_mode_focus(false);
        //traits.set_supports_fan_mode_diffuse(false);		
        
        //traits.set_supports_action(true);

        traits.set_supported_modes(
        {
            climate::CLIMATE_MODE_OFF,
            climate::CLIMATE_MODE_COOL,
            climate::CLIMATE_MODE_HEAT,
            climate::CLIMATE_MODE_FAN_ONLY,
            climate::CLIMATE_MODE_DRY,
            climate::CLIMATE_MODE_AUTO
        });

        traits.set_supported_fan_modes(
        {
            climate::CLIMATE_FAN_AUTO,
            climate::CLIMATE_FAN_LOW,
            climate::CLIMATE_FAN_MEDIUM,
            climate::CLIMATE_FAN_HIGH,
            climate::CLIMATE_FAN_MIDDLE
            
        });

        traits.set_supported_swing_modes(
        {
            climate::CLIMATE_SWING_OFF,
            climate::CLIMATE_SWING_BOTH,
            climate::CLIMATE_SWING_VERTICAL,
            climate::CLIMATE_SWING_HORIZONTAL
        });

        traits.set_visual_min_temperature(MIN_SET_TEMPERATURE);
        traits.set_visual_max_temperature(MAX_SET_TEMPERATURE);
        traits.set_visual_temperature_step(1.0f);
        traits.set_supports_current_temperature(true);
        //traits.set_supports_swing_mode_off(true);
        //traits.set_supports_swing_mode_both(true);
        //traits.set_supports_swing_mode_vertical(true);
        //traits.set_supports_swing_mode_horizontal(true);		
        //traits.set_supports_action(true);// Cal identificar el byte
        return traits;
    }

public:

    void parseStatus() {


        auto raw = getHex(status, sizeof(status));
        ESP_LOGD("Haier", "Readed message ALBA: %s ", raw.c_str());

        byte check = getChecksum(status, sizeof(status));

        if (check != status[CRC_OFFSET(status)]) {
            ESP_LOGW("Haier", "Invalid checksum (%d vs %d)", check, status[CRC_OFFSET(status)]);
            return;
        }

        lastCRC = check;

        current_temperature = status[TEMPERATURE_OFFSET]/2;
        target_temperature = status[SET_POINT_OFFSET] + 16;

        if(current_temperature < MIN_VALID_INTERNAL_TEMP || current_temperature > MAX_VALID_INTERNAL_TEMP 
            || target_temperature < MIN_SET_TEMPERATURE || target_temperature > MAX_SET_TEMPERATURE){
            ESP_LOGW("Haier", "Invalid temperatures");
            return;
        }
		
		// Read all the info from the status message and update values in control message
		// so the next message is updated
		// This is usefull if there are manual changes with the remote control
		SetPowerControl(GetPowerStatus());
		SetHvacModeControl(GetHvacModeStatus());
		// workaround for Purify problem
        SetPurifyControl(false);
		SetQuietModeControl(GetQuietModeStatus());
		SetFastModeControl(GetFastModeStatus());
		SetFanSpeedControl(GetFanSpeedStatus());
		SetHorizontalSwingControl(GetHorizontalSwingStatus());
		SetVerticalSwingControl(GetVerticalSwingStatus());
		SetTemperatureSetpointControl(GetTemperatureSetpointStatus());
		
		if(GetHvacModeStatus() == MODE_FAN){
			fan_mode_fan_speed = GetFanSpeedStatus();
			fan_mode_setpoint = GetTemperatureSetpointStatus();
		}
		else{
			climate_mode_fan_speed = GetFanSpeedStatus();
			climate_mode_setpoint = GetTemperatureSetpointStatus();
		}
		
		// Flag to enable modifications from UI as we now know the status of the A/C
		first_status_received = true;

		
		// DEBUG DATA, uncomment what's needed
		//ESP_LOGW("Debug", "Power Status = 0x%X", GetPowerStatus());
		//ESP_LOGW("Debug", "HVAC Mode = 0x%X", GetHvacModeStatus());
		//ESP_LOGW("Debug", "Purify status = 0x%X", GetPurifyStatus());
		//ESP_LOGW("Debug", "Quiet mode Status = 0x%X", GetQuietModeStatus());
		//ESP_LOGW("Debug", "Fast mode Status = 0x%X", GetFastModeStatus());
		//ESP_LOGW("Debug", "Fan speed Status = 0x%X", GetFanSpeedStatus());
		//ESP_LOGW("Debug", "Horizontal Swing Status = 0x%X", GetHorizontalSwingStatus());
		//ESP_LOGW("Debug", "Vertical Swing Status = 0x%X", GetVerticalSwingStatus());
		//ESP_LOGW("Debug", "Set Point Status = 0x%X", GetTemperatureSetpointStatus());
		CompareStatusByte();
		
		
		// Update home assistant component
		
        if (GetPowerStatus() == false) {
            mode = CLIMATE_MODE_OFF;
		} else {
			// Check current hvac mode
            switch (GetHvacModeStatus()) {
                case MODE_COOL:
                    mode = CLIMATE_MODE_COOL;
                    break;
                case MODE_HEAT:
                    mode = CLIMATE_MODE_HEAT;
                    break;
                case MODE_DRY:
				    mode = CLIMATE_MODE_DRY;
					break;
				case MODE_FAN:
                    mode = CLIMATE_MODE_FAN_ONLY;
                    break;
                case MODE_AUTO:
                default:
                    mode = CLIMATE_MODE_AUTO;
            }
					
			// Get fan speed
			// If "quiet mode" is set we will read it as "fan low"
			if ( GetQuietModeStatus() == true) {
                fan_mode = CLIMATE_FAN_LOW;
            }
			// If we detect that fast mode is on the we read it as "fan high"
			else if( GetFastModeStatus() == true) {
				fan_mode = CLIMATE_FAN_HIGH;
			}			
			else {				
				// No quiet or fast so we read the actual fan speed.
                switch (GetFanSpeedStatus()) {
                    case FAN_AUTO:
                        fan_mode = CLIMATE_FAN_AUTO;
                        break;
                    case FAN_MID:
                        fan_mode = CLIMATE_FAN_MEDIUM;
                        break;
                    //case FAN_MIDDLE:
                    //    fan_mode = CLIMATE_FAN_MIDDLE;
                    //    break;
					case FAN_LOW:
						fan_mode = CLIMATE_FAN_LOW;
                        break;
                    case FAN_HIGH:
                        fan_mode = CLIMATE_FAN_HIGH;
                        break;
                    default:
                        fan_mode = CLIMATE_FAN_AUTO;
						
                }
            }				


			// Check the status of the swings (vertical and horizontal and translate according component configuration
			if( (GetHorizontalSwingStatus() == HORIZONTAL_SWING_AUTO) && (GetVerticalSwingStatus() == VERTICAL_SWING_AUTO) ){
				swing_mode = CLIMATE_SWING_BOTH;				
			}
			else if(GetHorizontalSwingStatus() == HORIZONTAL_SWING_AUTO){
				swing_mode = CLIMATE_SWING_HORIZONTAL;
			}
			else if(GetVerticalSwingStatus() == VERTICAL_SWING_AUTO){
				swing_mode = CLIMATE_SWING_VERTICAL;
			}
			else{
				swing_mode = CLIMATE_SWING_OFF;
			}
		}

        this->publish_state();

    }


    void control(const ClimateCall &call) override {
		
        ClimateMode new_mode;
		bool new_control_cmd = false;
		
		
		ESP_LOGD("Control", "Control call");
		
		if(first_status_received == false){
			ESP_LOGD("Control", "No action, first poll answer not received");
			return;
		}

        if (call.get_mode().has_value()) {
            // User requested mode change
            new_mode = *call.get_mode();
        
			ESP_LOGD("Control", "*call.get_mode() = %d", new_mode);
			
			// It seems that this message is no needed, we keep it here commented
						
			if((new_mode != CLIMATE_MODE_OFF) && (GetPowerStatus() == false)){
			//	 if the current mode is off -> we need to power on
				sendData(power_command, sizeof(power_command));  
				delay(1000);	
			}
			else 
			{
			
            switch (new_mode) {
                case CLIMATE_MODE_OFF:
					SetPowerControl(false);
					sendData(control_command, sizeof(control_command)); 
                    break;
					
                case CLIMATE_MODE_AUTO:
					SetPowerControl(true);
					SetHvacModeControl(MODE_AUTO);
					
					// Recover fan_speed and setpoint (when switching to fan_only they are "lost")
					SetFanSpeedControl(climate_mode_fan_speed);
					SetTemperatureSetpointControl(climate_mode_setpoint);	
						
					sendData(control_command, sizeof(control_command));
                    break;
					
                case CLIMATE_MODE_HEAT:	
					SetPowerControl(true);
					SetHvacModeControl(MODE_HEAT);
					
					// Recover fan_speed and setpoint (when switching to fan_only they are "lost")
					SetFanSpeedControl(climate_mode_fan_speed);
					SetTemperatureSetpointControl(climate_mode_setpoint);	
					
					sendData(control_command, sizeof(control_command));
                    break;
					
                case CLIMATE_MODE_DRY:		
					SetPowerControl(true);
					SetHvacModeControl(MODE_DRY);
					
					// Recover fan_speed and setpoint (when switching to fan_only they are "lost")
					SetFanSpeedControl(climate_mode_fan_speed);
					SetTemperatureSetpointControl(climate_mode_setpoint);	
										
					sendData(control_command, sizeof(control_command));					
                    break;
					
                case CLIMATE_MODE_FAN_ONLY:				
					SetPowerControl(true);
					SetHvacModeControl(MODE_FAN);				
					
					// Recover fan_speed and setpoint (fan_only values are "special")
					SetFanSpeedControl(fan_mode_fan_speed);
					SetTemperatureSetpointControl(fan_mode_setpoint);	
					
					sendData(control_command, sizeof(control_command));
                    break;

                case CLIMATE_MODE_COOL:
					SetPowerControl(true);
					SetHvacModeControl(MODE_COOL);
					
					// Recover fan_speed and setpoint (when switching to fan_only they are "lost")
					SetFanSpeedControl(climate_mode_fan_speed);
					SetTemperatureSetpointControl(climate_mode_setpoint);
					
					sendData(control_command, sizeof(control_command));
                    break;
            }
			}
            // Publish updated state
            mode = new_mode;
            this->publish_state();
		}
		
		        //Set fan speed
        if (call.get_fan_mode().has_value()) {
            switch(call.get_fan_mode().value()) {
                case CLIMATE_FAN_LOW:
					SetFanSpeedControl(FAN_LOW);
                    break;
                case CLIMATE_FAN_MIDDLE:
					SetFanSpeedControl(FAN_MID);
                    break;
                case CLIMATE_FAN_MEDIUM:
					SetFanSpeedControl(FAN_MID);
                    break;
                case CLIMATE_FAN_HIGH:
					SetFanSpeedControl(FAN_HIGH);
                    break;
                case CLIMATE_FAN_AUTO:
                    SetFanSpeedControl(FAN_AUTO);
                    break;
			}
			sendData(control_command, sizeof(control_command)); 
		}

        //Set swing mode
        if (call.get_swing_mode().has_value()){
            switch(call.get_swing_mode().value()) {
                case CLIMATE_SWING_OFF:
					// When not auto we decide to set it to the center
					SetHorizontalSwingControl(HORIZONTAL_SWING_CENTER);
					// When not auto we decide to set it to the center
					SetVerticalSwingControl(VERTICAL_SWING_CENTER);
                    break;
                case CLIMATE_SWING_VERTICAL:
					// When not auto we decide to set it to the center
                    SetHorizontalSwingControl(HORIZONTAL_SWING_CENTER);
					SetVerticalSwingControl(VERTICAL_SWING_AUTO);
                    break;
                case CLIMATE_SWING_HORIZONTAL:
                    SetHorizontalSwingControl(HORIZONTAL_SWING_AUTO);
					// When not auto we decide to set it to the center
					SetVerticalSwingControl(VERTICAL_SWING_CENTER);
                    break;
                case CLIMATE_SWING_BOTH:
                    SetHorizontalSwingControl(HORIZONTAL_SWING_AUTO);
					SetVerticalSwingControl(VERTICAL_SWING_AUTO);
                    break;
			}
			sendData(control_command, sizeof(control_command)); 
        }
		
		
		if (call.get_target_temperature().has_value()) {
		    float temp = *call.get_target_temperature();
			ESP_LOGD("Control", "*call.get_target_temperature() = %f", temp);
			control_command[SET_POINT_OFFSET] = (unsigned int) temp - 16;
			sendData(control_command, sizeof(control_command));			
			target_temperature = temp;
            this->publish_state();
		}
		
		
   }


    void sendData(byte * message, byte size) {
        byte crc_offset = CRC_OFFSET(message);
        byte crc = getChecksum(message, size);
        word crc_16 = crc16(0, &(message[2]), crc_offset-2);
        
        // Updates the crc
        message[crc_offset] = crc;
        message[crc_offset+1] = (crc_16>>8)&0xFF;
        message[crc_offset+2] = crc_16&0xFF;

        Serial.write(message, size); delay(1000);Serial.write(message, size);
        auto raw = getHex(message, size);
        ESP_LOGD("Haier", "Message sent: %s  - CRC: %X - CRC16: %X", raw.c_str(), crc, crc_16);

    }

    String getHex(byte * message, byte size) {


        String raw;

        for (int i=0; i < size; i++){
			raw += " " + String(message[i]);

        }
        raw.toUpperCase();

        return raw;


    }

    byte getChecksum(const byte * message, size_t size) {
		byte position = CRC_OFFSET(message);
        byte crc = 0;
        
        if (size < ( position)) {
        	ESP_LOGE("Control", "frame format error (size = %d vs length = %d)", size, message[2]);
        	return 0;
        }

        for (int i = 2; i < position; i++)
            crc += message[i];

        return crc;
    }


    unsigned crc16(unsigned crc, unsigned char *buf, size_t len)
    { 
        while (len--) {
            crc ^= *buf++;
            crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
            crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
            crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
            crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
            crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
            crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
            crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
            crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
        }
        return crc;
    }


};


#endif //HAIER_ESP_HAIER_H
1 Like

So this one should work for me too?

I can skip this below?

I’ve modified Haierv2.h in order to eliminate deprecated warnings and I had your same problem.
For me solution has been to delete

traits.set_supports_action(true);

Never mind it’s working thanks.

I have connected Wemos using you code. I can set modes, temperature etc, but hassio always shows climate entity as unavailableBez tytułu

I have tried to upgrade esphome on hassio, now i cant add wemos as climate device:

Traceback (most recent call last):
File “/usr/src/homeassistant/homeassistant/components/esphome/init.py”, line 214, in on_login
entity_infos, services = await cli.list_entities_services()
File “/usr/local/lib/python3.8/site-packages/aioesphomeapi/client.py”, line 137, in list_entities_services
entities.append(cls(**kwargs))
File “”, line 9, in init
self.supported_modes = __attr_converter_supported_modes(supported_modes)
File “/usr/local/lib/python3.8/site-packages/aioesphomeapi/model.py”, line 245, in _convert_climate_modes
return [ClimateMode(val) for val in value]
File “/usr/local/lib/python3.8/site-packages/aioesphomeapi/model.py”, line 245, in
return [ClimateMode(val) for val in value]
File “/usr/local/lib/python3.8/enum.py”, line 339, in call
return cls.new(cls, value)
File “/usr/local/lib/python3.8/enum.py”, line 662, in new
raise ve_exc
ValueError: 6 is not a valid ClimateMode

My device is model AS25S2SF2FA year 2021.
My yaml code is:

esphome:
  name: clima-studio
  platform: ESP8266
  board:   board: d1_mini
  includes:
    - Haierv3.h #my code
    
# Enable logging
logger:
  level: DEBUG
  baud_rate: 0 #Important. You can't use serial port
# Enable Home Assistant API
api:

ota:
  password: "8b77208d0177e64ea90bd3a1ae555326"

wifi:
  networks:  !include wifi_network.yaml  

    
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Clima-Studio Fallback Hotspot"
    password: "IUMKWgrC3oid"

captive_portal:

climate:
  - platform: custom
    lambda: |-
      auto haier = new Haier();
      App.register_component(haier);
      return {haier};
    climates:
      - name: "Clima Studio"

The difference from the original version (MiguelAngelLV) and my version (derived by the albetaCOM v2 version: [GitHub - albetaCOM/esp-haier: ESP8266 code to connect to Haier Air Conditioner ((firmware version R_1
is the firmware of the Haier Conditioner.

What is your version of esphome?

Copyright © 2019-2021 ESPHome | Made with Materialize

v2021.8.0

Hi, Does anyone is using Alexa or Google assistant to control the ESP Haier? My problem is that when I turn on with “Alexa turn on ac” or “OK google, turn on ac” Its always turning on in heat at 25°c no matter how was turned off before. Is there a way to change this defaul behavior to for example Heat/Cool 23°c?

Assuming you are using Haierv2.h file…

I had the same behavior with Alexa. Solution is available in my Haierv3.h file (look here: ESP Haier: Haier Air Conditioner + ESP Home + Wemos D1 mini - #133 by CarloPinasco)

At row 612 you can see what I’ve changed from the Haierv2.h file.

Is there a possibility to calibrate the temperature of my airco unit in ESP home?


I flashed with the haier.h version and I have no more control or temperature I no longer have the original file! help please

problem solved :hugs: known problem

I’m looking for how to activate silent mode! cordially