[Tutorial] Connect your dumb Midea AC Unit to Openhab with MQTT

Hi Giuseppe,

thank you very much for your tutorial!

I have a Midea/ Dimstal split air conditioner and I built the serial interface as described to control and monitor the system with OH3.

Receiving the data works fine so far.
Topic:openhab/in/DGKlima/state
Data: [{“is”:22. 5, “outside”:17.5, “conf”:{“on”:true, “turbo”:false, “eco”:false, “soll”:23, “lamelle”:true, “mode”:2, “fan”:0}}]

Unfortunately, my AC system always turns off immediately when I send the following data via MQTT:
Topic: openhab/out/DGKlima/command
Data: {“on”:true,“soll”:18,“lamelle”:true,“mode”:2,“fan”:0}

Do you have any idea what this could be?

One more request:
Would you provide your Things/ Items files, that would be a great help.

Thanks
Dirk

Hello,

Did you try to send the whole json : {“on”:true,“turbo”:false,“eco”:false,“soll”:23,“lamelle”:false,“mode”:4,“fan”:0}
I see in your json, you miss some properties.
I’m not currently home, I will post here my items and thing with my rules. :slight_smile:

Edit :
I made an update to the ino script, the last one has a problem, sometime the wifi disconnect, here’s the updated one :

/**
   I used this code with an ESP32 devkit 1
   and with a logic level converter :
   https://www.aliexpress.com/item/32482721005.html?spm=a2g0s.9042311.0.0.1dc64c4dDrGSej
   or https://protosupplies.com/product/i2c-logic-level-converter-with-regulator-module/
   I linked GPIO1 (Tx) and GPIO3 (RX) to BSCL and BSDA of the level shifter
   Linked the 3v3 pin of the esp32 to BVCC, and GND from esp32 to BGND

   Unsolderer the USB port from AC unit
   Linked the AC Unit GND to AGND
   Linked the AC Unit 5V to AVCC
   Linked the AC Unit RX to ASCL
   Linked the AC Unit TX to ASCL

   NOTE : In the mideaAC.h file, in the source folder, I removed this part :
   #ifdef __AVR__
      typedef void (*acSerialEvent)(ac_status_t * status);
    #else
      typedef std::function<void(ac_status_t * status)> acSerialEvent;
    #endif
       and replaced with :
    typedef std::function<void(ac_status_t * status)> acSerialEvent;


    Mqtt output command example :
    {"on":false,"turbo":false,"eco":false,"soll":23,"lamelle":false,"mode":4,"fan":off}
    
*/
#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <mideaAC.h>
#include <ArduinoJson.h>

// Replace the next variables with your SSID/Password combination
const char* ssid = "WIFI NAME";
const char* password = "PASSWORD";

// Add your MQTT Broker IP address, example:
const char* mqtt_server = "192.168.1.25";

// Replace the next variables with your SSID/Password combination
const char* mqttState = "esp32/acsalon/state";
const char* mqttCommand = "esp32/acsalon/command";

acSerial acSeria;

WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;
#define USE_SERIAL Serial2


void ACevent(ac_status_t * status) {

  if (client.connected()) {

    DynamicJsonDocument doc(1024);
    JsonArray array = doc.to<JsonArray>();


    JsonObject statusObj = array.createNestedObject();
    statusObj["ist"]     = status->ist;
    statusObj["aussen"]  = status->aussen;

    JsonObject confObj = statusObj.createNestedObject("conf");
    confObj["on"]      = status->conf.on;
    confObj["turbo"]   = status->conf.turbo;
    confObj["eco"]     = status->conf.eco;
    confObj["soll"]    = status->conf.soll;

    confObj["lamelle"] = status->conf.lamelle != acLamelleOff;
    confObj["mode"]    = (uint8_t)status->conf.mode;

    switch (status->conf.fan) {
      case acFAN1:
        confObj["fan"] = 1;
        break;
      case acFAN2:
        confObj["fan"] = 2;
        break;
      case acFAN3:
        confObj["fan"] = 3;
        break;
      case acFANA:
        confObj["fan"] = 0;
        break;
      default:
        confObj["fan"] = (uint8_t)status->conf.fan;
        break;
    }

    String output;
    serializeJson(doc, output);
    USE_SERIAL.print("[ACevent] JSON: ");
    USE_SERIAL.println(output);
    char charBuf[output.length() + 1];
    output.toCharArray(charBuf, output.length() + 1);
    // send state through mqttt
    client.publish(mqttState, charBuf);
  } else {
    USE_SERIAL.printf("[ACevent] ----------------------------\n");
    acSeria.print_status(status);
  }

}

void setup() {
  USE_SERIAL.begin(115200);

  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);

  Serial.begin(9600);
  acSeria.begin((Stream *)&Serial, "Serial");
  acSeria.send_getSN();

  acSeria.onStatusEvent(ACevent);
}

void setup_wifi() {
  delay(10);
  // We start by connecting to a WiFi network
  USE_SERIAL.println();
  USE_SERIAL.print("Connecting to ");
  USE_SERIAL.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    USE_SERIAL.print(".");
  }

  USE_SERIAL.println("");
  USE_SERIAL.println("WiFi connected");
  USE_SERIAL.println("IP address: ");
  USE_SERIAL.println(WiFi.localIP());
}

void callback(char* topic, byte* message, unsigned int length) {
  USE_SERIAL.print("Message arrived on topic: ");
  USE_SERIAL.print(topic);
  USE_SERIAL.print(". Message: ");
  StaticJsonDocument<256> doc;
  deserializeJson(doc, message, length);
  if (strcmp(topic, mqttCommand) == 0) {
  
    bool on        = doc["on"];
    bool turbo     = doc["turbo"];
    bool eco       = doc["eco"];
    ac_mode_t mode = (ac_mode_t)((uint8_t)doc["mode"]);
    bool lamelle   = doc["lamelle"];

    uint8_t fan    = doc["fan"];
    uint8_t soll   = doc["soll"];

    acSeria.send_conf_h(on, soll, fan, mode, lamelle, turbo, eco);
  }
  //acSeria.send_conf_h(true, 21, 1, acModeHeat, false, false, false);
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    if(WiFi.status() != WL_CONNECTED){
      WiFi.reconnect();

    }
    USE_SERIAL.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("acsalon", "openhabian", NULL)) {
      USE_SERIAL.println("connected");
      // Subscribe
      client.subscribe(mqttCommand);
    } else {
      USE_SERIAL.print("failed, rc=");
      USE_SERIAL.print(client.state());
      USE_SERIAL.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}
void loop() {
  
  if (!client.connected()) {
    reconnect();
  }
  client.loop();
  acSeria.loop();

  long now = millis();
  if (now - lastMsg > 5000) {
    lastMsg = now;
    acSeria.send_status(client.connected(), true);
    acSeria.request_status();
  }
}

Hi,
thanks for the update of the arduino sketch, i will compile and flash it later.

I tried to send the complete JSON, this caused the air conditioner to turn off immediately.
The short version without ECO Mode and TURBO Mode now works for me. Great!

I am currently testing everything with “MQTT Spy” and not yet with openHAB.

For the setup in openHAB 3.1 I would like to use your Things/Items/Rules.

Thanks a lot for your support.

Cheers

1 Like

Hello,

My rule :

triggers:
  - id: "4"
    configuration:
      groupName: SalonThermostat
    type: core.GroupCommandTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/javascript
      script: >-
        var logger =
        Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' +
        ctx.ruleUID);      

        var onOff = ir.getItem("ACSalonOnOff").state + '' === 'OFF' ? false : true;

        var turbo = ir.getItem("ACSalonTurbo").state+ '' === 'OFF' ? false : true;

        var eco = ir.getItem("ACSalonEco").state+ '' === 'OFF' ? false : true;

        var lamelle = ir.getItem("ACSalonLamelle").state+ '' === 'OFF' ? false : true;

        var mode = ir.getItem("ACSalonMode").state + '' ; var fan = ir.getItem("ACSalonFan").state + '' ; var temp = ir.getItem("ACSalonTemp").state; var logger = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' + ctx.ruleUID); 


        logger.info(event.itemName);


        switch (event.itemName) {
            case 'ACSalonMode':
                mode = event.itemCommand;
                break;
            case 'ACSalonFan':
                fan = event.itemCommand;
                break;
            case 'ACSalonLamelle':
                lamelle = event.itemCommand + '' === 'OFF' ? false : true;
                break;
            case 'ACSalonEco':
                eco = event.itemCommand + ''=== 'OFF' ? false : true;
                break;
            case 'ACSalonTurbo':
                turbo = event.itemCommand + ''=== 'OFF' ? false : true;
                break;
            case 'ACSalonOnOff':
                onOff = event.itemCommand + ''=== 'OFF' ? false : true;
                break;
            case 'ACSalonTemp':
                temp = event.itemCommand;
                break;                
            default:






                
        }

        actions.get("mqtt", "mqtt:broker:9c3815a8")
            .publishMQTT("esp32/acsalon/command", "{\"on\":" + onOff + ",\"turbo\":" + turbo + ",\"eco\":" + eco + ",\"soll\":" + temp + ",\"lamelle\":" + lamelle + ",\"mode\":" + mode + ",\"fan\":" + fan + "}");
    type: script.ScriptAction

My things :
NB : The command topic is a fake command topic, the command part is controlled by the rule

UID: mqtt:topic:9c3815a8:AC_SALON
label: AC Salon
thingTypeUID: mqtt:topic
configuration: {}
bridgeUID: mqtt:broker:9c3815a8
channels:
  - id: MODE
    channelTypeUID: mqtt:string
    label: Ac Salon Mode
    description: null
    configuration:
      commandTopic: esp32/acsalon/POURRI
      stateTopic: esp32/acsalon/state
      transformationPattern: JSONPATH:$.[0]conf.mode
  - id: TEMPERARURE
    channelTypeUID: mqtt:number
    label: Ac Salon Temperature
    description: null
    configuration:
      commandTopic: esp32/acsalon/POURRI
      min: 16
      stateTopic: esp32/acsalon/state
      transformationPattern: JSONPATH:$.[0]conf.soll
      max: 30
  - id: ON_OFF
    channelTypeUID: mqtt:switch
    label: AC Salon ON Off
    description: null
    configuration:
      commandTopic: esp32/acsalon/POURRI
      stateTopic: esp32/acsalon/state
      transformationPattern: JSONPATH:$.[0]conf.on
      off: "false"
      on: "true"
  - id: ECO
    channelTypeUID: mqtt:switch
    label: AC Salon Eco
    description: null
    configuration:
      commandTopic: esp32/acsalon/POURRI
      stateTopic: esp32/acsalon/state
      transformationPattern: JSONPATH:$.[0]conf.eco
      off: "false"
      on: "true"
  - id: TURBO
    channelTypeUID: mqtt:switch
    label: AC Salon turbo
    description: null
    configuration:
      commandTopic: esp32/acsalon/POURRI
      stateTopic: esp32/acsalon/state
      transformationPattern: JSONPATH:$.[0]conf.turbo
      off: "false"
      on: "true"
  - id: FAN
    channelTypeUID: mqtt:string
    label: Ac Salon FAN
    description: null
    configuration:
      commandTopic: esp32/acsalon/POURRI
      stateTopic: esp32/acsalon/state
      transformationPattern: JSONPATH:$.[0]conf.fan
  - id: LAMELLE
    channelTypeUID: mqtt:switch
    label: AC Salon LAMELLE
    description: null
    configuration:
      commandTopic: esp32/acsalon/POURRI
      stateTopic: esp32/acsalon/state
      transformationPattern: JSONPATH:$.[0]conf.lamelle
      off: "false"
      on: "true"

I don’t know how to export my items from the UI, do you have any clue?

Cheers :slight_smile:

I think that’s missing at the moment, but API Explorer can show the structure of Items in a similar way.

1 Like

Hello,

just wow! It works!

I have adapted your Things and Rules to my needs and created my Items.
I still create my Items and Things in files, so I can edit them better and also export them.

If someone is interested in my configuration for the Midea air conditioner, I can make them available here (they are in german though).

My big thanks go to Giuseppe and the original developer “Links2004” on github.

1 Like

Hi @Tukks,

do you have any idea what is needed to port this sketch to an ESP8266?

Kind regards,
Sebastian

Ok. Made I to compile for ESP8266. Had to do some changes like using ESP8266WiFi.h instead of WiFi.h and Serial instead of Serial2.

Actually I’m struggling with the wiring between the converter board and the ac. Could anyone (@DirkF or @Tukks) who has it running provide an image of the wiring? Any help is appreciated :grinning:

Hi Sebastian,
this was my test environment which works fine:


The connection between AC and the level converter is a cut USB cable from which the 4 wires black/green/white/red are connected directly to the level converter.
Does this help ?

Cheers,
Dirk

1 Like

Hi @DirkF,

thanks for the image. That helps and clarifies things :+1: Just fiddling around with the mqtt connection to get stable. Had a lot reconnects so have to dive into that first.

Regards
Sebastian

Want to let you know that all is working now. Thanks for your help :slight_smile:

PS: The reason for the mqtt reconnects in my case was too much log output to serial1 and delay statements which where only used for debugging purpose :grimacing:

2 Likes

I made some light improvements to your sketch and moved over to asynchronous libraries of wifi, mqtt and web server.

If anyone is interested it can be found under https://github.com/Eisi1482/midea-ac-async.

1 Like

Hello an good day,

i had changed the code for the esp 8266 with eisi1482 changes of the wifi and serial lib.
The connection to my mqtt broker is established but there is no communication between the a/a (midea).

Is it Possible to talk direcly with an successful installer of this solution like eisi or an other pro ?

thx spottich

Did you upload your code somewhere to review it? What do you mean by changes of serial lib? Did your solution worked before your changes? Had you successfully established a connection to your ac? If so break it down into little changes and see where it gets broken.

RE thx for the reply.

It is the complete Code from the first Post.

I switched now to an esp 32 where the upload is successful. But i have an brownout.
It is very curious. Now I have used an bigger powersolution an it runs.
But the cammand {“on”:true,“turbo”:false,“eco”:false,“soll”:18,“lamelle”:true,“mode”:1,“fan”:0}
didnt run. The A/C do nothing.

Could the last problem being that i use the sk-102 stick ?

thx

Hi Dirk,

would be great if you can share your rule File.
I also still edit my whole configuration in the files.

So far everything works with the ESP32 at the AirCon and openhab/mqtt. Thanks for your Picture from the breadboard.

My Issue right now is, that I have understood JsonPath as a two way Path to get and push the Data transformed to and from my items. That is where I am stuck right now if I want to keep the rules in the file version. I have successfully implemented the javascript ruleset via the Openhab GUI which i do not like to have an mix of GUI and file configuration.

My Things file:

Bridge mqtt:broker:openhab [host="192.168.1.10",secure="OFF"]{
        Thing topic acsalon {
        Channels:
                Type number : acsalon_mode "AC Salon Modus" [stateTopic="esp32/acsalon/state", commandTopic="esp32/acsalon/POURRI", transformationPattern="JSONPATH:$.[0]conf.mode"]
                Type number : acsalon_temperature "AC Salon Temperature" [stateTopic="esp32/acsalon/state", commandTopic="esp32/acsalon/POURRI", transformationPattern="JSONPATH:$.[0]conf.soll"]
                Type switch : acsalon_onoff "AC Salon ON Off" [stateTopic="esp32/acsalon/state", commandTopic="esp32/acsalon/com",  on="true", off="false", transformationPattern="JSONPATH:$.[0]conf.on" ]
                Type switch : acsalon_eco "AC Salon ECO" [stateTopic="esp32/acsalon/state", commandTopic="esp32/acsalon/POURRI",  on="true", off="false", transformationPattern="JSONPATH:$.[0]conf.eco" ]
                Type switch : acsalon_turbo "AC Salon Turbo" [stateTopic="esp32/acsalon/state", commandTopic="esp32/acsalon/POURRI",  on="true", off="false", transformationPattern="JSONPATH:$.[0]conf.turbo" ]
                Type string : acsalon_fan "Ac Salon FAN" [stateTopic="esp32/acsalon/state", commandTopic="esp32/acsalon/POURRI", transformationPattern="JSONPATH:$.[0]conf.fan"]
                Type switch : acsalon_lamelle "AC Salon LAMELLE"  [stateTopic="esp32/acsalon/state", commandTopic="esp32/acsalon/POURRI", on="true", off="false", transformationPattern="JSONPATH:$.[0]conf.lamelle"]

                Type number : acsalon_temperature_ist "AC Salon Temperature IST" [stateTopic="esp32/acsalon/state", commandTopic="esp32/acsalon/POURRI", transformationPattern="JSONPATH:$.[0].ist"]
                Type number : acsalon_temperature_aussen "AC Salon Temperature Außen" [stateTopic="esp32/acsalon/state", commandTopic="esp32/acsalon/POURRI", transformationPattern="JSONPATH:$.[0].aussen"]
        }
}

My Items file

Number klima_eg_modus "Klima EG Modus" { channel="mqtt:topic:openhab:acsalon:acsalon_mode" }
Number klima_eg_soll_temperatur "Klima EG Solltemperatur" { channel="mqtt:topic:openhab:acsalon:acsalon_temperature" }
Switch klima_eg_onoff "Klima EG Ein/Aus" { channel="mqtt:topic:openhab:acsalon:acsalon_onoff" }
Switch klima_eg_eco "Klima EG ECO Modus" { channel="mqtt:topic:openhab:acsalon:acsalon_eco" }
Switch klima_eg_turbo "Klima EG Turbo Modus" { channel="mqtt:topic:openhab:acsalon:acsalon_turbo" }
Number klima_eg_fan "Klima EG Lüfter Modus" { channel="mqtt:topic:openhab:acsalon:acsalon_fan" }
Switch klima_eg_lamelle "Klima EG Lamelle"  { channel="mqtt:topic:openhab:acsalon:acsalon_lamelle" }
Number klima_eg_innen "Klima EG Ist-Temperatur Innen" { channel="mqtt:topic:openhab:acsalon:acsalon_temperature_ist" }
Number klima_eg_aussen "Klima EG Ist-Temperatur Außen" { channel="mqtt:topic:openhab:acsalon:acsalon_temperature_aussen" }

My Sitemap file (including KNX Binary output Switch )

Frame label="Klimaanlage" {
              Switch item=AMS_13_Kanal_C_Schalten label="Klimaanlage Hauptstrom" icon=switch
              Switch item=klima_eg_onoff icon=climate
              Text item=AMS_13_Kanal_C_Leistungswert label="Leistungswert [%.0f W]" icon=energy
              Text item=AMS_13_Kanal_C_KiloWattStunden label="kWh [%.0f kWh]" icon=price
              Text item=klima_eg_aussen icon=temperature
              Text item=klima_eg_innen icon=temperature
              Setpoint item=klima_eg_soll_temperatur minValue=18 maxValue=25 step=1 icon=temperature
              Selection item=klima_eg_modus mappings=[1="AUTO", 2="KÜHLEN", 3="TROCKNEN", 4="HEIZEN", 5="LÜFTEN"] icon=pump
              Selection item=klima_eg_fan icon=fan mappings=[0="AUTO", 1="LOW", 2="MEDIUM", 3="HIGH"]
              Switch item=klima_eg_eco icon=bedroom mappings=[OFF="Aus", ON="An"]
              Switch item=klima_eg_turbo icon=flowpipe mappings=[OFF="Aus", ON="An"]
              Switch item=klima_eg_lamelle icon=flow mappings=[OFF="Aus", ON="An"]
              }

Hi kgfgfg1,

I edit my Things and Items in files, since these are rather static.
I edit my rules in the OH3 GUI, so I have the possibility to change a rule remotely and flexibly.
I think it should be no problem to put my rule in a file.

Here is my (GUI-) rule to control the air conditioner:

var logger = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' + ctx.ruleUID);      
var onOff = ir.getItem("Dachgeschoss_Klimaanlage_power_state").state + '' === 'OFF' ? false : true;
var turbo = ir.getItem("Dachgeschoss_Klimaanlage_turbo_mode").state+ '' === 'OFF' ? false : true;
var eco = ir.getItem("Dachgeschoss_Klimaanlage_eco_mode").state+ '' === 'OFF' ? false : true;
var lamelle = ir.getItem("Dachgeschoss_Klimaanlage_swing_mode").state+ '' === 'OFF' ? false : true;
var mode = ir.getItem("Dachgeschoss_Klimaanlage_operational_mode").state + '' ; 
var fan = ir.getItem("Dachgeschoss_Klimaanlage_fan_speed").state + '' ; 
var temp = ir.getItem("Dachgeschoss_Klimaanlage_target_temperature").state; 
var logger = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' + ctx.ruleUID); 

logger.info(event.itemName);

switch (event.itemName) {
    case 'Dachgeschoss_Klimaanlage_operational_mode':
        mode = event.itemCommand;
        break;
    case 'Dachgeschoss_Klimaanlage_fan_speed':
        fan = event.itemCommand;
        break;
    case 'Dachgeschoss_Klimaanlage_swing_mode':
        lamelle = event.itemCommand + '' === 'OFF' ? false : true;
        break;
    case 'Dachgeschoss_Klimaanlage_eco_mode':
        eco = event.itemCommand + ''=== 'OFF' ? false : true;
        break;
    case 'Dachgeschoss_Klimaanlage_turbo_mode':
        turbo = event.itemCommand + ''=== 'OFF' ? false : true;
        break;
    case 'Dachgeschoss_Klimaanlage_power_state':
        onOff = event.itemCommand + ''=== 'OFF' ? false : true;
        break;
    case 'Dachgeschoss_Klimaanlage_target_temperature':
        temp = event.itemCommand;
        break;                
    default:

       
}
actions.get("mqtt", "mqtt:broker:mosquitto")
    .publishMQTT("openhab/out/DGKlima/command", "{\"on\":" + onOff + ",\"turbo\":" + turbo + ",\"eco\":" + eco + ",\"soll\":" + temp + ",\"lamelle\":" + lamelle + ",\"mode\":" + mode + ",\"fan\":" + fan + "}");

Hope it helps
Greetings,
Dirk

Continuing the discussion from [Tutorial] Connect your dumb Midea AC Unit to Openhab with MQTT:

Hi, i have look to what you did and i try to do a project with arduino mega and have hard time to make it work if you could help me out i could compensate for you time best regards

Tell me how to replace the lines correctly.

class acSerial {
public:
#ifdef AVR
typedef void (*acSerialEvent)(ac_status_t * status);
#else
typedef std::function<void(ac_status_t * status)> acSerialEvent;
#endif

Hi Sebastian,
The libraries you used need an update: (I changed the platformio.ini file accordingly):
lib_deps =
alanswx/ESPAsyncWiFiManager@^0.31
ayushsharma82/AsyncElegantOTA@^2.2.7
marvinroger/AsyncMqttClient@^0.9.0
me-no-dev/ESP Async WebServer@^1.2.3
bblanchon/ArduinoJson@^6.21.3

But I am getting an error regarding get and setHostname…?

Do you have any udated version or a working binary for ESP8266?