Other > Daikin Web Remote Control - WiFi & Arduino
Introduction
As part of the ongoing process of adding automation to various items around the home, I came across an interesting project for controlling a Daikin air conditioner via a network-connected Arduino. The details are here:
http://harizanov.com/2012/02/control-daikin-air-conditioner-over-the-internet/
Here in Australia, a local electronics company called Freetronics sells Arduino clones. There is a model called EtherTen, which is the equivalent of an Arduino Uno with the ethernet shield fitted.
Porting from EtherCard to WizNet. Harizanov's design is based on a Nanode (another Arduino variant). These are not as widespread as other stock standard Arduino and clones. The nanode uses an ENC28J60 network chip and requires the EtherCard library, whereas most other 'standard' Arduinos use the WizNet chip and the stock Ethernet library.
His code could not be directly compiled and uploaded to a Wiznet-based Arduino. Much of the original code had a very high dependency on the EtherCard library, and he stated that he had "no intention to move it to Wiznet". So this project was born. As such, lot of the features have been stripped out (such as Authentication, NTP and DDNS), and others re-written to work with the Wiznet Library. The JQuery interface was also removed.
The omission of JQuery and authentication were not a concern in my case, as the device will primarily be integrated with a home automation web server for centralised control. This central web server handles the security and authentication, leaving the Arduino to only do the IR control. Therefore, the Arduino would not be directly exposed to the internet, so the built-in authentication system was left out. An important feature that was carried over in the new project was the ability to read the status via JSON, and also to be able to change the air conditioner settings via a URL. The IP address is hardcoded, although it would not be too difficult to change, as long as the Arduino can be connected to a computer to re-upload the compiled code. Using DHCP causes the WizNet library to consume more memory.
Unfortunately, he did not go into much detail about the specifics of how it was assembled. For instance, the IR LED needs to be driven via a transistor, as the MCU provides a very limited output on its PWM pins. The LED needs to be driven much harder in order to obtain its maximum output. In the introduction, a transistor was briefly mentioned in the parts list, but wasn't immediately obvious that its purpose was to drive the IR LED. The only mention of the transistor drive requirement is in the comments section, where somebody asked about why their attained IR transmission range was so short.
This project
In this example, we use a Freetronics EtherTen. This is the equivalent to an Arduino Uno with the ethernet shield installed.
A DHT22 temperature/humidity sensor can be added. This is optional, and allows the room temperature to be monitored remotely. If it is used, it should be kept at least 20cm from the EtherTen, so as not to pick up the heat from the Arduino.
A prototyping shield was used. The protoshield is made by Freetronics specifically for its EtherTen product. This shield is sized to clear the ethernet jack. If you use a standard Arduino with an Ethernet shield, you will need to stack the protoshield on top. The transistor and resistors are mounted on the protoshield.
Once the project was completed, it can be connected to the home network. An existing WiFi access point elsewhere on the network allows smartphones to access and control the air conditioner.
External access is also possible, so that prior to coming home, the room temperature can be checked, and the air conditioner can be turned on if one feels it is necessary.
Network Connection
I decided to make the IR controller wireless, so I added a TP-Link WR703N pocket router, flashed with OpenWRT. I used the Relayd package to turn the device into a WiFi client bridge.
The WR703N was chosen because it was the smallest WiFi router available that could be powered by a Micro-USB cable. Both the EtherTen and the WR703N were powered by a 5v switching regulator fed with a 12v plugpack.
What's Missing
Modes such as Econo, Comfort/Sensor were not implemented in his project.
Construction details
Everything is housed in a small jiffy box. The only visible components are the IR LED at one end, and a DC power socket at the other.
Power: The DC socket is fitted to a drilled hole on one end of the jiffy box. This feeds a DC-DC step down converter. The one I used is sold by various sellers on the internet, and is designed to be a drop-in replacement for the LM7805 series three-pin linear regulators. Filter capacitors are added to the input and output stages. The 5v output from the DC converter is connected to a pair of micro USB connectors. I bought two cheap micro USB cables online, and cut the cable, leaving about 10cm on the micro USB side. The wires are stripped and soldered to the 5v output. These two micro USB pigtails are used to power the EtherTen and WR703N.
I used a spare 12v switchmode 'plug-pack' or 'wall wart'. These are commonly used by things such as external hard drives and ADSL/broadband routers.
Network connection: I made a very short 7cm ethernet patch lead to go between the WR703N and EtherTen. Straight-through is fine because the WR703N's ethernet interface is auto-crossover.
Arduino wiring: The "ProtoShield Short" was used, as it was designed by freetronics to fit the EtherTen. The transistor, resistors and LED are soldered here.
[SHIELD_DIAGRAM]
IR LED: A 5mm hole is drilled into the front side of the jiffy box. The IR LED is pushed through. In my case, the hole is small enough to hold the LED in place by friction. You may need to apply hot glue to hold it in place if your LED is smaller than the hole.
Arduino Sketch
/* An Arduino sketch to emulate IR Daikin ARC433** remote control unit Modified to work on standard WizNet Arduinos.
Based on code originally by Martin Harizanov in 2012 */
#include <Ethernet.h>
#include <SPI.h>
#include <avr/eeprom.h>
#include <IRremote.h> #include "DHT.h" #define DEBUG 1 // set to 1 to display free RAM on web page #define SERIAL 1 // set to 1 to show incoming requests on serial port #define IRSTATE_EEPROM_ADDR ((byte*) 0x100) #define CONFIG_EEPROM_ADDR ((byte*) 0x10) #define ledPin 13 // the number of the LED pin #define switchPin 5 // the number of the button pin #define interval 500 // interval at which to blink (milliseconds) #define DHTPIN 2 #define DHTTYPE DHT22 // # of bytes per command const int COMMAND_LENGTH = 27; unsigned char daikin[COMMAND_LENGTH] = { 0x11,0xDA,0x27,0xF0,0x00,0x00,0x00,0x20, //0 1 2 3 4 5 6 7 0x11,0xDA,0x27,0x00,0x00,0x41,0x1E,0x00, //8 9 10 11 12 13 14 15 0xB0,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x00,0x00,0xE3 }; //16 17 18 19 20 21 22 23 24 25 26 EthernetServer server = EthernetServer(80); byte mymac[] = { 0x65,0x5A,0x5A,0x1E,0x21,0x12 }; byte ip[4] = {192,168,0,123}; byte gw[4] = {192,168,0,1}; byte subnet[4] = {255,255,255,0};
IRsend irsend;
DHT dht(DHTPIN, DHTTYPE);
struct IRState {
byte mode;
byte temp;
byte fan;
byte aux;
byte state;
byte enabled;
byte sched;
byte hour;
byte minutes;
long lastused;
} irstate;
// Variables will change:
byte ledState = LOW; // ledState used to set the LED
long previousMillis = 0; // will store last time LED was updated
// the follow variables is a long because the time, measured in miliseconds,
// will quickly become a bigger number than can be stored in an int.
static void loadIRstate(int offset) {
for (byte i = 0; i < sizeof irstate; i++)
((byte*) &irstate)[i] = eeprom_read_byte(IRSTATE_EEPROM_ADDR + (offset*sizeof irstate) + i);
airController_setTemp(irstate.temp);
airController_setFan(irstate.fan);
airController_setMode(irstate.mode);
airController_setState(irstate.state);
}
static void saveIRstate(int offset) {
irstate.aux=airController_getAux();
irstate.temp=airConroller_getTemp();
irstate.fan= airConroller_getFan();
irstate.mode=airConroller_getMode();
irstate.state=airConroller_getState();
for (byte i = 0; i < sizeof irstate; i++)
eeprom_write_byte(IRSTATE_EEPROM_ADDR + (offset*sizeof irstate) + i, ((byte*) &irstate)[i]);
}
#if DEBUG
static int freeRam () {
extern int __heap_start, *__brkval;
int v;
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}
#endif
void setup(){
// set the digital pin as output:
pinMode(ledPin, OUTPUT);
pinMode(switchPin, INPUT); // Set the switch pin as input
#if SERIAL
Serial.begin(9600);
Serial.println("[Configuration]");
#endif
Ethernet.begin( mymac, ip, gw, subnet);
#if SERIAL
Serial.print(F("IP:"));
Serial.println(Ethernet.localIP());
Serial.print(F("Free RAM: "));
Serial.println(freeRam());
#endif
loadIRstate(0);
dht.begin();
}
void loop(){
if(0)
if(digitalRead(switchPin) == HIGH){
//send the last command if switch is pressed
sendDaikinCommand();
}
String readString;
char data[512];
byte readChar = 0;
int pos = 0;
boolean currentLineIsBlank = true;
EthernetClient client = server.available();
if (client) {
Serial.println(F("Client connected"));
while (client.connected()) {
if (client.available()) {
char c = client.read();
readString += c;
if(pos<512) data[pos] = c;
if(pos==511) data[pos] = ' ';
pos++;
// if you've gotten to the end of the line (received a newline
// character) and the line is blank, the http request has ended,
// so you can send a reply
if (c == ' ' && currentLineIsBlank) {
//Serial.println(readString);
// send a standard http response header
printHttpHeaders(client);
if (strncmp_P(data,PSTR("GET / "), 6) == 0){
printHtmlHeaders(client);
printHomePage(client);
printHttpFooter(client);
}
if (strncmp_P(data,PSTR("GET /i"), 6) == 0){
jsonPage(client);
}
else if (strncmp_P(data,PSTR("GET /r"), 6) == 0){
printHtmlHeaders(client);
rcSettingsPage(client);
printHttpFooter(client);
}
else if (strncmp_P(data,PSTR("GET /w"), 6) == 0){
//write aircon parameters to EEPROM
printHtmlHeaders(client);
saveIRsettings(client);
printHttpFooter(client);
}
else if (strncmp_P(data,PSTR("GET /s"), 6) == 0){
printHtmlHeaders(client);
char *firstSep = "?";
char *secondSep = " ";
char *tokTemp;
char *tokReturnTemp;
tokReturnTemp = strtok_r(data, firstSep,&tokTemp);
tokReturnTemp = strtok_r(NULL, firstSep,&tokTemp);
tokReturnTemp = strtok_r(NULL, secondSep,&tokReturnTemp);
receiveSettings(client, tokReturnTemp);
}
break;
}
if (c == ' ') {
// you're starting a new line
currentLineIsBlank = true;
}
else if (c != ' ') {
// you've gotten a character on the current line
currentLineIsBlank = false;
}
}
}
delay(500);
client.stop();
Serial.println(F("client disconnected"));
}
}
void printHttpHeaders(EthernetClient client){
client.println(F("HTTP/1.1 200 OK"));
client.println(F("Content-Type: text/html"));
client.println(F("Connection: close")); // the connection will be closed after completion of the response
client.println();
}
void printHtmlHeaders(EthernetClient client){
client.println(F("<!DOCTYPE HTML> <html> <title>Daikin Arduino Remote</title>"));
client.println(F("<meta name='viewport' content='width=device-width, initial-scale=1'><body>"));
}
void printHttpFooter(EthernetClient client){
client.println(F("</body></html>"));
}
void printHomePage(EthernetClient client){
client.println(F("<b>Web Daikin Control</b><br />"));
client.println(F("<a href="/i">JSON</a><br />"));
client.println(F("<a href="/r">Remote Control</a><br /><br />"));
float h = dht.readHumidity();
float t = dht.readTemperature();
if (isnan(t) || isnan(h)) {
Serial.println("Failed to read from DHT");
} else {
client.print("Humidity: ");
client.print(h);
client.print(" % ");
client.print("Temperature: ");
client.print(t);
client.println(" *C");
}
}
void jsonPage(EthernetClient client){
client.print(F("{"state":""));
client.print(airConroller_getState());
client.print(F("", "mode":""));
client.print(airConroller_getMode());
client.print(F("", "temp":""));
client.print(airConroller_getTemp());
client.print(F("", "fan":""));
client.print(airConroller_getFan());
client.print(F("", "aux":""));
client.print(airController_getAux());
client.print(F(""}"));
}
void rcSettingsPage(EthernetClient client){
String tempString;
int modeVal[] = {0,2,3,4,6};
char *modeName[] = {"Auto","Dry","Cool","Heat","Fan Only" };
int fanModeVal[] = {48, 64, 80, 96, 112, 160, 176};
char *fanModeName[] = {"1","2","3","4","5","Auto","Night"};
client.println(F("Remote Control Settings"));
client.print(F("<form action='/s' method='get'><table>"));
client.print(F("<tr><td>State:</td><td><select name=s id=s data-role='slider' >"));
client.print(F("<option value='1'"));
if(airConroller_getState()) client.print(F(" selected"));
client.print(F(">On</option><option value='0'"));
if(airConroller_getState()==0) client.print(F(" selected"));
client.print(F(">Off</option>"));
client.print(F("</select><br />"));
client.println(F("<tr><td>Mode:</td><td><select name="d">"));
for(int i=0; i<5; i++){
client.print(F("<option value='"));
client.print(modeVal[i]);
client.print("'");
if(airConroller_getMode()==modeVal[i]) client.print(F(" selected"));
client.print(">");
client.print(modeName[i]);
client.print(F("</option>"));
}
client.println(F("</select><tr><td>Temp:</td><td><select name=t id=t>"));
for(int i=14;i<32;i++){
client.print(F("<option value='"));
client.print(i);
client.print("'");
if(airConroller_getTemp()==i) client.print(F(" selected"));
client.print(">");
client.print(i);
client.print(F("</option>"));
}
client.println(F("</select><tr><td>Fan:</td><td>"));
client.print(F("<select data-native-menu='false' id=f name=f>"));
for(int i=0; i<7; i++){
client.print(F("<option value='"));
client.print(fanModeVal[i]);
client.print("'");
if(airConroller_getFan()==fanModeVal[i]) client.print(F(" selected"));
client.print(">");
client.print(fanModeName[i]);
client.print(F("</option>"));
}
client.println(F("</select><tr><td>Aux:</td><td><select data-native-menu='false' name=a id=a>"));
client.print(F("<option value='0'>Normal</option><option value='1'>Powerful</option><option value='16'>Outdoor Unit Silent Operation</option></select><br>"));
client.println(F("<tr><td></td><td><input type="submit" value='Send' /></table></form><a href="/">Back</a>"));
client.println(F("<br /><br /><form action='/w' method='get'><input type='submit' value='Save settings' /></form>"));
}
int getQuerystringKeyValue(char* queryString, char keyToGet){
//requires a string in the format a=1&b=3&d=4. Returns value of requested keyToGet
char *firstSep = "&";
char *secondSep = "=";
char *arg, *key, *val, *tokTemp1, *tokTemp2;
for(arg = strtok_r(queryString, firstSep,&tokTemp1); arg; arg=strtok_r(NULL, firstSep,&tokTemp1)){
key = strtok_r(arg, secondSep, &tokTemp2);
val = strtok_r(NULL, secondSep, &tokTemp2);
if(key[0]==keyToGet) return atoi(val);
}
}
void receiveSettings( EthernetClient client, char* queryString){
//parse header for settings
char workingQueryString[30];
strcpy(workingQueryString,queryString);
int temp = getQuerystringKeyValue(workingQueryString,'s'); //state
switch (temp) {
case 0: airController_off(); break;
case 1: airController_on(); break;
default: break;
}
strcpy(workingQueryString,queryString);
temp=getQuerystringKeyValue(workingQueryString,'t'); //temp
if(temp) {
if(temp>=14 && temp <=32) {
airController_setTemp(temp);
}
}
strcpy(workingQueryString,queryString);
temp=getQuerystringKeyValue(workingQueryString,'f'); //fan
if (temp) {
if(temp >=1 && temp <=250) {
airController_setFan(temp);
// Serial.println(temp);
}
}
strcpy(workingQueryString,queryString);
temp=getQuerystringKeyValue(workingQueryString,'d'); ///mode
if(temp >=0 && temp <=7) {
airController_setMode(temp);
}
strcpy(workingQueryString,queryString);
temp=getQuerystringKeyValue(workingQueryString,'a'); //aux
if(temp==0 || temp==1 || temp==16) airController_setAux(temp);
sendDaikinCommand();
client.print(F("Daikin command sent. <script type="text/javascript">setTimeout(function(){document.location='/r';},500);</script>"));
}
void saveIRsettings(EthernetClient client){
//save settings to EEPROM
client.print(F("Aircon parameters saved. <script type="text/javascript">setTimeout(document.location='/r',1000);</script>"));
saveIRstate(0);
}
void sendDaikinCommand(){
//digitalWrite(ledPin, LOW);
airController_checksum();
irsend.sendDaikin(daikin, 8,0);
delay(29);
irsend.sendDaikin(daikin, 19,8);
//delay(500);
//digitalWrite(ledPin, HIGH);
}
uint8_t airController_checksum(){
uint8_t sum = 0;
uint8_t i;
for(i = 0; i <= 6; i++){
sum += daikin[i];
}
daikin[7] = sum &0xFF;
sum=0;
for(i = 8; i <= 25; i++){
sum += daikin[i];
}
daikin[26] = sum &0xFF;
}
void airController_setState(uint8_t state){
daikin[13] = state;
airController_checksum();
}
void airController_on(){
//state = ON;
daikin[13] |= 0x01;
airController_checksum();
}
void airController_off(){
//state = OFF;
daikin[13] &= 0xFE;
airController_checksum();
}
void airController_setAux(uint8_t aux){
daikin[21] = aux;
airController_checksum();
}
uint8_t airController_getAux(){
return daikin[21];
}
void airController_setTemp(uint8_t temp){
daikin[14] = (temp)*2;
airController_checksum();
}
void airController_setFan(uint8_t fan){
daikin[16] = fan;
airController_checksum();
}
uint8_t airConroller_getTemp(){
return (daikin[14])/2;
}
uint8_t airConroller_getMode(){
/*
Modes: b6+b5+b4
011 = Cool
100 = Heat (temp 23)
110 = FAN (temp not shown, but 25)
000 = Fully Automatic (temp 25)
010 = DRY (temp 0xc0 = 96 degrees c)
*/
return (daikin[13])>>4;
}
void airController_setMode(uint8_t mode){
daikin[13]=mode<<4 | airConroller_getState();
airController_checksum();
}
uint8_t airConroller_getState(){
return (daikin[13])&0x01;
}
uint8_t airConroller_getFan(){
return (daikin[16]);
}
void reboot() {
// A dirty hack to jump to start of boot loader
asm volatile (" jmp 0x7C00");
}
void restartac () {
if(airConroller_getState()==1) {
airController_off();
irstate.aux=airController_getAux();
irstate.temp=airConroller_getTemp();
irstate.fan= airConroller_getFan();
irstate.mode=airConroller_getMode();
irsend.sendDaikin(daikin, 8,0);
delay(29);
irsend.sendDaikin(daikin, 19,8);
delay (10000);
airController_on();
airController_setAux(0);
airController_setTemp(irstate.temp);
airController_setFan(irstate.fan);
airController_setMode(irstate.mode);
irsend.sendDaikin(daikin, 8,0);
delay(29);
irsend.sendDaikin(daikin, 19,8);
}
}
Screenshots
Jonathan Oxer, Sat, 02 Jan 2016 07:34 am: Reply
hello sir, Tue, 06 Aug 2019 05:26 pm: Reply