Kontrol pendingin udara Haier berdasarkan ESP8266

Sebagai kelanjutan dari artikel tentang membangun "rumah pintar" ...



AC seri Haier Lightera memiliki papan modul WiFi untuk mengontrolnya melalui aplikasi pada telepon yang bekerja melalui layanan cloud Cina yang tidak dikenal. Untuk model yang lebih lama, modul ini merupakan opsi dan dibeli secara terpisah, yang terhubung ke papan kontrol di unit dalam-ruang. Pada model-model baru, konektor berada di bawah trim dekoratif dan dalam seri Lightera modul sudah terpasang. Dengan demikian, unit ini berlaku untuk banyak AC merek Haier.

Untuk mengontrol AC melalui modul WiFi asli, Anda perlu mengunduh aplikasi di smartphone / tablet Anda, mendaftar di dalamnya, sambungkan smartphone / tablet Anda ke router melalui Wi-Fi. Nyalakan AC dalam mode pendinginan 30 derajat dengan kecepatan kipas minimum, pastikan bahwa jaringan Haier-uAC telah muncul, dan mulai program untuk mencari perangkat dan jaringan. Program ini menemukan AC Anda dan jaringan yang tersedia. Anda mendaftarkan jaringan Anda dengan memilihnya dari daftar dan melanjutkan untuk mendaftarkan model peralatan Anda (AC). Di jaringan rumah saya, server DHCP dinonaktifkan pada router dan untuk terhubung ke jaringan WiFi saya di perangkat yang terhubung, Anda perlu membuat koneksi baru dan mendaftar di sana selain SSID (karena tersembunyi) dan kata sandi juga alamat IP statis.Karena alasan inilah saya tidak dapat menambahkan AC saya ke aplikasi, karena ketika menambahkan AC dia meminta saya untuk memilih hanya titik akses WiFi dan kata sandi. Aplikasi mengirimkan data yang dimasukkan ke modul WiFi AC dan, menggunakan data ini, ia mencoba untuk terhubung ke titik akses Anda, berharap itu akan diberikan alamat IP, tetapi router saya mematahkan semua harapannya.

Penampilan modul WiFi asli.

Penampilan modul WiFi Haier asli


Untuk pengujian, saya masih menghubungkannya melalui router lain. Manajemen melalui aplikasi berfungsi, tetapi tidak ada cara untuk mengontrol AC tanpa aplikasi, di mana layanan cloud tidak jelas, tidak ada akun pribadi. Akibatnya, Haier, seperti banyak produsen peralatan, menciptakan perangkat keras mereka sendiri dengan aplikasi mereka tanpa kemungkinan integrasi dengan sistem otomasi lainnya (tanpa modul dan peralatan khusus). Pada akhirnya, saya memutuskan untuk membuat modul WiFi saya dengan semua karakteristik karakter yang terkenal.

Dasarnya diambil ESP8266 12F, yang akan bekerja langsung dengan server saya menggunakan protokol MQTT. IOBroker diinstal pada server , yang juga bertindak sebagai server MQTT.

Tetap memahami protokol pertukaran dengan AC itu sendiri. Setelah mempelajari modul asli dan sirkuit unit kontrol dari model sebelumnya, menjadi jelas bahwa modul WiFi berkomunikasi dengan AC melalui UART reguler dengan level TTL. Setelah menghubungkan adaptor UART / USB secara paralel dengan jalur RX / TX dan mengendalikan AC dari aplikasi dan remote control, saya membaca semua data.

Foto motherboard motherboard.

Haier Native WiFi Module Board


Papan menunjukkan konverter 3,3 V DC / DC dan konverter level logika. Layar tidak mulai membidik, apa yang tidak diketahui di bawahnya.

Haier Native WiFi Module Board


Ini adalah pengalaman pertama saya dengan pembalikan protokol, tetapi menurut saya protokol tersebut ternyata sangat sederhana.
Nilai tukar adalah 9600/8-N-1. Modul WiFi mengirimkan permintaan setiap 13 detik (13 byte), di mana AC mengeluarkan paket (37 byte) dengan semua data. Di bawah spoiler, daftar byte yang ternyata terurai.

Protokol pertukaran
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 — . .

Tim pendek
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

Misalnya, untuk mengatur suhu, perlu untuk mengirim:
FF FF 22 00 00 00 00 00 01 01 4D 5F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04 04 D8 - set ke 20 derajat.

Kami menggambar diagram sirkuit. Rangkaian ini ditenagai oleh 5 volt dari AC, dan karena tegangan suplai ESP8266 adalah 3,3 volt, maka ada stabilisator LM1117 (AMS1117) untuk tegangan output yang sesuai. Konverter level logika dirakit pada elemen R1, Q1, R3 dan R2, R3 karena RXD TXD dari modul ESP8266 tidak toleran terhadap 5 V. Untuk pemrograman ESP, kontak U2 U3 harus ditutup bersama.
Diagram sirkuit.
Skema


Kami membiakkan papan sirkuit cetak. Tata letak papan dibuat untuk pemasangan di perumahan dari modul WiFi asli.
Papan sirkuit

Papan sirkuit

Pada foto di bawah ini, papan tes.

Kode ini ditulis dalam lingkungan Arduino. Versi saat ini tersedia di GitHub .
kode
#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)); // 
  }
}


Setelah menginstal ESP8266, kami meletakkan modul di AC. Topik secara otomatis dibuat di server MQTT:
Papan sirkuit


Panel kontrol pendingin udara pada halaman web.
Papan sirkuit

Selain mengelola dari halaman web, manajemen perintah suara diatur, serta melalui driver Telegram untuk IOBroker.
Dengan biaya, biaya modul baru sekitar 200 rubel.


Bagian pertama - Rumah pintar, awalnya.
Bagian Dua - Konter Pengunjung Kamar Mandi

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


All Articles