Haier Klimaanlagensteuerung basierend auf ESP8266

In der Fortsetzung der Artikel zum Bau eines „Smart Home“ ... verfĂŒgt die



Klimaanlage der Haier Lightera-Serie ĂŒber ein WiFi-Modul an Bord, mit dem sie ĂŒber eine Anwendung auf dem Telefon gesteuert werden kann, die ĂŒber einen unbekannten chinesischen Cloud-Dienst funktioniert. Bei Ă€lteren Modellen war das Modul eine Option und wurde separat erworben. Es wird an die Steuerplatine im InnengerĂ€t angeschlossen. Bei neuen Modellen befindet sich der Stecker unter einer dekorativen Verkleidung, und in der Lightera-Serie ist das Modul bereits installiert. Daher ist dieses GerĂ€t fĂŒr viele Klimaanlagen der Marke Haier geeignet.

Um die Klimaanlage ĂŒber das native WiFi-Modul zu steuern, mĂŒssen Sie die Anwendung auf Ihr Smartphone / Tablet herunterladen, sich registrieren und Ihr Smartphone / Tablet ĂŒber WLAN mit dem Router verbinden. Schalten Sie die Klimaanlage im KĂŒhlmodus mit der minimalen LĂŒftergeschwindigkeit um 30 Grad ein, stellen Sie sicher, dass das Haier-uAC-Netzwerk angezeigt wurde, und starten Sie das Programm zur Suche nach GerĂ€ten und Netzwerken. Das Programm findet Ihre Klimaanlage und verfĂŒgbare Netzwerke. Sie registrieren Ihr Netzwerk, indem Sie es aus der Liste auswĂ€hlen und Ihr GerĂ€temodell (Klimaanlage) registrieren. In meinem Heimnetzwerk ist der DHCP-Server auf dem Router deaktiviert. Um eine Verbindung zu meinem WLAN-Netzwerk auf dem verbundenen GerĂ€t herzustellen, mĂŒssen Sie eine neue Verbindung herstellen und sich dort zusĂ€tzlich zur SSID (da diese ausgeblendet ist) und dem Kennwort sowie einer statischen IP-Adresse registrieren.Aus diesem Grund konnte ich meine Klimaanlage nicht zur Anwendung hinzufĂŒgen, da ich beim HinzufĂŒgen einer Klimaanlage aufgefordert werde, nur den WLAN-Zugangspunkt und das Kennwort auszuwĂ€hlen. Die Anwendung sendet die eingegebenen Daten an das WiFi-Modul der Klimaanlage und versucht anhand dieser Daten, eine Verbindung zu Ihrem Zugangspunkt herzustellen, in der Hoffnung, dass ihm eine IP-Adresse zugewiesen wird, aber mein Router bricht alle Hoffnungen.

Aussehen des nativen WiFi-Moduls.

Aussehen des nativen WiFi Haier-Moduls


FĂŒr den Test habe ich es immer noch ĂŒber einen anderen Router angeschlossen. Die Verwaltung ĂŒber die Anwendung funktioniert, aber es gibt keine Möglichkeit, die Klimaanlage ohne die Anwendung zu steuern. Über den Cloud-Service ist nicht klar, es gibt kein persönliches Konto. Infolgedessen hat Haier, wie viele GerĂ€tehersteller, mit ihrer Anwendung ihre eigene Hardware erstellt, ohne die Möglichkeit der Integration in andere Automatisierungssysteme (ohne spezielle Module und GerĂ€te). Am Ende entschied ich mich, mein WiFi-Modul mit allen Eigenschaften eines bekannten Charakters zu gestalten.

Die Basis war ESP8266 12F, das ĂŒber das MQTT-Protokoll direkt mit meinem Server zusammenarbeitet. IOBroker ist auf dem Server installiert , der auch als MQTT-Server fungiert.

Es blieb das Austauschprotokoll mit der Klimaanlage selbst zu verstehen. Nachdem die Schaltkreise der nativen Module und SteuergerĂ€te frĂŒherer Modelle untersucht wurden, wurde klar, dass das WiFi-Modul ĂŒber einen regulĂ€ren UART mit TTL-Pegeln mit der Klimaanlage kommuniziert. Nachdem ich den UART / USB-Adapter parallel zur RX / TX-Leitung angeschlossen und die Klimaanlage ĂŒber die Anwendung und die Fernbedienung gesteuert habe, habe ich alle Daten gelesen.

Foto Motherboard Motherboard.

Haier Native WiFi Module Board


Die Karte zeigt einen 3,3-V-DC / DC-Wandler und Logikpegelwandler. Der Bildschirm begann nicht zu schießen, was darunter unbekannt ist.

Haier Native WiFi Module Board


Dies ist meine erste Erfahrung mit der Protokollumkehr, aber meiner Meinung nach erwies sich das Protokoll als sehr einfach.
Der Wechselkurs betrÀgt 9600/8-N-1. Das WiFi-Modul sendet alle 13 Sekunden (13 Byte) eine Anfrage, an die die Klimaanlage ein Paket (37 Byte) mit allen Daten ausgibt. Unter dem Spoiler eine Liste von Bytes, die sich als entwirrt herausstellten.

Austauschprotokoll
1 — FF c
2 — FF c
3 — 22
4 — 00
5 — 00
6 — 00
7 — 00
8 — 00
9 — 01
10 — 01 — , 02 —
11 — 4D — , 6D —
12 — 5F —
13 — 00
14 — 1A — 26 , 1B — 27,
15 — 00
16 — 00
17 — 00
18 — 00 — , 7F-
19 — 00
20 — 00
21 — 00
22 — 00
23 — 00
24 — 00 — smart, 01 — cool, 02 — heat, 03 — , 04 — DRY,
25 — 00
26 — 00 — max, 01 — mid, 02 — min, 03 — auto — FanSpeed
27 — 00
28 — 00 — ., 01 — . 02 — / . 03 —
29 — 00 — , 80 .
30 — 00 — power off, x1 — power on, (1x ) — ? x9 — QUIET
31 — 00
32 — 00 — fresh off, 01 — fresh on
33 — 00
34 — 00
35 — 00
36 — 00 — 16 , 01 — 17 0E — 30 .
37 — . .

Kurze Teams
FF FF 0A 00 00 00 00 00 01 01 4D 02 5B
FF FF 0A 00 00 00 00 00 01 01 4D 03 5C
FF FF 0A 00 00 00 00 00 01 03 00 00 0E
FF FF 0A 00 00 00 00 00 01 01 4D 01 5A

Zum Einstellen der Temperatur muss beispielsweise Folgendes gesendet werden:
FF FF 22 00 00 00 00 00 01 01 4D 5F 00 00 00 00 00 00 00 00 00 00 01 00 02 00 00 00 00 00 00 00 00 04 04 D8 - auf 20 Grad eingestellt.

Wir zeichnen den Schaltplan. Die Schaltung wird von der Klimaanlage mit 5 Volt gespeist. Da die Versorgungsspannung des ESP8266 3,3 Volt betrĂ€gt, gibt es einen linearen Stabilisator LM1117 (AMS1117) fĂŒr die entsprechende Ausgangsspannung. Logikpegelwandler sind an den Elementen R1, Q1, R3 und R2, R3 montiert, da der RXD TXD des ESP8266-Moduls 5 V nicht toleriert. Zum Programmieren des ESP mĂŒssen die Kontakte U2 U3 zusammen geschlossen werden.
Schaltplan.
Schema


Wir zĂŒchten eine Leiterplatte. Das Layout der Platine wird fĂŒr die Installation im GehĂ€use ĂŒber das native WiFi-Modul erstellt.
Leiterplatte

Leiterplatte

Auf dem Foto unten das Testboard.

Der Code ist in einer Arduino-Umgebung geschrieben. Die aktuelle Version ist auf GitHub verfĂŒgbar .
Code
#include <ESP8266WiFi.h>
#include <PubSubClient.h>

const char* ssid = "...";
const char* password = "...";
const char* mqtt_server = "xx.xx.xx.xx"; // MQTT

IPAddress ip(xx,xx,xx,x); //IP 
IPAddress gateway(xx,xx,xx,xx); // 
IPAddress subnet(xx,xx,xx,xx); // 

WiFiClient espClient;
PubSubClient client(espClient);

#define ID_CONNECT "myhome-Conditioner"
#define LED     12
#define LEN_B   37

#define B_CUR_TMP   13  // 
#define B_CMD       17  // 00- 7F- ???
#define B_MODE      23  //04 - DRY, 01 - cool, 02 - heat, 00 - smart 03 - 
#define B_FAN_SPD   25  // 02 - min, 01 - mid, 00 - max, 03 - auto
#define B_SWING     27  //01 -     . 00 - . 02 - / . 03 -  
#define B_LOCK_REM  28  //80  . 00 -  
#define B_POWER     29  //on/off 01 - on, 00 - off (10, 11)-??? 09 - QUIET
#define B_FRESH     31  //fresh 00 - off, 01 - on
#define B_SET_TMP   35  // 

int fresh;
int power;
int swing;
int lock_rem;
int cur_tmp;
int set_tmp;
int fan_spd;
int Mode;
long prev = 0;
byte inCheck = 0;
byte qstn[] = {255,255,10,0,0,0,0,0,1,1,77,1,90}; //  
//byte start[] = {255,255};
byte data[37] = {}; // 
byte on[]   = {255,255,10,0,0,0,0,0,1,1,77,2,91}; //  
byte off[]  = {255,255,10,0,0,0,0,0,1,1,77,3,92}; //  
byte lock[] = {255,255,10,0,0,0,0,0,1,3,0,0,14};  //  
//byte buf[10];

void setup_wifi() {
  delay(10);
  WiFi.begin(ssid, password);
  WiFi.config(ip, gateway, subnet);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    digitalWrite(LED, !digitalRead(LED));
  }
  digitalWrite(LED, HIGH);
}

void reconnect() {
  digitalWrite(LED, !digitalRead(LED));
  while (!client.connected()) {
    if (client.connect(ID_CONNECT)) {
      client.publish("myhome/Conditioner/connection", "true");
      client.publish("myhome/Conditioner/RAW", "");
      client.subscribe("myhome/Conditioner/#");
      digitalWrite(LED, HIGH);
    } else {
      delay(5000);
    }
  }
}

void InsertData(byte data[], size_t size){
    set_tmp = data[B_SET_TMP]+16;
    cur_tmp = data[B_CUR_TMP];
    Mode = data[B_MODE];
    fan_spd = data[B_FAN_SPD];
    swing = data[B_SWING];
    power = data[B_POWER];
    lock_rem = data[B_LOCK_REM];
    fresh = data[B_FRESH];
  /////////////////////////////////
  if (fresh == 0x00){
      client.publish("myhome/Conditioner/Fresh", "off");
  }
  if (fresh == 0x01){
      client.publish("myhome/Conditioner/Fresh", "on");
  }
  /////////////////////////////////
  if (lock_rem == 0x80){
      client.publish("myhome/Conditioner/Lock_Remote", "true");
  }
  if (lock_rem == 0x00){
      client.publish("myhome/Conditioner/Lock_Remote", "false");
  }
  /////////////////////////////////
  if (power == 0x01 || power == 0x11){
      client.publish("myhome/Conditioner/Power", "on");
  }
  if (power == 0x00 || power == 0x10){
      client.publish("myhome/Conditioner/Power", "off");
  }
  if (power == 0x09){
      client.publish("myhome/Conditioner/Power", "quiet");
  }
  if (power == 0x11 || power == 0x10){
      client.publish("myhome/Conditioner/Compressor", "on");
  } else {
    client.publish("myhome/Conditioner/Compressor", "off");
  }
  /////////////////////////////////
  if (swing == 0x00){
      client.publish("myhome/Conditioner/Swing", "off");
  }
  if (swing == 0x01){
      client.publish("myhome/Conditioner/Swing", "ud");
  }
  if (swing == 0x02){
      client.publish("myhome/Conditioner/Swing", "lr");
  }
  if (swing == 0x03){
      client.publish("myhome/Conditioner/Swing", "all");
  }
  /////////////////////////////////  
  if (fan_spd == 0x00){
      client.publish("myhome/Conditioner/Fan_Speed", "max");
  }
  if (fan_spd == 0x01){
      client.publish("myhome/Conditioner/Fan_Speed", "mid");
  }
  if (fan_spd == 0x02){
      client.publish("myhome/Conditioner/Fan_Speed", "min");
  }
  if (fan_spd == 0x03){
      client.publish("myhome/Conditioner/Fan_Speed", "auto");
  }
  /////////////////////////////////
  char b[5]; 
  String char_set_tmp = String(set_tmp);
  char_set_tmp.toCharArray(b,5);
  client.publish("myhome/Conditioner/Set_Temp", b);
  ////////////////////////////////////
  String char_cur_tmp = String(cur_tmp);
  char_cur_tmp.toCharArray(b,5);
  client.publish("myhome/Conditioner/Current_Temp", b);
  ////////////////////////////////////
  if (Mode == 0x00){
      client.publish("myhome/Conditioner/Mode", "smart");
  }
  if (Mode == 0x01){
      client.publish("myhome/Conditioner/Mode", "cool");
  }
  if (Mode == 0x02){
      client.publish("myhome/Conditioner/Mode", "heat");
  }
  if (Mode == 0x03){
      client.publish("myhome/Conditioner/Mode", "vent");
  }
  if (Mode == 0x04){
      client.publish("myhome/Conditioner/Mode", "dry");
  }
  
  String raw_str;
  char raw[75];
  for (int i=0; i < 37; i++){
     if (data[i] < 10){
       raw_str += "0";
       raw_str += String(data[i], HEX);
     } else {
      raw_str += String(data[i], HEX);
     }    
  }
  raw_str.toUpperCase();
  raw_str.toCharArray(raw,75);
  client.publish("myhome/Conditioner/RAW", raw);
  
///////////////////////////////////
}

byte getCRC(byte req[], size_t size){
  byte crc = 0;
  for (int i=2; i < size; i++){
      crc += req[i];
  }
  return crc;
}

void SendData(byte req[], size_t size){
  //Serial.write(start, 2);
  Serial.write(req, size - 1);
  Serial.write(getCRC(req, size-1));
}

inline unsigned char toHex( char ch ){
   return ( ( ch >= 'A' ) ? ( ch - 'A' + 0xA ) : ( ch - '0' ) ) & 0x0F;
}

void callback(char* topic, byte* payload, unsigned int length) {
  payload[length] = '\0';
  String strTopic = String(topic);
  String strPayload = String((char*)payload);
  ///////////
  if (strTopic == "myhome/Conditioner/Set_Temp"){
    set_tmp = strPayload.toInt()-16;
    if (set_tmp >= 0 && set_tmp <= 30){
      data[B_SET_TMP] = set_tmp;      
    }
  }
  //////////
  if (strTopic == "myhome/Conditioner/Mode"){
     if (strPayload == "smart"){
      data[B_MODE] = 0; 
    }
    if (strPayload == "cool"){
        data[B_MODE] = 1;
    }
    if (strPayload == "heat"){
        data[B_MODE] = 2; 
    }
    if (strPayload == "vent"){
        data[B_MODE] = 3;
    }
    if (strPayload == "dry"){
        data[B_MODE] = 4;
    }
  }
  //////////
  if (strTopic == "myhome/Conditioner/Fan_Speed"){
     if (strPayload == "max"){
      data[B_FAN_SPD] = 0; 
    }
    if (strPayload == "mid"){
        data[B_FAN_SPD] = 1;
    }
    if (strPayload == "min"){
        data[B_FAN_SPD] = 2; 
    }
    if (strPayload == "auto"){
        data[B_FAN_SPD] = 3; 
    }
  }
  ////////
  if (strTopic == "myhome/Conditioner/Swing"){
     if (strPayload == "off"){
      data[B_SWING] = 0; 
    }
    if (strPayload == "ud"){
        data[B_SWING] = 1;
    }
    if (strPayload == "lr"){
        data[B_SWING] = 2; 
    }
    if (strPayload == "all"){
        data[B_SWING] = 3; 
    }
  }
  ////////
  if (strTopic == "myhome/Conditioner/Lock_Remote"){
     if (strPayload == "true"){
      data[B_LOCK_REM] = 80;
    }
    if (strPayload == "false"){
        data[B_LOCK_REM] = 0;
    }
  }
  ////////
  if (strTopic == "myhome/Conditioner/Power"){
     if (strPayload == "off" || strPayload == "false" || strPayload == "0"){
      SendData(off, sizeof(off)/sizeof(byte));
      return;
    }
    if (strPayload == "on" || strPayload == "true" || strPayload == "1"){
      SendData(on, sizeof(on)/sizeof(byte));
      return;
    }
    if (strPayload == "quiet"){
        data[B_POWER] = 9;
    }
  }
  ////////
  if (strTopic == "myhome/Conditioner/RAW"){
    char buf[75];
    char hexbyte[3] = {0};
    strPayload.toCharArray(buf, 75);
    int octets[sizeof(buf) / 2] ;
    for (int i=0; i < 76; i += 2){
      hexbyte[0] = buf[i] ;
      hexbyte[1] = buf[i+1] ;
      data[i/2] = (toHex(hexbyte[0]) << 4) | toHex(hexbyte[1]);
    }
    Serial.write(data, 37);
    client.publish("myhome/Conditioner/RAW", buf);
  }
  
  data[B_CMD] = 0;
  data[9] = 1;
  data[10] = 77;
  data[11] = 95;
  SendData(data, sizeof(data)/sizeof(byte));
}

void setup() {
  pinMode(LED, OUTPUT);
  Serial.begin(9600);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
}

void loop() {
  if(Serial.available() > 0){
    Serial.readBytes(data, 37);
    while(Serial.available()){
      delay(2);
      Serial.read();
    }
    if (data[36] != inCheck){
      inCheck = data[36];
      InsertData(data, 37);
    }
  }
  
  if (!client.connected()){
    reconnect();
  }
  client.loop();

  long now = millis();
  if (now - prev > 5000) {
    prev = now;
    SendData(qstn, sizeof(qstn)/sizeof(byte)); // 
  }
}


Nach dem Flashen des ESP8266 setzen wir das Modul in die Klimaanlage ein. Themen werden automatisch auf dem MQTT-Server erstellt:
Leiterplatte


Klimaanlage Bedienfeld auf einer Webseite.
Leiterplatte

Neben der Verwaltung ĂŒber eine Webseite wird die Verwaltung von Sprachbefehlen sowie ĂŒber den Telegrammtreiber fĂŒr IOBroker organisiert.
Das neue Modul kostete etwa 200 Rubel.


Der erste Teil - Smart Home, der Anfang.
Teil Zwei - BadezimmerbesucherzÀhler

Source: https://habr.com/ru/post/de395533/


All Articles