DIY IOT: Wifi for the Air Conditioner

Earlier this year, I’ve bought a used air conditioner. Since then, I’ve wanted to automate it, control it from far away etc.

Now, this is possible. (I assume this works for all AC Units using the ZH/LW-04 Remote)

The remote of this unit differs from normal remotes.
Instead of sending a keycode, it sends a whole state. This is necessary because the remote itself has a display and the state should stay consistent.

Fig A: The remote
Fig A: The remote

This is important, because it makes connecting the AC Unit to the internet a lot easier.

I’ve connected an arduino to the signal wire of the AC Units IR receiver to get some sample data.

Fig B: Dank wiring
Fig B: Dank wiring

Moving on, I’ve spend the night trying to understand the protocol.

Fig C: Ugh.
Fig C: Ugh.

See this (outdated) gist for more info: https://gist.github.com/Hypfer/38b3b276e1a0fea92c30

Thanks to #arduino on freenode, I was able to understand how the checksum is calculated.

Using this knowledge, I’ve created this cactus micro (Arduino clone with esp8266 attached) sketch which provides a way to control the AC Unit from remote.
int p = 8; //IR PIN
byte Header = B00010001;
byte Padding = B00000000;
byte checksum;
byte PON_NOHOLD = B00100100;
byte PON_HOLD = B00100101;
byte POFF_NOHOLD = B00100000;
byte POFF_HOLD = B00100001;
byte COOL = B00000011;
byte DRY = B00000010;
byte HEAT = B00000001;
byte T_31 = B00000000;
byte T_30 = B00000001;
byte T_29 = B00000010;
byte T_28 = B00000011;
byte T_27 = B00000100;
byte T_26 = B00000101;
byte T_25 = B00000110;
byte T_24 = B00000111;
byte T_23 = B00001000;
byte T_22 = B00001001;
byte T_21 = B00001010;
byte T_20 = B00001011;
byte T_19 = B00001100;
byte T_18 = B00001101;
byte T_17 = B00001110;
byte FAN3_NOSWING = B00000100;
byte FAN2_NOSWING = B00000110;
byte FAN1_NOSWING = B00000010;
byte FAN3_SWING = B00001100;
byte FAN2_SWING = B00001110;
byte FAN1_SWING = B00001010;
#include <SoftwareSerial.h>
// ESP8266 rx, tx
SoftwareSerial esp8266(11, 12);
void setup() {
delay(8000);
Serial.begin(9600);
Serial.println("Hallo, Marcel Davis von 1&1");
esp8266.begin(9600);
// enable wifi
pinMode(13, OUTPUT);
digitalWrite(13, HIGH);
delay(1000);
esp8266.println("AT+CIPMUX=1");
delay(500);
esp8266.println("AT+CIPSERVER=1,80");
delay(500);
}
String reqBuf = "";
String conID = "";
String req = "";
String setResponse = "";
boolean fail = false;
byte P_IN;
byte M_IN;
byte T_IN;
byte F_IN;
String P_INSTR;
String M_INSTR;
String T_INSTR;
String F_INSTR;
String paramBuf = "";
void loop()
{
while (esp8266.available() > 0) {
char ch = esp8266.read();
reqBuf += ch;
Serial.print(ch);
if (ch == '\n') {
if (reqBuf.startsWith("+IPD")) {
//GET THE CONNECTION ID
conID = reqBuf.substring(reqBuf.indexOf(",") + 1);
conID = conID.substring(0, conID.indexOf(","));
Serial.println(freeRam());
//GET THE REQUEST
req = reqBuf.substring(reqBuf.indexOf(":") + 1);
Serial.println(req);
if (req.indexOf("favicon") != -1) {
esp8266.println("AT+CIPCLOSE=" + conID);
reqBuf = "";
} else if (req.indexOf("/set?state") != -1) {
Serial.println("ITS A STATE SET REQUEST");
P_INSTR = "";
M_INSTR = "";
T_INSTR = "";
F_INSTR = "";
paramBuf = req.substring(req.indexOf("="));
Serial.println(paramBuf);
P_INSTR = paramBuf.substring(1, paramBuf.indexOf(","));
paramBuf = paramBuf.substring(paramBuf.indexOf(",") + 1);
M_INSTR = paramBuf.substring(0, paramBuf.indexOf(","));
paramBuf = paramBuf.substring(paramBuf.indexOf(",") + 1);
T_INSTR = paramBuf.substring(0, paramBuf.indexOf(","));
paramBuf = paramBuf.substring(paramBuf.indexOf(",") + 1);
F_INSTR = paramBuf.substring(0, paramBuf.indexOf(" "));
if (P_INSTR && M_INSTR && T_INSTR && F_INSTR) {
if (P_INSTR == "PON_NOHOLD") {
P_IN = PON_NOHOLD;
} else if (P_INSTR == "PON_HOLD") {
P_IN = PON_HOLD;
} else if (P_INSTR == "POFF_NOHOLD") {
P_IN = POFF_NOHOLD;
} else if (P_INSTR == "POFF_HOLD") {
P_IN = POFF_HOLD;
} else {
setResponse = "Set " + P_INSTR + " failed";
esp8266.println("AT+CIPSEND=" + conID + "," + setResponse.length());
delay(300);
esp8266.println(setResponse);
delay(300);
esp8266.println("AT+CIPCLOSE=" + conID);
reqBuf = "";
fail = true;
}
if (M_INSTR == "COOL") {
M_IN = COOL;
} else if (M_INSTR == "DRY") {
M_IN = DRY;
} else if (M_INSTR == "HEAT") {
M_IN = HEAT;
} else {
setResponse = "Set " + M_INSTR + " failed";
esp8266.println("AT+CIPSEND=" + conID + "," + setResponse.length());
delay(300);
esp8266.println(setResponse);
delay(300);
esp8266.println("AT+CIPCLOSE=" + conID);
reqBuf = "";
fail = true;
}
if (T_INSTR == "T_31") {
T_IN = T_31;
} else if (T_INSTR == "T_30") {
T_IN = T_30;
} else if (T_INSTR == "T_29") {
T_IN = T_29;
} else if (T_INSTR == "T_28") {
T_IN = T_28;
} else if (T_INSTR == "T_27") {
T_IN = T_27;
} else if (T_INSTR == "T_26") {
T_IN = T_26;
} else if (T_INSTR == "T_25") {
T_IN = T_25;
} else if (T_INSTR == "T_24") {
T_IN = T_24;
} else if (T_INSTR == "T_23") {
T_IN = T_23;
} else if (T_INSTR == "T_22") {
T_IN = T_22;
} else if (T_INSTR == "T_21") {
T_IN = T_21;
} else if (T_INSTR == "T_20") {
T_IN = T_20;
} else if (T_INSTR == "T_19") {
T_IN = T_19;
} else if (T_INSTR == "T_18") {
T_IN = T_18;
} else if (T_INSTR == "T_17") {
T_IN = T_17;
} else {
setResponse = "Set " + T_INSTR + " failed";
esp8266.println("AT+CIPSEND=" + conID + "," + setResponse.length());
delay(300);
esp8266.println(setResponse);
delay(300);
esp8266.println("AT+CIPCLOSE=" + conID);
reqBuf = "";
fail = true;
}
if ( F_INSTR == "FAN3_NOSWING") {
F_IN = FAN3_NOSWING;
} else if ( F_INSTR == "FAN2_NOSWING") {
F_IN = FAN2_NOSWING;
} else if ( F_INSTR == "FAN1_NOSWING") {
F_IN = FAN1_NOSWING;
} else if ( F_INSTR == "FAN3_SWING") {
F_IN = FAN3_SWING;
} else if ( F_INSTR == "FAN2_SWING") {
F_IN = FAN2_SWING;
} else if ( F_INSTR == "FAN1_SWING") {
F_IN = FAN1_SWING;
} else {
setResponse = "Set " + F_INSTR + " failed";
esp8266.println("AT+CIPSEND=" + conID + "," + setResponse.length());
delay(300);
esp8266.println(setResponse);
delay(300);
esp8266.println("AT+CIPCLOSE=" + conID);
reqBuf = "";
fail = true;
}
if (!fail) {
executeCommand(P_IN, M_IN, T_IN, F_IN);
setResponse = "Set " + P_INSTR + "," + M_INSTR + "," + T_INSTR + "," + F_INSTR;
esp8266.println("AT+CIPSEND=" + conID + "," + setResponse.length());
delay(300);
esp8266.println(setResponse);
delay(300);
esp8266.println("AT+CIPCLOSE=" + conID);
reqBuf = "";
}
fail = false;
}
} else {
esp8266.println("AT+CIPSEND=" + conID + "," + 2);
delay(300);
esp8266.println("OK");
delay(300);
esp8266.println("AT+CIPCLOSE=" + conID);
}
}
reqBuf = "";
}
}
}
void executeCommand(byte P, byte M, byte T, byte F) {
checksum = B00000000;
checksum = checksum + Header + Header + Header + Header + Header;
checksum = checksum + P + M + T + F;
pinMode(p, OUTPUT);
digitalWrite(p, HIGH);
//Startsequenz
digitalWrite(p, LOW);
delayMicroseconds(3440);
digitalWrite(p, HIGH);
delayMicroseconds(1700);
digitalWrite(p, LOW);
delayMicroseconds(610);
//5 Header
sendByte(Header);
sendByte(Header);
sendByte(Header);
sendByte(Header);
sendByte(Header);
sendByte(P);
sendByte(M);
sendByte(T);
sendByte(F);
//4 Padding
sendByte(Padding);
sendByte(Padding);
sendByte(Padding);
sendByte(Padding);
sendByte(checksum);
pinMode(p, INPUT);
}
void sendByte(byte toSend) {
for (int i = 0; i < 8; i++) { if (toSend & 1) { digitalWrite(p, HIGH); delayMicroseconds(1160); } else { digitalWrite(p, HIGH); delayMicroseconds(240); } digitalWrite(p, LOW); delayMicroseconds(610); toSend >>= 1;
}
}
int freeRam ()
{
extern int __heap_start, *__brkval;
int v;
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}

Usage:
http://arduino_ip/set?state=PON_NOHOLD,COOL,T_22,FAN3_SWING

For all possible state values take a look at the code 😉

I suggest running the auto code format function of the arduino IDE on this. Apparently wordpress hates well formatted code.
Also, the ESP8266 needs to be programmed for your local wifi network. Google will help you out.

So now I can control my cheap AC Unit with any internet enabled device with a browser. Pretty nice for a 35€ device 🙂

Fig D: The Cactus micro
Fig D: The Cactus micro

Thanks to everyone who helped me with this.

Todo: Some nifty webinterface

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.