Links2004 / arduinoMideaAC

hack job for decoding and sending Midea AC Serial commands
GNU Lesser General Public License v2.1
34 stars 8 forks source link

Tutorial to connect and use this repo #2

Closed Tukks closed 3 years ago

Tukks commented 3 years ago

Hello,

I have 2 midea air con with 2 "USB" entry (sell for usb wifi key) I want to connect my AC with Openhab, do you have any tutorial how to build and control my AC with your code? How to connect an ESP8266, do I need to remove the USB ? After that, How to send command to it?

Thank for your code and your help.

Links2004 commented 3 years ago

Hi,

currently there is no full tutorial. yes the "USB" key needs to removed since you can not have 2 serial devices on the same connection.

the connection is quit simple.

USB connector <-- RX / TX --> level shifter <-- RX / TX --> ESP8266 USB connector <-- 5V / GND --> LDO or DC/DC converter <-- 3V3 / GND --> ESP8266

not sure what the best way is to connect to Openhab, but the code allows you to implement any interface you like. HTTP / rest, MQTT, Websocket implement what works for Openhab.

a simple program that controls the AC can be found here: https://github.com/Links2004/arduinoMideaAC/blob/master/examples/esp8266/enableAC/enableAC.ino

you only need to integrate it with the API you need for Openhab. call send_conf_h to send settings to the AC. (https://github.com/Links2004/arduinoMideaAC/blob/master/src/mideaAC.h#L71) the ACevent is called on status updates from the AC. (https://github.com/Links2004/arduinoMideaAC/blob/master/src/mideaAC.h#L48-L52)

Tukks commented 3 years ago

Thank's for your reponse. MQTT will do the trick. I ordered the level shifter and the LDO, and if I succesful control my AC I will make a pull request here with the mqtt example and maybe make a tutorial for the electronic part.

Thank's for your help

Links2004 commented 3 years ago

sure a MQTT example is welcome and more info for the electronic too. locally i have code for using websockets, but since the protocol is used for my own home automation I have not released it since no one can really use it, if you need it as example I can post it.

Tukks commented 3 years ago

Yes I would want your example, it would be easier for me.

Thank for you help

Links2004 commented 3 years ago

The code here connects to a socketio server, so most if the code is websocket and JSON related :)

Serial is used for AC communication. Serial1 is for debug info.

#include <Arduino.h>

#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>

#include <ArduinoJson.h>

#include <WebSocketsClient.h>
#include <SocketIOclient.h>

#include <ArduinoUniqueID.h>

#include <mideaAC.h>

#include <Hash.h>

#define SIO_EVENT_PREFIX "ac::eg::schlafzimmer::"

ESP8266WiFiMulti WiFiMulti;
SocketIOclient socketIO;

acSerial s1;

#define USE_SERIAL Serial1

void register_event(const char * name, bool reg = true) {
    if(!socketIO.isConnected()) {
        USE_SERIAL.printf("[register_event] socket.IO not connected!\n");
        return;
    }

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

    array.add("-register_event");
    array.add(name);
    array.add(reg);

    String output;
    serializeJson(doc, output);
    USE_SERIAL.print("[register_event] JSON: ");
    USE_SERIAL.println(output);
    socketIO.sendEVENT(output);
}

void socketIOEvent(socketIOmessageType_t type, uint8_t * payload, size_t length) {
    switch(type) {
        case sIOtype_DISCONNECT:
            USE_SERIAL.printf("[IOc] Disconnected!\n");
            s1.send_status(false, false);
            break;
        case sIOtype_CONNECT:
            USE_SERIAL.printf("[IOc] Connected to url: %s\n", payload);
            s1.send_status(true, false);

            register_event(SIO_EVENT_PREFIX "conf");
            register_event(SIO_EVENT_PREFIX "reboot");
            break;
        case sIOtype_EVENT: {
            // creat JSON message for Socket.IO (ack)
            DynamicJsonDocument docOut(1024);
            JsonArray array = docOut.to<JsonArray>();

            char * sptr = NULL;
            int id = strtol((char *)payload, &sptr, 10);
            USE_SERIAL.printf("[IOc] get event: %s id: %d\n", payload, id);
            if(id) {
                payload = (uint8_t *)sptr;
            }
            DynamicJsonDocument doc(1024);
            DeserializationError error = deserializeJson(doc, payload, length);
            if(error) {
                USE_SERIAL.print(F("deserializeJson() failed: "));
                USE_SERIAL.println(error.c_str());
                return;
            }

            String eventName = doc[0];
            USE_SERIAL.printf("[IOc] event name: %s\n", eventName.c_str());
            if(eventName == SIO_EVENT_PREFIX "reboot") {
                USE_SERIAL.println("reboot Triggert via Network...");
                USE_SERIAL.flush();
                ESP.restart();
            } else if(eventName == SIO_EVENT_PREFIX "conf") {
                bool on        = doc[1]["on"];
                bool turbo     = doc[1]["turbo"];
                bool eco       = doc[1]["eco"];
                ac_mode_t mode = (ac_mode_t)((uint8_t)doc[1]["mode"]);
                bool lamelle   = doc[1]["lamelle"];
                uint8_t fan    = doc[1]["fan"];
                uint8_t soll   = doc[1]["soll"];
                s1.send_conf_h(on, soll, fan, mode, lamelle, turbo, eco);

                if(id) {             
                    array.add(nullptr);
                    array.add(doc[1]);
                }
            } else {
                 array.add("event unknown");
            }

            // Message Includes a ID for a ACK (callback)
            if(id) {   
                // JSON to String (serializion)
                String output;
                output += id;
                serializeJson(docOut, output);

                USE_SERIAL.print("[ACconfACK] JSON: ");
                USE_SERIAL.println(output);                

                // Send event        
                socketIO.send(sIOtype_ACK, output);
            }
        } break;
        case sIOtype_ACK:
            USE_SERIAL.printf("[IOc] get ack: %u\n", length);
            hexdump(payload, length);
            break;
        case sIOtype_ERROR:
            USE_SERIAL.printf("[IOc] get error: %u\n", length);
            hexdump(payload, length);
            break;
        case sIOtype_BINARY_EVENT:
            USE_SERIAL.printf("[IOc] get binary: %u\n", length);
            hexdump(payload, length);
            break;
        case sIOtype_BINARY_ACK:
            USE_SERIAL.printf("[IOc] get binary ack: %u\n", length);
            hexdump(payload, length);
            break;
    }
}

void WiFiEvent(WiFiEvent_t event) {
    USE_SERIAL.printf("[WiFi-event] event: %d\n", event);
    if(event != WIFI_EVENT_STAMODE_CONNECTED) {
        s1.send_status(false, false);
    }
}

void ACevent(ac_status_t * status) {
    if(socketIO.isConnected()) {
        DynamicJsonDocument doc(1024);
        JsonArray array = doc.to<JsonArray>();

        array.add(SIO_EVENT_PREFIX "status");

        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);
        socketIO.sendEVENT(output);
    } else {
        USE_SERIAL.printf("[ACevent] ----------------------------\n");
        s1.print_status(status);
    }
}

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

    //Serial.setDebugOutput(true);
    USE_SERIAL.setDebugOutput(true);

    USE_SERIAL.println();
    USE_SERIAL.println();
    USE_SERIAL.println();

    for(uint8_t t = 4; t > 0; t--) {
        USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t);
        USE_SERIAL.flush();
        delay(1000);
    }

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

    s1.onStatusEvent(ACevent);

    // disable AP
    if(WiFi.getMode() & WIFI_AP) {
        WiFi.softAPdisconnect(true);
    }

    WiFi.onEvent(WiFiEvent);

    WiFiMulti.addAP("IoT-WIFI", "password");

    //WiFi.disconnect();
    while(WiFiMulti.run() != WL_CONNECTED) {
        delay(100);
    }

    String ip = WiFi.localIP().toString();

    USE_SERIAL.printf("[SETUP] WiFi Connected %s\n", ip.c_str());

    // server address, port and URL
    socketIO.begin("192.168.0.10", 80);

    // event handler
    socketIO.onEvent(socketIOEvent);
}

unsigned long statusMillis = 0;

void loop() {
    socketIO.loop();
    s1.loop();
    unsigned long currentMillis = millis();
    if(currentMillis - statusMillis >= 5000) {
        statusMillis = currentMillis;
        s1.send_status(socketIO.isConnected(), true);
        s1.request_status();
    }
    //s1.send_conf_h(true, 21, 1, acModeCool, false, false, false);
}
Tukks commented 3 years ago

Hello,

Sorry to bother you, I'm better in software engineering than in electronics. I bought this : image Vi connected to USB connector, Vo to the 3v3 pin ESP32 GND to GND ESP32 USB Connector GND to second GND of esp32

now the level shifter I have this from Aliexpress : https://www.aliexpress.com/item/32482721005.html

IIC I2C level conversion module 5-3v system compatibility. AVCC connected to 5V system supply ASCL connected to 5V system SCL ASDA SDA connected to 5V system GND AGND connected to 5V system

BVCC connect to 3V system power BSCL connect to 3V system SCL BSDA connect to 3V system SDA BGND connect to 3V system GND

ASCL to USB connector TX BSCL to ESP32 RX ASDA to USB connector RX BSDA to ESP32 RX

Are we good? maybe the level shifter is not suitable for that?

Thanks for your help

Links2004 commented 3 years ago

no worry I am a Software Engineering too. electronics is only a hobby for me ;)

from the description it is looking good, if the level shifter that is designend for I2C will work with Serial is hard to say but there is a good change it will since both technologies are idle High.

based on what esp32 module you have there may is already a 5V to 3V3 LDO on it (all modules that can be programmed via USB port have one) but it will not hurt anything to us a external one.

Tukks commented 3 years ago

It work! Thank again for your great work I made a PR with MQTT Example on an ESP32. 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; If i didn't do that, Arduino Ide didn't want to compile. I didn't included in the PR

I didn't use the level shifter, only IIC logic level controller. I wrote everything in the header of the file