From d0d8608716e396c24efd84006d9422629c66ea84 Mon Sep 17 00:00:00 2001 From: ToniA Date: Wed, 4 Dec 2013 15:03:17 +0200 Subject: [PATCH 01/94] Support long airconditioner codes, tested with Panasonic CKP & DKE and Fujitsu AWYZ remotes --- rawirdecode.pde => rawirdecode.ino | 35 ++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 12 deletions(-) rename rawirdecode.pde => rawirdecode.ino (77%) mode change 100755 => 100644 diff --git a/rawirdecode.pde b/rawirdecode.ino old mode 100755 new mode 100644 similarity index 77% rename from rawirdecode.pde rename to rawirdecode.ino index 3175317..d15df4b --- a/rawirdecode.pde +++ b/rawirdecode.ino @@ -28,12 +28,12 @@ #define RESOLUTION 20 // we will store up to 100 pulse pairs (this is -a lot-) -uint16_t pulses[100][2]; // pair is high and low pulse -uint8_t currentpulse = 0; // index for pulses we're storing +uint16_t pulses[400][2]; // pair is high and low pulse +uint16_t currentpulse = 0; // index for pulses we're storing void setup(void) { Serial.begin(9600); - Serial.println("Ready to decode IR!"); + Serial.println(F("Ready to decode IR!")); } void loop(void) { @@ -79,25 +79,36 @@ void loop(void) { } void printpulses(void) { - Serial.println("\n\r\n\rReceived: \n\rOFF \tON"); + Serial.print(F("Free RAM: ")); + Serial.println(freeRam()); + Serial.println(F("\n\r\n\rReceived: \n\rOFF \tON")); + Serial.print(F("Number of pulses: ")); + Serial.println(currentpulse); + for (uint8_t i = 0; i < currentpulse; i++) { Serial.print(pulses[i][0] * RESOLUTION, DEC); - Serial.print(" usec, "); + Serial.print(" usec,\t"); Serial.print(pulses[i][1] * RESOLUTION, DEC); Serial.println(" usec"); } // print it in a 'array' format - Serial.println("int IRsignal[] = {"); - Serial.println("// ON, OFF (in 10's of microseconds)"); + Serial.println(F("int IRsignal[] = {")); + Serial.println(F("// ON, OFF (in 10's of microseconds)")); for (uint8_t i = 0; i < currentpulse-1; i++) { - Serial.print("\t"); // tab + Serial.print(F("\t")); // tab Serial.print(pulses[i][1] * RESOLUTION / 10, DEC); - Serial.print(", "); + Serial.print(F(", ")); Serial.print(pulses[i+1][0] * RESOLUTION / 10, DEC); - Serial.println(","); + Serial.println(F(",")); } - Serial.print("\t"); // tab + Serial.print(F("\t")); // tab Serial.print(pulses[currentpulse-1][1] * RESOLUTION / 10, DEC); - Serial.print(", 0};"); + Serial.print(F(", 0};")); +} + +int freeRam () { + extern int __heap_start, *__brkval; + int v; + return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); } From 427cd19fff5159191a67ad2c307615b0bf55ae1b Mon Sep 17 00:00:00 2001 From: ToniA Date: Wed, 4 Dec 2013 18:31:03 +0200 Subject: [PATCH 02/94] Let the user enter a name for each sample, and output the data in JSON format --- rawirdecode.ino | 159 ++++++++++++++++++++++++++++-------------------- 1 file changed, 93 insertions(+), 66 deletions(-) diff --git a/rawirdecode.ino b/rawirdecode.ino index d15df4b..014a48e 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -1,13 +1,13 @@ /* Raw IR decoder sketch! - - This sketch/program uses the Arduno and a PNA4602 to + + This sketch/program uses the Arduno and a PNA4602 to decode IR received. This can be used to make a IR receiver (by looking for a particular code) or transmitter (by pulsing an IR LED at ~38KHz for the - durations detected - + durations detected + Code is public domain, check out www.ladyada.net and adafruit.com - for more tutorials! + for more tutorials! */ // We need to use the 'raw' pin reading methods @@ -25,86 +25,113 @@ // what our timing resolution should be, larger is better // as its more 'precise' - but too large and you wont get // accurate timing -#define RESOLUTION 20 +#define RESOLUTION 20 // we will store up to 100 pulse pairs (this is -a lot-) -uint16_t pulses[400][2]; // pair is high and low pulse +uint16_t pulses[400][2]; // pair is high and low pulse uint16_t currentpulse = 0; // index for pulses we're storing +boolean nodata = true; // No name for this sample yet +char samplename[50]; // Holds the name of the sample + void setup(void) { Serial.begin(9600); + delay(1000); Serial.println(F("Ready to decode IR!")); + Serial.println(F("Enter the name of each sample and press 'Send' before sending an IR signal. The data is in JSON format\n\n")); + + Serial.println(F("")); + Serial.println(F("{")); + } void loop(void) { + char incoming = 0; + char sampleposition = 0; + + memset(samplename, 0, 50); // Wipe the name of the sample + + // Receive the name of the sample + while (nodata) { + while (nodata || Serial.available() ) { + if (Serial.available()) + { + //read the incoming character + char incoming = Serial.read(); + samplename[sampleposition++] = incoming; + nodata=false; + delay(50); + } + } + } + + // Print out the first part of the JSON array row + Serial.print(F(" \"")); + Serial.print(samplename); + Serial.print(F("\": [ ")); + Serial.flush(); + + currentpulse=0; + receivepulses(); + Serial.print("pulses: "); + Serial.println(currentpulse); + printpulses(); +} + +void receivepulses(void) { uint16_t highpulse, lowpulse; // temporary storage timing - highpulse = lowpulse = 0; // start out with no pulse length - - -// while (digitalRead(IRpin)) { // this is too slow! + + while (true) + { + highpulse = lowpulse = 0; // start out with no pulse length + + // while (digitalRead(IRpin)) { // this is too slow! while (IRpin_PIN & (1 << IRpin)) { - // pin is still HIGH - - // count off another few microseconds - highpulse++; - delayMicroseconds(RESOLUTION); - - // If the pulse is too long, we 'timed out' - either nothing - // was received or the code is finished, so print what - // we've grabbed so far, and then reset - if ((highpulse >= MAXPULSE) && (currentpulse != 0)) { - printpulses(); - currentpulse=0; - return; - } - } - // we didn't time out so lets stash the reading - pulses[currentpulse][0] = highpulse; - - // same as above - while (! (IRpin_PIN & _BV(IRpin))) { - // pin is still LOW - lowpulse++; - delayMicroseconds(RESOLUTION); - if ((lowpulse >= MAXPULSE) && (currentpulse != 0)) { - printpulses(); - currentpulse=0; - return; - } - } - pulses[currentpulse][1] = lowpulse; + // pin is still HIGH + + // count off another few microseconds + highpulse++; + delayMicroseconds(RESOLUTION); - // we read one high-low pulse successfully, continue! - currentpulse++; + // If the pulse is too long, we 'timed out' - either nothing + // was received or the code is finished, so print what + // we've grabbed so far, and then reset + if ((highpulse >= MAXPULSE) && (currentpulse != 0)) { + return; + } + } + // we didn't time out so lets stash the reading + pulses[currentpulse][0] = highpulse; + + // same as above + while (! (IRpin_PIN & _BV(IRpin))) { + // pin is still LOW + lowpulse++; + delayMicroseconds(RESOLUTION); + if ((lowpulse >= MAXPULSE) && (currentpulse != 0)) { + return; + } + } + pulses[currentpulse][1] = lowpulse; + + // we read one high-low pulse successfully, continue! + currentpulse++; + } } void printpulses(void) { - Serial.print(F("Free RAM: ")); - Serial.println(freeRam()); - Serial.println(F("\n\r\n\rReceived: \n\rOFF \tON")); - Serial.print(F("Number of pulses: ")); - Serial.println(currentpulse); - - for (uint8_t i = 0; i < currentpulse; i++) { - Serial.print(pulses[i][0] * RESOLUTION, DEC); - Serial.print(" usec,\t"); - Serial.print(pulses[i][1] * RESOLUTION, DEC); - Serial.println(" usec"); - } - - // print it in a 'array' format - Serial.println(F("int IRsignal[] = {")); - Serial.println(F("// ON, OFF (in 10's of microseconds)")); + // print it in JSON format + for (uint8_t i = 0; i < currentpulse-1; i++) { - Serial.print(F("\t")); // tab - Serial.print(pulses[i][1] * RESOLUTION / 10, DEC); + Serial.print(pulses[i][1] * RESOLUTION, DEC); + Serial.print(F(", ")); + Serial.print(pulses[i+1][0] * RESOLUTION, DEC); Serial.print(F(", ")); - Serial.print(pulses[i+1][0] * RESOLUTION / 10, DEC); - Serial.println(F(",")); } - Serial.print(F("\t")); // tab - Serial.print(pulses[currentpulse-1][1] * RESOLUTION / 10, DEC); - Serial.print(F(", 0};")); + Serial.print(pulses[currentpulse-1][1] * RESOLUTION, DEC); + Serial.println(F(", 0 ],")); + + nodata = true; } int freeRam () { From 6df9daa8c4c31daed132b7aaed1474e2e001c51d Mon Sep 17 00:00:00 2001 From: ToniA Date: Wed, 4 Dec 2013 20:56:43 +0200 Subject: [PATCH 03/94] Do not print out the number of the pulses into the JSON. I wish Arduino IDE would save automatically on compile... --- rawirdecode.ino | 2 -- 1 file changed, 2 deletions(-) diff --git a/rawirdecode.ino b/rawirdecode.ino index 014a48e..0cac062 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -73,8 +73,6 @@ void loop(void) { currentpulse=0; receivepulses(); - Serial.print("pulses: "); - Serial.println(currentpulse); printpulses(); } From 5c4adf7190cfd62561911a4ec092dab178445ed8 Mon Sep 17 00:00:00 2001 From: ToniA Date: Sun, 8 Dec 2013 13:09:03 +0200 Subject: [PATCH 04/94] Adapted to be use on decoding air conditioner IR signals, for example for Panasonic, Mitsubishi and Fujitsu --- rawirdecode.ino | 220 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 159 insertions(+), 61 deletions(-) diff --git a/rawirdecode.ino b/rawirdecode.ino index 014a48e..113b6af 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -25,68 +25,103 @@ // what our timing resolution should be, larger is better // as its more 'precise' - but too large and you wont get // accurate timing -#define RESOLUTION 20 - -// we will store up to 100 pulse pairs (this is -a lot-) -uint16_t pulses[400][2]; // pair is high and low pulse +uint16_t RESOLUTION=20; + + +// The thresholds for different symbols +// These should work with Panasonic (from DKE on), Fujitsu and Mitsubishi +// Anyway, adjust these to get reliable readings + +#define MARK_THRESHOLD_BIT_HEADER 2000 // Value between BIT MARK and HEADER MARK +#define SPACE_THRESHOLD_ZERO_ONE 800 // Value between ZERO SPACE and ONE SPACE +#define SPACE_THRESHOLD_ONE_HEADER 1400 // Value between ONE SPACE and HEADER SPACE +#define SPACE_THRESHOLD_HEADER_PAUSE 8000 // Value between HEADER SPACE and PAUSE SPACE (Panasonic only) + +// This set works on the Panasonic CKP +/* +#define MARK_THRESHOLD_BIT_HEADER 2000 // Value between BIT MARK and HEADER MARK +#define SPACE_THRESHOLD_ZERO_ONE 1800 // Value between ZERO SPACE and ONE SPACE +#define SPACE_THRESHOLD_ONE_HEADER 3200 // Value between ONE SPACE and HEADER SPACE +#define SPACE_THRESHOLD_HEADER_PAUSE 8000 // Value between HEADER SPACE and PAUSE SPACE (Panasonic only) +*/ + +/* +Panasonic CKP timings: +PAUSE SPACE: 13520 +HEADER MARK: 3394 +HEADER SPACE: 3326 +BIT MARK: 812 +ZERO SPACE: 751 +ONE SPACE: 2449 + +Panasonic DKE, JNE and NKE timings: +PAUSE SPACE: 9700 +HEADER MARK: 3439 +HEADER SPACE: 1599 +BIT MARK: 380 +ZERO SPACE: 319 +ONE SPACE: 1197 + +Fujitsu Nocria timings: +HEADER MARK 3327 +HEADER SPACE 1519 +BIT MARK 364 +ZERO SPACE 277 +ONE SPACE 1104 + +Mitsubishi FD-25 timings: +HEADER MARK: 3450 +HEADER SPACE: 1700 +BIT MARK: 500 +ZERO SPACE: 350 +ONE SPACE: 1250 +*/ + + +uint32_t mark_header_avg = 0; +uint16_t mark_header_cnt = 0; +uint32_t mark_bit_avg = 0; +uint16_t mark_bit_cnt = 0; +uint32_t space_zero_avg = 0; +uint16_t space_zero_cnt = 0; +uint32_t space_one_avg = 0; +uint16_t space_one_cnt = 0; +uint32_t space_header_avg = 0; +uint16_t space_header_cnt = 0; +uint32_t space_pause_avg = 0; +uint16_t space_pause_cnt = 0; + +// we will store up to 500 symbols +char symbols[500]; // decoded symbols uint16_t currentpulse = 0; // index for pulses we're storing -boolean nodata = true; // No name for this sample yet -char samplename[50]; // Holds the name of the sample - void setup(void) { Serial.begin(9600); delay(1000); - Serial.println(F("Ready to decode IR!")); - Serial.println(F("Enter the name of each sample and press 'Send' before sending an IR signal. The data is in JSON format\n\n")); - - Serial.println(F("")); - Serial.println(F("{")); - + Serial.println(F("Ready to decode IR!\n\n")); } void loop(void) { char incoming = 0; - char sampleposition = 0; - - memset(samplename, 0, 50); // Wipe the name of the sample - - // Receive the name of the sample - while (nodata) { - while (nodata || Serial.available() ) { - if (Serial.available()) - { - //read the incoming character - char incoming = Serial.read(); - samplename[sampleposition++] = incoming; - nodata=false; - delay(50); - } - } - } - // Print out the first part of the JSON array row - Serial.print(F(" \"")); - Serial.print(samplename); - Serial.print(F("\": [ ")); - Serial.flush(); + memset(symbols, 0, sizeof(symbols)); // Wipe the symbols + + // Only Panasonic seems to use the pause + space_pause_avg = 0; + space_pause_cnt = 0; currentpulse=0; receivepulses(); - Serial.print("pulses: "); - Serial.println(currentpulse); printpulses(); } void receivepulses(void) { uint16_t highpulse, lowpulse; // temporary storage timing - while (true) + while (currentpulse < sizeof(symbols)) { - highpulse = lowpulse = 0; // start out with no pulse length - - // while (digitalRead(IRpin)) { // this is too slow! - while (IRpin_PIN & (1 << IRpin)) { + highpulse = 0; + while (IRpin_PIN & (1 << IRpin)) { // pin is still HIGH // count off another few microseconds @@ -100,10 +135,32 @@ void receivepulses(void) { return; } } - // we didn't time out so lets stash the reading - pulses[currentpulse][0] = highpulse; + + highpulse = highpulse * RESOLUTION; + + if (currentpulse > 0) + { + // this is a SPACE + if ( highpulse > SPACE_THRESHOLD_HEADER_PAUSE ) { + symbols[currentpulse] = 'W'; + // Cumulative moving average, see http://en.wikipedia.org/wiki/Moving_average#Cumulative_moving_average + space_pause_avg = (highpulse + space_pause_cnt * space_pause_avg) / ++space_pause_cnt; + } else if ( highpulse > SPACE_THRESHOLD_ONE_HEADER ) { + symbols[currentpulse] = 'h'; + // Cumulative moving average, see http://en.wikipedia.org/wiki/Moving_average#Cumulative_moving_average + space_header_avg = (highpulse + space_header_cnt * space_header_avg) / ++space_header_cnt; + } else if ( highpulse > SPACE_THRESHOLD_ZERO_ONE ) { + symbols[currentpulse] = '1'; + space_one_avg = (highpulse + space_one_cnt * space_one_avg) / ++space_one_cnt; + } else { + symbols[currentpulse] = '0'; + space_zero_avg = (highpulse + space_zero_cnt * space_zero_avg) / ++space_zero_cnt; + } + } + currentpulse++; // same as above + lowpulse = 0; while (! (IRpin_PIN & _BV(IRpin))) { // pin is still LOW lowpulse++; @@ -112,30 +169,71 @@ void receivepulses(void) { return; } } - pulses[currentpulse][1] = lowpulse; + + // this is a MARK + + lowpulse = lowpulse * RESOLUTION; + + if ( lowpulse > MARK_THRESHOLD_BIT_HEADER ) { + symbols[currentpulse] = 'H'; + currentpulse++; + mark_header_avg = (lowpulse + mark_header_cnt * mark_header_avg) / ++mark_header_cnt; + } else { + mark_bit_avg = (lowpulse + mark_bit_cnt * mark_bit_avg) / ++mark_bit_cnt; + } // we read one high-low pulse successfully, continue! - currentpulse++; } } void printpulses(void) { - // print it in JSON format - for (uint8_t i = 0; i < currentpulse-1; i++) { - Serial.print(pulses[i][1] * RESOLUTION, DEC); - Serial.print(F(", ")); - Serial.print(pulses[i+1][0] * RESOLUTION, DEC); - Serial.print(F(", ")); - } - Serial.print(pulses[currentpulse-1][1] * RESOLUTION, DEC); - Serial.println(F(", 0 ],")); + int bitCount = 0; + byte currentByte = 0; - nodata = true; -} + Serial.print("Number of symbols: "); + Serial.println(currentpulse); + + Serial.println("Symbols:"); + Serial.println(symbols+1); + + Serial.println("Timings (in us): "); + Serial.print("PAUSE SPACE: "); + Serial.println(space_pause_avg); + Serial.print("HEADER MARK: "); + Serial.println(mark_header_avg); + Serial.print("HEADER SPACE: "); + Serial.println(space_header_avg); + Serial.print("BIT MARK: "); + Serial.println(mark_bit_avg); + Serial.print("ZERO SPACE: "); + Serial.println(space_zero_avg); + Serial.print("ONE SPACE: "); + Serial.println(space_one_avg); + + + // Decode the string of symbols to HEX digits + for (int i = 0; i < currentpulse; i++) + { + if (symbols[i] == '0' || symbols[i] == '1') + { + currentByte >>= 1; + bitCount++; + + if (symbols[i] == '1') { + currentByte |= 0x80; + } + + if (bitCount == 8) { + if (currentByte < 0x10) { + Serial.print("0"); + } + Serial.print(currentByte,HEX); + Serial.print(","); + bitCount = 0; + } + } + } -int freeRam () { - extern int __heap_start, *__brkval; - int v; - return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); + Serial.println(); } From 5ced7e7b69e8e02aa22055e58c61ade607333fa9 Mon Sep 17 00:00:00 2001 From: ToniA Date: Sun, 8 Dec 2013 18:26:11 +0200 Subject: [PATCH 05/94] Basic decoding for Mitsubishi MSZ FD-25 IR protocol --- rawirdecode.ino | 155 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 145 insertions(+), 10 deletions(-) diff --git a/rawirdecode.ino b/rawirdecode.ino index 113b6af..de681a8 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -190,6 +190,8 @@ void printpulses(void) { int bitCount = 0; byte currentByte = 0; + byte byteCount = 0; + byte bytes[32]; Serial.print("Number of symbols: "); Serial.println(currentpulse); @@ -212,11 +214,9 @@ void printpulses(void) { Serial.println(space_one_avg); - // Decode the string of symbols to HEX digits - for (int i = 0; i < currentpulse; i++) - { - if (symbols[i] == '0' || symbols[i] == '1') - { + // Decode the string of bits to a byte array + for (int i = 0; i < currentpulse; i++) { + if (symbols[i] == '0' || symbols[i] == '1') { currentByte >>= 1; bitCount++; @@ -225,15 +225,150 @@ void printpulses(void) { } if (bitCount == 8) { - if (currentByte < 0x10) { - Serial.print("0"); - } - Serial.print(currentByte,HEX); - Serial.print(","); + bytes[byteCount++] = currentByte; bitCount = 0; } } } + // Print the byte array + for (int i = 0; i < byteCount; i++) { + if (bytes[i] < 0x10) { + Serial.print("0"); + } + Serial.print(bytes[i],HEX); + if ( i < byteCount - 1 ) { + Serial.print(","); + } + } Serial.println(); + + // If this looks like a Mitsubishi FD-25 code... + if ( byteCount == 36 && bytes[0] == 0x23 && (memcmp(bytes, bytes+18, 17) == 0)) { + Serial.println("Looks like a Mitsubishi FD-25 protocol"); + + // Check if the checksum matches + byte checksum = 0; + + for (int i=0; i<17; i++) { + checksum += bytes[i]; + } + + if (checksum == bytes[17]) { + Serial.println("Checksum matches"); + } else { + Serial.println("Checksum does not match"); + } + + // Power mode + switch (bytes[5]) { + case 0x00: + Serial.println("POWER OFF"); + break; + case 0x20: + Serial.println("POWER ON"); + break; + default: + Serial.println("POWER unknown"); + break; + } + + // Operating mode + switch (bytes[6]) { + case 0x60: + Serial.println("MODE AUTO"); + break; + case 0x48: + Serial.println("MODE HEAT"); + break; + case 0x58: + Serial.println("MODE COOL"); + break; + case 0x50: + Serial.println("MODE DRY"); + break; + default: + Serial.println("MODE unknown"); + break; + } + + // Temperature + Serial.print("Temperature: "); + Serial.println(bytes[7] + 16); + + // Fan speed + switch (bytes[9] & 0x07) { + case 0x00: + Serial.println("FAN AUTO"); + break; + case 0x01: + Serial.println("FAN 1"); + break; + case 0x02: + Serial.println("FAN 2"); + break; + case 0x03: + Serial.println("FAN 3"); + break; + case 0x04: + Serial.println("FAN 4"); + break; + default: + Serial.println("FAN unknown"); + break; + } + + // Vertical air direction + switch (bytes[9] & 0xF8) { + case 0x48: + Serial.println("VANE: UP"); + break; + case 0x50: + Serial.println("VANE: UP-1"); + break; + case 0x58: + Serial.println("VANE: UP-2"); + break; + case 0x60: + Serial.println("VANE: UP-3"); + break; + case 0x68: + Serial.println("VANE: DOWN"); + break; + case 0x78: + Serial.println("VANE: SWING"); + break; + case 0x80: + Serial.println("VANE: AUTO"); + break; + default: + Serial.println("VANE: unknown"); + break; + } + + // Horizontal air direction + switch (bytes[8] & 0xF0) { + case 0x10: + Serial.println("WIDE VANE: LEFT"); + break; + case 0x20: + Serial.println("WIDE VANE: MIDDLE LEFT"); + break; + case 0x30: + Serial.println("WIDE VANE: MIDDLE"); + break; + case 0x40: + Serial.println("WIDE VANE: MIDDLE RIGHT"); + break; + case 0x50: + Serial.println("WIDE VANE: RIGHT"); + break; + case 0xC0: + Serial.println("WIDE VANE: SWING"); + break; + default: + Serial.println("WIDE VANE: unknown"); + break; + } + } } From 9f894547b0584b7a8d4fffc9f3b269ffea7aa519 Mon Sep 17 00:00:00 2001 From: ToniA Date: Sun, 8 Dec 2013 20:14:29 +0200 Subject: [PATCH 06/94] I-See, Plasma and air direction additions --- rawirdecode.ino | 58 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/rawirdecode.ino b/rawirdecode.ino index de681a8..afcbe1e 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -274,17 +274,17 @@ void printpulses(void) { } // Operating mode - switch (bytes[6]) { - case 0x60: + switch (bytes[6] & 0x38) { // 0b00111000 + case 0x20: Serial.println("MODE AUTO"); break; - case 0x48: + case 0x08: Serial.println("MODE HEAT"); break; - case 0x58: + case 0x18: Serial.println("MODE COOL"); break; - case 0x50: + case 0x10: Serial.println("MODE DRY"); break; default: @@ -292,12 +292,32 @@ void printpulses(void) { break; } + // I-See + switch (bytes[6] & 0x40) { // 0b01000000 + case 0x40: + Serial.println("I-See: ON"); + break; + case 0x00: + Serial.println("I-See: OFF"); + break; + } + + // Plasma + switch (bytes[15] & 0x40) { // 0b01000000 + case 0x40: + Serial.println("Plasma: ON"); + break; + case 0x00: + Serial.println("Plasma: OFF"); + break; + } + // Temperature Serial.print("Temperature: "); Serial.println(bytes[7] + 16); // Fan speed - switch (bytes[9] & 0x07) { + switch (bytes[9] & 0x07) { // 0b00000111 case 0x00: Serial.println("FAN AUTO"); break; @@ -319,27 +339,33 @@ void printpulses(void) { } // Vertical air direction - switch (bytes[9] & 0xF8) { - case 0x48: + switch (bytes[9] & 0xF8) { // 0b11111000 + case 0x40: // 0b01000 + Serial.println("VANE: AUTO1?"); + break; + case 0x48: // 0b01001 Serial.println("VANE: UP"); break; - case 0x50: + case 0x50: // 0b01010 Serial.println("VANE: UP-1"); break; - case 0x58: + case 0x58: // 0b01011 Serial.println("VANE: UP-2"); break; - case 0x60: + case 0x60: // 0b01100 Serial.println("VANE: UP-3"); break; - case 0x68: + case 0x68: // 0b01101 Serial.println("VANE: DOWN"); break; - case 0x78: + case 0x78: // 0b01111 Serial.println("VANE: SWING"); break; - case 0x80: - Serial.println("VANE: AUTO"); + case 0x80: // 0b10000 + Serial.println("VANE: AUTO2?"); + break; + case 0xB8: // 0b10111 + Serial.println("VANE: AUTO3?"); break; default: Serial.println("VANE: unknown"); @@ -347,7 +373,7 @@ void printpulses(void) { } // Horizontal air direction - switch (bytes[8] & 0xF0) { + switch (bytes[8] & 0xF0) { // 0b11110000 case 0x10: Serial.println("WIDE VANE: LEFT"); break; From 0517c6af9d1667c43154058d4f4a6ce67b5feefb Mon Sep 17 00:00:00 2001 From: ToniA Date: Sun, 15 Dec 2013 13:57:50 +0200 Subject: [PATCH 07/94] Carrier & Midea timing constants --- rawirdecode.ino | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/rawirdecode.ino b/rawirdecode.ino index afcbe1e..4ede94f 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -35,14 +35,22 @@ uint16_t RESOLUTION=20; #define MARK_THRESHOLD_BIT_HEADER 2000 // Value between BIT MARK and HEADER MARK #define SPACE_THRESHOLD_ZERO_ONE 800 // Value between ZERO SPACE and ONE SPACE #define SPACE_THRESHOLD_ONE_HEADER 1400 // Value between ONE SPACE and HEADER SPACE -#define SPACE_THRESHOLD_HEADER_PAUSE 8000 // Value between HEADER SPACE and PAUSE SPACE (Panasonic only) +#define SPACE_THRESHOLD_HEADER_PAUSE 8000 // Value between HEADER SPACE and PAUSE SPACE (Panasonic/Midea only) // This set works on the Panasonic CKP /* #define MARK_THRESHOLD_BIT_HEADER 2000 // Value between BIT MARK and HEADER MARK #define SPACE_THRESHOLD_ZERO_ONE 1800 // Value between ZERO SPACE and ONE SPACE #define SPACE_THRESHOLD_ONE_HEADER 3200 // Value between ONE SPACE and HEADER SPACE -#define SPACE_THRESHOLD_HEADER_PAUSE 8000 // Value between HEADER SPACE and PAUSE SPACE (Panasonic only) +#define SPACE_THRESHOLD_HEADER_PAUSE 8000 // Value between HEADER SPACE and PAUSE SPACE (Panasonic/Midea only) +*/ + +// This set works on Carrier & Midea / Ultimate Pro Plus 13 FP +/* +#define MARK_THRESHOLD_BIT_HEADER 2000 // Value between BIT MARK and HEADER MARK +#define SPACE_THRESHOLD_ZERO_ONE 1200 // Value between ZERO SPACE and ONE SPACE +#define SPACE_THRESHOLD_ONE_HEADER 3200 // Value between ONE SPACE and HEADER SPACE +#define SPACE_THRESHOLD_HEADER_PAUSE 4500 // Value between HEADER SPACE and PAUSE SPACE (Panasonic/Midea only) */ /* From 70f2870a70adf211c75f1e072841825a5cb07124 Mon Sep 17 00:00:00 2001 From: ToniA Date: Mon, 30 Dec 2013 16:54:26 +0200 Subject: [PATCH 08/94] Mitsubishi EF maintenance mode, Carrier codes --- arduino_irreceiver.png | Bin 0 -> 4615469 bytes rawirdecode.ino | 164 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 arduino_irreceiver.png diff --git a/arduino_irreceiver.png b/arduino_irreceiver.png new file mode 100644 index 0000000000000000000000000000000000000000..6950b8144a15063edd784f266cac1d72799d7699 GIT binary patch literal 4615469 zcmeF44V)CkxvvXF1T?HsKrnomCR8DtF$&>aFSNp6QwCp6Q->%HP`6 zU0?6}RPQGJ_pPe`eep#Xe5>&Zjg?a0su+0wrApPmRjI>2Y1kn4r1q$Wv45-jKRdT# zSi@Mlt>INuWA}|F4E$d|Q>yb_@>6g6@UP!j>O@s>e!pRp>#2d$7fq;lFqWhx2?7v+ z00bZa0SG_<0uac7z}saxG^Qy8AOHafK)@dYhxyYDy+8m05P$##AOHafKmY;|fI!3u zJo>voFV?XD5i=cgApij{1nPUKL>&k~00Izz00bZa0SG|AJAp6DXM3+qO$a~$0uX?J zPXy}Mj8R1Q_ym(~AOHafKmY;|fIvwQST+0RqlpD5iK)g(2tWV=5QshjM4#x-As#>g z0uX=z1Rwwb2tWV=5P$##AOL|l0fZi21PDL?0uTtAK#%rQ#}Eq;G-djS00bZa0SG_< z0uX?JjR2yL4G6aofB*y_009U<00Izz00bZa0SG`Kssz}iIjS>P>~{a^wa)hu3sCH{ zjDHY-fX@Wpc;gNA?6c4M+$-JrP2lLGk5)I{c%wS}@WcJ?n*JaF0SG_<0uX>eB7uR^ z7fnFlY%WD(ckkY~AkkQZ2M<{@X|{!ss8=@t4}}uG>Go$69N!`00bZa zff6Tx&{N_s0PaHo0uX=z1WK5|+~dlY7d#f=wbx!#=bn46`uO9IOL!`99Rd)500bZa zfglL5VRH~v=@SAFfB*y_0D-gwzWnk_wQt|P_|Jg@2h`VJf33dy>MM2d;6ZiRVTZ+k z>esKY8Z>C2jy&?n_*I@4OIp2pwR-sBht+Mj-Bv6v;RggD009U<00JQ(fY1|yNx%dM zKp;W{e)_=Qx)(eaAVRz_6#^~^8`;NZ|Ni|hHJu;Vt5;7oYSc){`R3pH z*0C$bGg+ z+RCx!@*~TiB=V=MtV}g++B7ZzW!o?9AOHafKmY;|fIv|Z7&v{=1k|2Dr?ltZg|mqT z2oy1$LjVF0fI#R8$QrF3J9en;+qWxOvlT>=kfBdL`6T`$VL#2AH&;!XGznrT`h)-k zAOHafKmY=fA%N%;nW@512tWV=5P(252#ENyZQHiM1Rf^}vNlY9MDY2}cfLc&j}s)G zK>z{}fIwjgH2=Sk-byS$Vc0XODxR$mnM?>k00Izz00at3Kz3l>xN)QU;DZkW5qvJc z{Bl)YU9Il9=N@&=Ip?Uu4?jG~UlD%ZefM3pWy_ZM?$1dTcmM$iKmY;|fB*!FoB%>k zkxwx0K>z{}fB*#YK|msUL}2;%zyBTBaIe4qx;pEuv()6tlhw;FzpVcCuYaj~@4Z*G zYu7GmOxfT^?Ag0_Z&Dc#AP@lpDG}<{Dc4mAOL~lCa`nnANv$A7C;1_fBy5I@r{{N zftK^rd-v|0s*2|_iSV(f!wx%4{qToBRByfYmb&Y%yVQ|K9%)ti>Z`9*O-)Vws4(jx z83;fC0uX=z1Rzkv1Q30Scye(K0uX=z1RxL;0g2=hktbc`%+R4j)%x}8<4dsm_3M|j zITFot{q@(!muuNOYtW#9y7ksu)f;cTp-w*ekQn>WW#@Je{d4Fn(n0SG_<0uU%d z0th`tIH|Y<0gnVey72#XDPSysM=PlY0SJVifQTrm1fPZt8>-*`{`cz1C!dU;nk5l8 z&ph*tI_B3K>z{}fB*!7B_QXmfA-mDu7k*#=>PrS|E)%i z8kJC|Ns}gO(V|6;yDTSE^l&2~MxrKl>C#0#_uO+yE#z$W1q&7=*&rJLv6FKWE*?Mt z0uX=z1RzjE1Uw5pt%v;dBw_)IXy$PR0uX=z1R#(jfzLnxT#4x8mPGLR+u#1C&N$-? z$11YZ^5VsdRf`ra9LqC(C`W|-$AA2X`rYq-m(*0wY=898N7aZCBdjvL9xdn{rmX?V zKmY;|fB*y_0D(Lc@GSb|*)E2I00bZa0SG`KWCTPUk@Zk+sd@9}YRQr%s=U11txEj4 zluMLPSy@@idR_~~uJPl?t68&VSxw}b9Ot!g;llW_U9y|APW$%lQ(L!g)wf(B009U< zAOiv?{OmSj0W#o4RR|QCK>b1+!1xe=00bZa0SH8cfCwe~_wRQ!Rw8NU&!4YOIw=+y z>>){%&zWbQ8ISbwP~B4%+0D7Sx;lPFyByWEd-rbFcH6gaSKt2jw^ifDja}gG4U z`Hkw(p@Xj?kM#fLmtU$s{NWF3#flXvMILPn5k8tkz}N8;2LF^;fPf*>Jp>>C0SG|A znE=r}&Rlp30SG_<0uX?JK|qedI(YD)@uV)>y?b{hqEEW?>8GEn%PzZ2z4+pb>8j?X zT=pUQ;)^eoZ0<~g00bZa0SG_<0>wfAp{H185V5q2$Eshye(LSF-*zf4*fWVRcI3!9|91700bZaf!qj`T|Kl9`vN#HMD%fh!XpSk z00Izz00a&Zke!X)(j}K%qUzUAy)WRDDO22fB*y_009Uul(+9;17HKlkZo!~%qVHZc$a5P$##AOHbP0MSR2;0gf6tt@_Sj=py?XT=%k;x{-+fnSLw|KUb3XKjV;}?|009U<00I!O5kTm%0pS(`5P*QM z1gojZ4?s}h=WH$OvTAcjBy0uX=z1RwwbuLK58Uo-*b z$15%BK>z{}fB*zakbv8;VOiBMcG z{Oj7-akeL_iu3!$&I%ww00Izz00bZa0SG_<0uX=z1Rwx`YzZLxWJ`{A5P$##AOL|9 zCO|Yy2~R$*LjVF0fIzSWF8jaleM&4qu#8KncSN5OnrvK!00bZa0SG_<0uX=z1Rwwb z2tc5?2_Wjij;OVYSEXR=j7~U=NcZWWqv2 z1_BU(K*j{vz$as5G=KmEAOHafKmY;|fIu_}G;7vGoe~pu%v1=GN5)^hrp7YkymL>n z@?;gQx7~sEI?_!On3_c2tWV=rAdHwU8OnicntwR z3CwzUvDMu%#~fwl$v_|q1O^Z4p$_bs9|cpfu>=Va-BW^-j>`~$00bZa0SG_<0uX=z z1oA*&!w1LQO)NkjKr;jc+z250xRK#G1Rwwb2tWV=t^|ngaYaJ`1Rwwb2tWV=5GW`C zgr0)pXK)BW00Izz00bZafdm5GhWwxru>c83!gM2Nxy#S=8P8f~ZT?_~nmuQU8ajMh zyiCfg?wqIo{QOE)yKA>q&z_MosdN4K*iH~eYS-~%KydGD^(MdL(mU~?$nc5)zxG{Qp={ox`p6_2@z9H_1woCjM+hi=ilO7{1zRSBK z>|`Fz_fGQiOv>c_NSFMY7tKemCV%s}7kk8Bsb{vc`{1|D+03ckCho&cAh3OW!tFNJ zK>z{}fB*y_009U<00Nm1sI5ID;D}gq@zAMBP4Bwr$@nul`@Qn^5lI_EOJURFf0WcN z-Gg199~yV##;xk)Q*KD8-ltDz>wez+W$KA1mbsogFK1`#v~u~qj`rzt`_-!rx}69Q z=6L$KFp?i(H0!}}N;c5;l0*o&^H=kfx!;krTlm}x%iqW6PEZ3cIL%9U8PzGY?_#Xm zJ11o(gDJNRQ&q8d&n@{qFHYmknmJC5yLMK*X!(kbO7{3km!zIf(%wu#*qrH{n)Z;7 z1_6YgXv`B_H1w6 zzVMe-o!t~2jJ;|=Tt$(GqPmE(VGnPxx6vJl(&JVp%1P^1P2+t^_`|Say^~ZV z=|{p_Lej7o%8(Q>YR;oeETu>KoO<__szZmC3HIK6(+M)^Wr#A?BOY~In_x*vTna^L`qdrY}cV%GQYZgkT!drQ|$-W=n-H8 zpXkjQ7UxLdi%aVLmRNusA<`TI5P$##AOHaf#0c1h3=Ks-R?QHT4I8)UqMWa?PUe}X zCaW={&vO)fWB>^#x_`!4N5MzRMO2bLbgHg?TW398z1Z-;%;&AXWNn+1kR|JmV)B%C%WeK;eVho4_K-5o!|cb8 zdC~p4UCp&w=9(?1`XWB+blGKfB4p4udzn*h7uVgoZMf?`y z8U!E!0SG_<0uX=z1pFk>{nYQK?C!{hN2Dyzws7DDr)S&Lw|41lSh3@ycJ%k0Omd2yWpqri;ja$DB5YIWT^ zBl}G0lnfIu_|WD+$bkreCR z+8jH!>LVp*PwV5Wa`iNw4dxOt+1T2Tq$5A1ub@O6FLoBfcavwU?;PjFOvgHAKYq=N z<3P5DdU=*Sb~~oaT+Y?2X9uf?eAt|BPAy}|M3MkPPb8-eqagqR2tWV=5QqSQ4ci`j zTE_xJz+6m(KyeY6KI2(m_YW#AI??qeiUNgR)+NH7h&cAsy3FNVvZs!0I!%IrUjz_+ z{31y|5P$##AOL|9BH(377tuW>^g`e&1VTXI`tgrg$6A}_$R^V|)zFIv}7rlx#_sMgzM5vdb`eRzHN3jgo0=hSbA6ATTy$W zHD_200SFW^fxEA2xr11MBA#Geg8&2|0D-&^m^Xh}g6bnjR$VltXHwXNoPVBb!<<}< zl`svx9_P|azL)Z-F0bRr&c9kY*tBU|%45>J_8COIAbdCb%*A)JAHU|sD?@XwmVIrP zxu)y%)9a47lfDaSvsd4!7j6RV+8plr!8!;)00IzzKqv_i;uFeg!6*onEP?9kw=LgA zAyw>z{n`xi6#i>C@TjXZP-XR^BbMb?Z*IqClSq;d^;`-45X& zZ?DbT{vd4gYK*MU646GIh=BT-E^{4MJcOr7VmMeKZT9LL^}l4mw)!zzXPMvwQFmu@23~MYUOjubU*dG zR&$9CNteuX%+uBNvpfjjzu)^L%S1C0#(L;66NGIZhmKb>&pwyk#3gOmxFvp-e^m@T z&C2Ucp>5W7aE&ejM4#x+9hO4?0uX=z1VT-K4SYg9Js1apq9w3r?|xU`9{JD3u4TCv zoY||Rl4VztM0k3A`G#D(R9(BYvHFlcB`U-%N#w}5YiGF?2l_k+-_1T{DVr?S%O;uq z_%$z?*kLZ^TD|6<@usF|C-&J>#x{Sx`SX~}c%rH}zuz!>XWW-O0YsmYe+2*l0SG_< z0uacYfR`m*nfFQy2tWV=*%6RsF*-fH;5D7KuC=>%D>=H$i~_O>eau{U?I5eSK7FUC zmzS=y?#+zYBchP(G%foHxg}W=WbQgW`PRoA>q{RqA6n$rOBEmqiG3_P!~(2c`+l56o7PPuOGGx} z)f+L(ReaKW;F!mAEnZZqWVx08==R&;e&=*mkI$W;M4*r~pyH1B)jV!VKK#ve^G%nS zw@G<9?y0(ZeSG6)iFlD8vp>6kdd#Gb0)7yL@6xA;UYeOiGR1#ld*kSRel#0#PwEa< zJ(4^P!nUMAQaq4lTl)Q6`0NU+X3w7Ot-L)`Xq)Z!a33uKh(6JpFRX;-3DFTdqJj>?4K?yxF z-iYC6siTi-s7$WeR}rs+Zl#+{=5LDizY^RCZAoegX(R z;h!Tcga8B}009UTp1{8@I{r<^SOBHcFX^f}SScP5?o;@A!~zJI1Vjx0c;meUB}R^J zlB1X;yd&M0zcy6X2HihntQvVmU$y9ke^~vAdN8P>hw9(|RO`I<)~yN~s^z!u!ociP zc0AT0KT;+PB%Dijd3M?uN9yQg_T$IA=zh~(MZL2ta+h6pR=Q^K@zI|Xe1PFXJqq>$@fCx+!CPs$2tWV=5P*Oa0it`HsPL>H1cuD``^~w<0#qHWEQsX{3IPZ}00I!m4*`^){BULz z2tWV=5P$##AOHafKmY;|fB*y_5I6yZp1=_^00bZa0SJVL!2V$^FUlwc9(Wf}(5icMB0SG_<0(m9iWnEWZ4Pr0|KmY;|fB*y_0D(9G zgdSc52&5tK_rorjODsSdNEAZ=0uV?;fasnyfGCCl1Rwwb2tWV=0T4j+2>>cxLI45~ zfB*y_009U<00Izz00baVGz7kQ_>ad$G8O>Qr)b_m9Dx7?AOHaf(05DQRsu+lG!=m!E2 zC{+R|KczbJ_zeLFKmY;|$bkSMJ~_anDFh$@0SG_<0uTt0K>YywrfUd500Izz00bZa z0Z#*{7C+eCDais#~|VEJw45kbywq2_Wz{}fIzSW*vuzbzVr?O z2tWV=5GYv!ME8{J)Z=)_3Fx!6r%fHDCXOExw|3*kt?Iew-%yh#&yC;9QA~p>dZ_;W zPfgm5_szBMs}(EWQ_nA2sTMxBB3^&WEyGmLGuo>@eLAbMCXF*9P`hils;*wIR=n|^ zy6c`Nt+u)J`TFt=>hG_<>&YK$K!=QGB}M7Z8{hoF4%M#X)mASD_RP2Pxn!i@zy5Vi z67RlecTxS$>6%N|p=lcwXJPm1YwyP1uea5#hZkG^%DdmO!wITa&kji&5?PfTGC|CR zU+=1exvr1&@9^q1HI7`e`8{?WjDms?K%;;S@BHFf zaT2Jl-5qyq-uz{1CexvR2L7O>R46xDTRv{ReBOQE@RLsr_G{yaW+f*N?iY4SMqDHU(wBfMul zI4&dMBJ@uCWMHayON*N_l=*(A8rtu@qI)7Ytr%Ql1Vkv12oWvvNW_Cq<_Dh$F%sD_=g}or z^o>N`l$9Nd;;E_GW(g}=9GWxxy0lR~_77(fEhMTyCyDsc{SBzNLy15$V)$9QIO!@; zLgTKPWwntC<9<3om6x}O*VT54ztUIOQ`MdGEZ$rWmm{R*2t6U47|en|X%Tqzu-Eqz z3s72fj<*ni00bZqHUc81$cNv=2}2S@nvs|HO%PBf-Z&>NrpU+h^xuw7TIMzOssTzq z5+%t;rHB$bBFM?-mzSZNj z(2AW?@4ixX=+IIZ$FDcvbcqsQw9u7M7@`~Dqd&l%mN;HLCi&=-c^Ze6Crfu6@49lk4FFGyo6oQW9i0PK#?etHm#fL$JSMr zZJ7}>8#ZpS%A7I}%zWN@>?HE&LJ@zYT&HQjenw{#t`aR_Mr=&F`QjvzM;eIjB2e{B%+Y)D?>U&K#OE@*ZX@XIbxA_cFgGW9Lvmye$PcA-&NUH>;4&I z9Yr4TS>7{Q)~1u}c|?jRfiJ5LuZ*JF^T^D>Gl&I<=u3y$5P$##AOHafKmY>aBrt2{ zxP;Ii)5^B7a2hj-vLlL+TROdaxm9GUFxJCwG7`3r1&-IHa9~X4uoJ zRW;V5bbZR#t@s_*B>tIvQf-EG*=2R>++=rRQZj*kdk-W(^!Ox*xd{8aD&b-}nLAzU zXI!H}Ac~?-G{|Bl1Rwwb2tc6339x}riN6529|;0oyS8?<(|*iW=T5C$>q^0q4Ub4! zt_#<#`#@)78}^)R15W`&+ONB z?GEkaY=0hxd(*$hjgL%ek~x3oTBekZN}#ZR|5Gh5B~t2yHe-|=O(kopGD$(qMKQgr znXsg8ehIK^bAEX<8U!E!0SG{#WC#%5Q!-N>8AtDbaAl|Tu>g^Arzi|Hmx*{4U(9;a zUN$5#lU@QP&&;}Jov7qZKC-JeDRTm{Y){SoKUNFP5a(gy?}P|5_R+%im+l{GG91UdOG5j-P?pB0bh zv5P+!51p!nuh+Bei(xLVAODClBbGGWX;Vk(tg5Zu9Y6ZbdO#+U1Q2~9Ic*pX0SG_< z0uX=z1Rwx`XcCZmvPpBa<_l{hKwxa22JQWi1*ke$83Erh5dsh>VFF&(brIrI!Y>G} zLm&l#?x)tB-Tv|R+7vbGip+XudtIYXpUyg~cJJP2<=rw{x9)T+%KEvfPG#LmtfA@O zC!c(tOIJnNHW$vK0F4=aUR?FiaGqZ9s?H`|gP4m#y{pyLZ(II}>LVPQHEUu$BU4-i zP=1PQKJf+u5P$##AOHafKmY=vCeX8I>_3uylb(2Dna-xXn)S@~x>5I2zpJyc4O>=O zdAE#tF1%YE`}2K!57-|i+{>=Fex$BlTU*_v5+STgMV842XY=?jGwj8VGKjgz$Gd9x zL+(vK&;G&5PF3R1KKtz7u4NQ>CGhfbFMrGbSOBl1+v_3v*oklt0SG_<0uX=z1Rwwb z2n0qz)+x!_7D=)e>*b~EQjYcd@(tFSEotYp&Z%pcHdY_4YWCJBYam>Rr_~d%5n}yWh3i zBmcSBwTy!35J2dO&Rk(B1Rwwb2tXh-1X#uunyJ7L2t<;=q?<3c?3S~}-NX!e-ly*r ztL}Bz4zluQMwX+PV!`BFA5*dsi9N})nGY?pKX$!0BQfMyr#IKWpHx@+9Cyv34PlZB zJwCYX+?%_*#R8Z<3kx@4tajJ#I7iuy*U5L&6!Bx|@M-ZMC!fr^`Of>zT#lo1^HG@n z;ZJ|FT$JTasgmDw5!8E|$_FWvU8i+=dckWtYh8tla5E!bvc(E9Ikqa-Py{Bx*>0 z%p;Y|V}}+ms#GF2rAsoNxyx>kUNWM z(!OOmV1oBn;XzsIta5W&YzFk1HrJ<*yktc3ss zAOHafgn|IkJ%yfz2J?0t;T8)}XxkVc0+|z#v$C5vE3?EGEz~&m^vqL})!;!roQkYx zvKjBJ2gfN9H1r3n{>Zf|#WK>De8`J{;#6+dmHgDZuZ%y^f{E_itfvJjuYHeAExB z=n;?P&`~I@TX~B{>CmC2nlt;lBpY+-(~B=sN5m~RJ`!alCr8N!)=v8w*~c)?7ngjd z$w}m~_mOH(7<*m&y`P*#a9X}%quQ`>OHwqCMEiWd_erW#r&h}AdnLB|ITzAj5bvt= zn<|N5)j8((l9%30;37-eWD{qZ14$>gZI-GV$|69ZUhKcl9gKjTm ziXR3P3s7~iGDqWR?jM25%1X6x;X?m9wDq)R%^KzO{~ek(ZK}3!-)^g&c-yvZTeW%f z=ER2~y*uu><5cU`b;slQZRYOXyVZsb$vdz9@|V9f?O@#R3!=Dcpeo1Rwwb4+QGf ztLLE>RUiNX2tWV=5P$##{3L+r<0nn}f&c^{009V;2mzvdN@SvO69N!`00c^!z;CAP zY(gwRv|j;4pJ>k?)yCGf!h!9OAvAivBR4FV8=fI9(1A9psCKmY;|fB*y_;6lL5k}jfqT;TB- z0uX=z1Rwwb2n0v~p(jANbPWLrKmY;|fIxZz_x*gylR?G;R2{5LZv-_U009U<00Izz zK(P`)`64gjfKV7l6kQfB*y_0D)Wy5Z#k2LE1wA0uX=z1Rwx` zUtjH`$y6IWab>?ynZ z;lN`7st#7V8bARAAOHafK)??I_5J9CJ|F-A2tWV=5P*Q403kkhBHTj&0%0Mr`GXy* zUB{~}a}Mm8Z{>r|9MNdFHN>V352-e-n_Bl|qC=oi^Z4f4_tlb@-c*w&&$VDpxn-E@ zc}9CRcu)`P8JW^1fY4Lg^N;rsfB*y_;1_|X&Yg94;IROH8S8I9g!}l*linZz0SFY5 zfGJ?O*?~aiMkasH=gnWHMvl5KX~4Vgc~Z@qIWFlr4@#E+qEG40J)T1V0uX=z1Og>M zbWfmo=^O$OfPilVF1oO%TKL?Gl>Ieo*dS#&g~1Y#s2)?~8TZoxs=T~Ke9-FZ^{Q*v z*1;M;?~x*a&=aZY!q_4s(EQbYok}zopvYztcOU=(2tWV=5P(3D5O`|-B(;6}F12;r zr>aM)=OM(*+fB*y_009U<00Izz00hEEVB^NEX_t$I z&+715H0$BTmQlwXbCi`Q0|6%j2t7_zcm@FoKmY;|fI!X!i0;W5Ass*f0uXQ{aPt58 zMepEZ0n8z*4py4Ce&koJt_xG~qd)q900hz#K>10}h#C-p00bZa0SMR#cv;tFYr-uA zAOHaflo)~9UAxs2Pb@1juW=Uwg(rZ}Q}{W+0ti3=0uX>eQ4%1;CnVF_;BPx7<|h^) zBo1RnCIndtxU&(E(^@{ylu zk~Y#-`q1O&+UM)bH+c3ZmA$4t={BN~acn-wV0-nzXIgkAEbgiJhYq>M`V9(MfjVmU~?$ zzq3wBkCW@~pdQDZSN$&NvV8t+aq`uCrvH`+Uu~aU#r{nHp6T|c?{2@lnf7hk>^09( zU0z914+0Q?00bZa0gC|9Jr)Ww5Qr>++S)^+%)I%_63%)Shva;2eMYw&(KL8akGP-m z5i@D>+$5jxy64IGGdbJ4^7au)yZFQX|d?+8b zcm8UgGB-Psa?_v3=T1-qFF4I?7TSC&-oi(C&w6m2k{y@Blukb7hMdju``r+Og6K1q zqat#c$DOTQey``>4e3{evP{#i&ky}x%h>jL=+mcjQg=Zx3B%u(Bu9-0@;6VS&h+0n zqbQy=|HfrHXWDl+uKMp)mdPJ1jwBaE&=g}off3QrsWtb`}YaFj$Q?tzyS@cKL zoY~i@vL=Vhb)WXDUC@w^AoHWNx_Z6!Q8u9B4kcpEh~a11yRq&iLSfuBv#d5!Fz%-V zRC#%ecpYu$$Wiyj`?4B_G$WA~nzxu}GS_T0@309!^1kRKktjM-|vR>9Ymj|&Gx(qkdtqH%z7;1sr@K4tI#Fmm802QLj_AIPw-?f-+_O1U0$(VY4FMlhgex?c0CsSh7hX4d1 z009U<00IzzK#>p-0YyGUCQcZVAk2)syl;YNGV#Va@edObW2XOhv})73X##<oY|{mLe+`mhbUQ&)u->2_;V3xPHfvOsh(~mLX`e^ z5<92feWmKqp`|X4UvIwY5+%M!UtvzdM}KTOF&8#k2oj&=?DVQwR8?4#{r%8(iOAzt zrt1XpZUoV%w%6%u7ac{UO!fQV)Q3ECW0i`dyf>{|HI4r+%$hk)4I9=w;dfh)6*QAV z{cX|yrMgOVpNPOCujuRe_hj@>&Qqp9`O?z0DMe=cSmL|W@2%9!^lzoTZ?E_4-Q$%M z^->eKebx=n6AO@<5v34-00fGZ086@xbYgLgFVefQ(tkbZ;cQ`_rwoqpf5mH;WjORA_er}nu% z8+Yw2CDBA_(=cxU{Z*`Ene73k#i!pAelUV@BjSW9t#lI-x;N<4pusBCKd#638B-;zB!nF?sk>vP z73C)zYP5j>1Rwwb2$VPhQDde}o2JH&9jhuTD%2TgoT1vcZ?9UkXc61gqf9kw)JVPa z&O1q}jf4h${q@)CtFOND^dohh!ihu?s$ajpI^u{U)R9LXsTwwH7$387T@mWssg-M8DLAs>5h=@c;ktDn=xl7mo|CP7s*I^{tXT10 zQq|RK{%Jk)(r0=3@mBq?WlaADUT}I!^JJZyPV3iysI!s12JvnL(Wf1-RzzhtV7(qs zRG&hvwM+NjKmY;|fIz7ekZ>Fk zQ$%q2^2;yPfddEB?%li9&wu{&f}s%sNPaR&A`Hoo4jB@$Y5)HHnRG#22oxOwbJyH- z&K+~iQR%99Dqp?kn{b?KYd^Wohy`fgtjtq;v)-Y;5<@6t^B|qfy84;@+OFN9ot*8@ z!*Fl0iW$?m@sTM_GUrmnhC+!9DUHma(DdEQJWAJQJ*Rggh(4WMlB1dQCQ%ZeCTp4s zl{$7fA?48dU}CD>sTPy3w6T0DImlaxs`nmszs-X_TVbkQUE zU=pN%e)TO!sI7Zzv#P22NXc3D=J8p6jZvt65PAwlo^c@n0SG_<0zMG9>Z+^Mo;`ci z4L982LvNw);-n=DCK<5+{@W`;&=+5P5&!YOcY1^X1Rwx`VkaQycAH6}d}MzX`%!Ub z9rA@GU_Xz(u-5oLI--yNOz9B<5P$##AdpuA*I$3V`qsC;rN)dIlkqaAyc)`25P$## zAdnFO+5e@itZ_zds9YEVGFP2Dx3ZRUO}_OpHD~s91ᴒkfMdj0rElo{cqEtbud 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

~v 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 NknvD{OdMgV-Cq~gs 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 literal 0 HcmV?d00001 diff --git a/rawirdecode.ino b/rawirdecode.ino index 4ede94f..bee3821 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -16,8 +16,14 @@ //uint8_t IRpin = 2; // Digital pin #2 is the same as Pin D2 see // http://arduino.cc/en/Hacking/PinMapping168 for the 'raw' pin mapping + +#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) +#define IRpin_PIN PINE +#define IRpin 4 +#else #define IRpin_PIN PIND #define IRpin 2 +#endif // the maximum pulse we'll listen for - 65 milliseconds is a long time #define MAXPULSE 65000 @@ -28,6 +34,17 @@ uint16_t RESOLUTION=20; +#define BYTETOBINARYPATTERN "%d%d%d%d%d%d%d%d" +#define BYTETOBINARY(byte) \ + (byte & 0x80 ? 1 : 0), \ + (byte & 0x40 ? 1 : 0), \ + (byte & 0x20 ? 1 : 0), \ + (byte & 0x10 ? 1 : 0), \ + (byte & 0x08 ? 1 : 0), \ + (byte & 0x04 ? 1 : 0), \ + (byte & 0x02 ? 1 : 0), \ + (byte & 0x01 ? 1 : 0) + // The thresholds for different symbols // These should work with Panasonic (from DKE on), Fujitsu and Mitsubishi // Anyway, adjust these to get reliable readings @@ -251,9 +268,9 @@ void printpulses(void) { } Serial.println(); - // If this looks like a Mitsubishi FD-25 code... + // If this looks like a Mitsubishi FD-25 or FE code... if ( byteCount == 36 && bytes[0] == 0x23 && (memcmp(bytes, bytes+18, 17) == 0)) { - Serial.println("Looks like a Mitsubishi FD-25 protocol"); + Serial.println("Looks like a Mitsubishi FD / FE series protocol"); // Check if the checksum matches byte checksum = 0; @@ -283,11 +300,18 @@ void printpulses(void) { // Operating mode switch (bytes[6] & 0x38) { // 0b00111000 + case 0x38: + Serial.println("MODE FAN"); + break; case 0x20: Serial.println("MODE AUTO"); break; case 0x08: - Serial.println("MODE HEAT"); + if (bytes[15] == 0x20) { + Serial.println("MODE MAINTENANCE HEAT (FE only)"); + } else { + Serial.println("MODE HEAT"); + } break; case 0x18: Serial.println("MODE COOL"); @@ -322,7 +346,11 @@ void printpulses(void) { // Temperature Serial.print("Temperature: "); - Serial.println(bytes[7] + 16); + if (bytes[7] == 0x00) { + Serial.println("10"); + } else { + Serial.println(bytes[7] + 16); + } // Fan speed switch (bytes[9] & 0x07) { // 0b00000111 @@ -405,4 +433,132 @@ void printpulses(void) { break; } } + + // If this looks like a Carrier code... + if ( byteCount == 18 && bytes[0] == 0x4F && bytes[1] == 0xB0 && (memcmp(bytes, bytes+9, 9) == 0)) { + Serial.println("Looks like a Carrier protocol"); + + // Check if the checksum matches + byte checksum = 0; + + checksum = bitReverse(bytes[0]) + + bitReverse(bytes[1]) + + bitReverse(bytes[2]) + + bitReverse(bytes[3]) + + bitReverse(bytes[4]) + + bitReverse(bytes[5]) + + bitReverse(bytes[6]) + + bitReverse(bytes[7]); + + switch (bytes[6] & 0x0F) { + case 0x00: + Serial.println("FAN: AUTO"); + break; + case 0x02: + Serial.println("FAN: 1"); + break; + case 0x06: + Serial.println("FAN: 2"); + break; + case 0x01: + Serial.println("FAN: 3"); + break; + case 0x05: + Serial.println("FAN: 4"); + break; + case 0x03: + Serial.println("FAN: 5"); + break; + } + + switch (bytes[6] & 0xF0) { + case 0xE0: + Serial.println("MODE: OFF"); + break; + case 0x00: + Serial.println("MODE: AUTO"); + checksum += 0x02; + switch (bytes[6] & 0x0F) { + case 0x02: // FAN1 + case 0x03: // FAN5 + case 0x06: // FAN2 + checksum += 0x80; + break; + } + break; + case 0x80: + Serial.println("MODE: COOL"); + break; + case 0x40: + Serial.println("MODE: DRY"); + checksum += 0x02; + break; + case 0xC0: + Serial.println("MODE: HEAT"); + switch (bytes[6] & 0x0F) { + case 0x05: // FAN4 + case 0x06: // FAN2 + checksum += 0xC0; + break; + } + break; + case 0x20: + Serial.println("MODE: FAN"); + checksum += 0x02; + switch (bytes[6] & 0x0F) { + case 0x02: // FAN1 + case 0x03: // FAN5 + case 0x06: // FAN2 + checksum += 0x80; + break; + } + break; + } + + checksum = bitReverse(checksum); + + const byte temperatures[] = { 17, 25, 21, 29, 19, 27, 23, 00, 18, 26, 22, 30, 20, 28, 24 }; + + + Serial.print("Temperature: "); + Serial.println(temperatures[bytes[5]]); + + char bin1[9]; + char bin2[9]; + char bin3[9]; + + snprintf(bin1, sizeof(bin1), BYTETOBINARYPATTERN, BYTETOBINARY(checksum)); + snprintf(bin2, sizeof(bin2), BYTETOBINARYPATTERN, BYTETOBINARY(bytes[8])); + snprintf(bin3, sizeof(bin3), BYTETOBINARYPATTERN, BYTETOBINARY(bytes[6])); + + + Serial.print("ModeFan "); + Serial.println(bin3); + + + Serial.print("Checksum "); + Serial.print(bin1); + + if (checksum == bytes[8]) { + Serial.println(" matches"); + } else { + Serial.println(" does not match real"); + Serial.print("checksum "); + Serial.println(bin2); + } + + } +} + + + +byte bitReverse(byte x) +{ + // 01010101 | 10101010 + x = ((x >> 1) & 0x55) | ((x << 1) & 0xaa); + // 00110011 | 11001100 + x = ((x >> 2) & 0x33) | ((x << 2) & 0xcc); + // 00001111 | 11110000 + x = ((x >> 4) & 0x0f) | ((x << 4) & 0xf0); + return x; } From d8fa1da2390e86b1b0535e9bdc850d3edb7081bf Mon Sep 17 00:00:00 2001 From: ToniA Date: Sun, 13 Apr 2014 16:10:38 +0300 Subject: [PATCH 09/94] Ignore bits which do not form octets --- rawirdecode.ino | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/rawirdecode.ino b/rawirdecode.ino index bee3821..9135a08 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -63,12 +63,12 @@ uint16_t RESOLUTION=20; */ // This set works on Carrier & Midea / Ultimate Pro Plus 13 FP -/* + #define MARK_THRESHOLD_BIT_HEADER 2000 // Value between BIT MARK and HEADER MARK #define SPACE_THRESHOLD_ZERO_ONE 1200 // Value between ZERO SPACE and ONE SPACE #define SPACE_THRESHOLD_ONE_HEADER 3200 // Value between ONE SPACE and HEADER SPACE #define SPACE_THRESHOLD_HEADER_PAUSE 4500 // Value between HEADER SPACE and PAUSE SPACE (Panasonic/Midea only) -*/ + /* Panasonic CKP timings: @@ -253,6 +253,9 @@ void printpulses(void) { bytes[byteCount++] = currentByte; bitCount = 0; } + } else { // Ignore bits which do not form octets + bitCount = 0; + currentByte = 0; } } From 887ed4204711c0b911571f3090b7fd066e93f006 Mon Sep 17 00:00:00 2001 From: ToniA Date: Tue, 3 Nov 2015 17:43:51 +0200 Subject: [PATCH 10/94] Sharp/IVT protocol decoder --- rawirdecode.ino | 83 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 76 insertions(+), 7 deletions(-) diff --git a/rawirdecode.ino b/rawirdecode.ino index 9135a08..3c914af 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -51,7 +51,7 @@ uint16_t RESOLUTION=20; #define MARK_THRESHOLD_BIT_HEADER 2000 // Value between BIT MARK and HEADER MARK #define SPACE_THRESHOLD_ZERO_ONE 800 // Value between ZERO SPACE and ONE SPACE -#define SPACE_THRESHOLD_ONE_HEADER 1400 // Value between ONE SPACE and HEADER SPACE +#define SPACE_THRESHOLD_ONE_HEADER 1500 // Value between ONE SPACE and HEADER SPACE #define SPACE_THRESHOLD_HEADER_PAUSE 8000 // Value between HEADER SPACE and PAUSE SPACE (Panasonic/Midea only) // This set works on the Panasonic CKP @@ -64,11 +64,6 @@ uint16_t RESOLUTION=20; // This set works on Carrier & Midea / Ultimate Pro Plus 13 FP -#define MARK_THRESHOLD_BIT_HEADER 2000 // Value between BIT MARK and HEADER MARK -#define SPACE_THRESHOLD_ZERO_ONE 1200 // Value between ZERO SPACE and ONE SPACE -#define SPACE_THRESHOLD_ONE_HEADER 3200 // Value between ONE SPACE and HEADER SPACE -#define SPACE_THRESHOLD_HEADER_PAUSE 4500 // Value between HEADER SPACE and PAUSE SPACE (Panasonic/Midea only) - /* Panasonic CKP timings: @@ -551,9 +546,83 @@ void printpulses(void) { } } -} + // If this looks like a Sharp code... + if ( byteCount == 13 && bytes[0] == 0xAA && bytes[1] == 0x5A && bytes[2] == 0xCF ) { + Serial.println("Looks like a Sharp protocol"); + // Power mode + switch (bytes[5]) { + case 0x21: + Serial.println("POWER OFF"); + break; + case 0x31: + Serial.println("POWER ON"); + break; + } + + // Operating mode + switch (bytes[6] & 0x0F) { + case 0x01: + if (bytes[4] == 0x00) { + Serial.println("MODE MAINTENANCE HEAT"); + } else { + Serial.println("MODE HEAT"); + } + break; + case 0x02: + Serial.println("MODE COOL"); + break; + case 0x03: + Serial.println("MODE DRY"); + break; + } + + // Temperature + Serial.print("Temperature: "); + if (bytes[4] == 0x00) { + Serial.println("10"); + } else { + Serial.println(bytes[4] + 17); + } + + switch (bytes[6] & 0xF0) { + case 0x20: + Serial.println("FAN: AUTO"); + break; + case 0x30: + Serial.println("FAN: 1"); + break; + case 0x50: + Serial.println("FAN: 2"); + break; + case 0x70: + Serial.println("FAN: 3"); + break; + } + + // Check if the checksum matches + byte checksum = 0x00; + + for (byte i = 0; i < 12; i++) { + checksum ^= bytes[i]; + } + + checksum ^= bytes[12] & 0x0F; + checksum ^= (checksum >> 4); + checksum &= 0x0F; + + Serial.print("Checksum '0x"); + Serial.print(checksum, HEX); + + if ( ((bytes[12] & 0xF0) >> 4) == checksum ) { + Serial.println("' matches"); + } else { + Serial.print(" does not match "); + Serial.println(((bytes[12] & 0xF0) >> 4), HEX); + } + } +} byte bitReverse(byte x) { From 7df2e22b7bc2817d48590824901beb30c27af6cd Mon Sep 17 00:00:00 2001 From: ToniA Date: Thu, 5 Nov 2015 19:02:05 +0200 Subject: [PATCH 11/94] Daikin protocol decode --- rawirdecode.ino | 100 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 2 deletions(-) diff --git a/rawirdecode.ino b/rawirdecode.ino index 3c914af..da43506 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -51,7 +51,7 @@ uint16_t RESOLUTION=20; #define MARK_THRESHOLD_BIT_HEADER 2000 // Value between BIT MARK and HEADER MARK #define SPACE_THRESHOLD_ZERO_ONE 800 // Value between ZERO SPACE and ONE SPACE -#define SPACE_THRESHOLD_ONE_HEADER 1500 // Value between ONE SPACE and HEADER SPACE +#define SPACE_THRESHOLD_ONE_HEADER 1300 // Value between ONE SPACE and HEADER SPACE #define SPACE_THRESHOLD_HEADER_PAUSE 8000 // Value between HEADER SPACE and PAUSE SPACE (Panasonic/Midea only) // This set works on the Panasonic CKP @@ -124,7 +124,18 @@ void setup(void) { void loop(void) { char incoming = 0; - memset(symbols, 0, sizeof(symbols)); // Wipe the symbols +mark_header_avg = 0; +mark_header_cnt = 0; +mark_bit_avg = 0; +mark_bit_cnt = 0; +space_zero_avg = 0; +space_zero_cnt = 0; +space_one_avg = 0; +space_one_cnt = 0; +space_header_avg = 0; +space_header_cnt = 0; +space_pause_avg = 0; +space_pause_cnt = 0; // Only Panasonic seems to use the pause space_pause_avg = 0; @@ -622,6 +633,91 @@ void printpulses(void) { Serial.println(((bytes[12] & 0xF0) >> 4), HEX); } } + + // If this looks like a Daikin code... + if ( byteCount == 27 && bytes[0] == 0x11 && bytes[1] == 0xDA && bytes[2] == 0x27 ) { + Serial.println("Looks like a Daikin protocol"); + + // Power mode + switch (bytes[13] & 0x01) { + case 0x00: + Serial.println("POWER OFF"); + break; + case 0x01: + Serial.println("POWER ON"); + break; + } + + // Operating mode + switch (bytes[13] & 0x70) { + case 0x00: + Serial.println("MODE AUTO"); + break; + case 0x40: + Serial.println("MODE HEAT"); + break; + case 0x30: + Serial.println("MODE COOL"); + break; + case 0x20: + Serial.println("MODE DRY"); + break; + case 0x60: + Serial.println("MODE FAN"); + break; + } + + // Temperature + Serial.print("Temperature: "); + Serial.println(bytes[14] / 2); + + // Fan speed + switch (bytes[16] & 0xF0) { + case 0xA0: + Serial.println("FAN: AUTO"); + break; + case 0x30: + Serial.println("FAN: 1"); + break; + case 0x40: + Serial.println("FAN: 2"); + break; + case 0x50: + Serial.println("FAN: 3"); + break; + case 0x60: + Serial.println("FAN: 4"); + break; + case 0x70: + Serial.println("FAN: 5"); + break; + } + + // Check if the checksum matches + byte checksum = 0x00; + + for (byte i = 0; i < 7; i++) { + checksum += bytes[i]; + } + + if ( bytes[7] == checksum ) { + Serial.println("Checksum 1 matches"); + } else { + Serial.println("Checksum 1 does not match"); + } + + checksum = 0x00; + + for (byte i = 8; i < 26; i++) { + checksum += bytes[i]; + } + + if ( bytes[26] == checksum ) { + Serial.println("Checksum 2 matches"); + } else { + Serial.println("Checksum 2 does not match"); + } + } } byte bitReverse(byte x) From a70a7aa3d3e8379fa1b2988ae47f5cb902ddf5b0 Mon Sep 17 00:00:00 2001 From: ToniA Date: Fri, 6 Nov 2015 09:17:52 +0200 Subject: [PATCH 12/94] Daikin protocol decode #2 --- rawirdecode.ino | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/rawirdecode.ino b/rawirdecode.ino index da43506..08fae9f 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -635,11 +635,11 @@ void printpulses(void) { } // If this looks like a Daikin code... - if ( byteCount == 27 && bytes[0] == 0x11 && bytes[1] == 0xDA && bytes[2] == 0x27 ) { + if ( byteCount == 35 && bytes[0] == 0x11 && bytes[1] == 0xDA && bytes[2] == 0x27 ) { Serial.println("Looks like a Daikin protocol"); // Power mode - switch (bytes[13] & 0x01) { + switch (bytes[21] & 0x01) { case 0x00: Serial.println("POWER OFF"); break; @@ -649,7 +649,7 @@ void printpulses(void) { } // Operating mode - switch (bytes[13] & 0x70) { + switch (bytes[21] & 0x70) { case 0x00: Serial.println("MODE AUTO"); break; @@ -669,10 +669,10 @@ void printpulses(void) { // Temperature Serial.print("Temperature: "); - Serial.println(bytes[14] / 2); + Serial.println(bytes[22] / 2); // Fan speed - switch (bytes[16] & 0xF0) { + switch (bytes[24] & 0xF0) { case 0xA0: Serial.println("FAN: AUTO"); break; @@ -708,15 +708,27 @@ void printpulses(void) { checksum = 0x00; - for (byte i = 8; i < 26; i++) { + for (byte i = 8; i < 15; i++) { checksum += bytes[i]; } - if ( bytes[26] == checksum ) { + if ( bytes[15] == checksum ) { Serial.println("Checksum 2 matches"); } else { Serial.println("Checksum 2 does not match"); } + + checksum = 0x00; + + for (byte i = 16; i < 34; i++) { + checksum += bytes[i]; + } + + if ( bytes[34] == checksum ) { + Serial.println("Checksum 3 matches"); + } else { + Serial.println("Checksum 3 does not match"); + } } } From 371a03933ae9df3b00ac269e51ec4967f9cd2209 Mon Sep 17 00:00:00 2001 From: ToniA Date: Mon, 4 Jan 2016 16:47:25 +0200 Subject: [PATCH 13/94] Refactored decoders --- Carrier.cpp | 145 +++++++++ Daikin.cpp | 104 +++++++ MitsubishiElectric.cpp | 174 +++++++++++ MitsubishiHeavy.cpp | 68 +++++ PanasonicCPK.cpp | 63 ++++ Sharp.cpp | 83 +++++ rawirdecode.ino | 678 ++++++++--------------------------------- 7 files changed, 758 insertions(+), 557 deletions(-) create mode 100644 Carrier.cpp create mode 100644 Daikin.cpp create mode 100644 MitsubishiElectric.cpp create mode 100644 MitsubishiHeavy.cpp create mode 100644 PanasonicCPK.cpp create mode 100644 Sharp.cpp diff --git a/Carrier.cpp b/Carrier.cpp new file mode 100644 index 0000000..c104030 --- /dev/null +++ b/Carrier.cpp @@ -0,0 +1,145 @@ +#include + + +#define BYTETOBINARYPATTERN "%d%d%d%d%d%d%d%d" +#define BYTETOBINARY(byte) \ + (byte & 0x80 ? 1 : 0), \ + (byte & 0x40 ? 1 : 0), \ + (byte & 0x20 ? 1 : 0), \ + (byte & 0x10 ? 1 : 0), \ + (byte & 0x08 ? 1 : 0), \ + (byte & 0x04 ? 1 : 0), \ + (byte & 0x02 ? 1 : 0), \ + (byte & 0x01 ? 1 : 0) + + +byte bitReverse(byte x) +{ + // 01010101 | 10101010 + x = ((x >> 1) & 0x55) | ((x << 1) & 0xaa); + // 00110011 | 11001100 + x = ((x >> 2) & 0x33) | ((x << 2) & 0xcc); + // 00001111 | 11110000 + x = ((x >> 4) & 0x0f) | ((x << 4) & 0xf0); + return x; +} + +bool decodeCarrier(byte *bytes, int byteCount) +{ + // If this looks like a Carrier code... + if ( byteCount == 18 && bytes[0] == 0x4F && bytes[1] == 0xB0 && (memcmp(bytes, bytes+9, 9) == 0)) { + Serial.println("Looks like a Carrier protocol"); + + // Check if the checksum matches + byte checksum = 0; + + checksum = bitReverse(bytes[0]) + + bitReverse(bytes[1]) + + bitReverse(bytes[2]) + + bitReverse(bytes[3]) + + bitReverse(bytes[4]) + + bitReverse(bytes[5]) + + bitReverse(bytes[6]) + + bitReverse(bytes[7]); + + switch (bytes[6] & 0x0F) { + case 0x00: + Serial.println("FAN: AUTO"); + break; + case 0x02: + Serial.println("FAN: 1"); + break; + case 0x06: + Serial.println("FAN: 2"); + break; + case 0x01: + Serial.println("FAN: 3"); + break; + case 0x05: + Serial.println("FAN: 4"); + break; + case 0x03: + Serial.println("FAN: 5"); + break; + } + + switch (bytes[6] & 0xF0) { + case 0xE0: + Serial.println("MODE: OFF"); + break; + case 0x00: + Serial.println("MODE: AUTO"); + checksum += 0x02; + switch (bytes[6] & 0x0F) { + case 0x02: // FAN1 + case 0x03: // FAN5 + case 0x06: // FAN2 + checksum += 0x80; + break; + } + break; + case 0x80: + Serial.println("MODE: COOL"); + break; + case 0x40: + Serial.println("MODE: DRY"); + checksum += 0x02; + break; + case 0xC0: + Serial.println("MODE: HEAT"); + switch (bytes[6] & 0x0F) { + case 0x05: // FAN4 + case 0x06: // FAN2 + checksum += 0xC0; + break; + } + break; + case 0x20: + Serial.println("MODE: FAN"); + checksum += 0x02; + switch (bytes[6] & 0x0F) { + case 0x02: // FAN1 + case 0x03: // FAN5 + case 0x06: // FAN2 + checksum += 0x80; + break; + } + break; + } + + checksum = bitReverse(checksum); + + const byte temperatures[] = { 17, 25, 21, 29, 19, 27, 23, 00, 18, 26, 22, 30, 20, 28, 24 }; + + + Serial.print("Temperature: "); + Serial.println(temperatures[bytes[5]]); + + char bin1[9]; + char bin2[9]; + char bin3[9]; + + snprintf(bin1, sizeof(bin1), BYTETOBINARYPATTERN, BYTETOBINARY(checksum)); + snprintf(bin2, sizeof(bin2), BYTETOBINARYPATTERN, BYTETOBINARY(bytes[8])); + snprintf(bin3, sizeof(bin3), BYTETOBINARYPATTERN, BYTETOBINARY(bytes[6])); + + + Serial.print("ModeFan "); + Serial.println(bin3); + + + Serial.print("Checksum "); + Serial.print(bin1); + + if (checksum == bytes[8]) { + Serial.println(" matches"); + } else { + Serial.println(" does not match real"); + Serial.print("checksum "); + Serial.println(bin2); + } + return true; + } + + return false; +} diff --git a/Daikin.cpp b/Daikin.cpp new file mode 100644 index 0000000..4a9e9d8 --- /dev/null +++ b/Daikin.cpp @@ -0,0 +1,104 @@ +#include + +bool decodeDaikin(byte *bytes, int byteCount) +{ + // If this looks like a Daikin code... + if ( byteCount == 35 && bytes[0] == 0x11 && bytes[1] == 0xDA && bytes[2] == 0x27 ) { + Serial.println("Looks like a Daikin protocol"); + + // Power mode + switch (bytes[21] & 0x01) { + case 0x00: + Serial.println("POWER OFF"); + break; + case 0x01: + Serial.println("POWER ON"); + break; + } + + // Operating mode + switch (bytes[21] & 0x70) { + case 0x00: + Serial.println("MODE AUTO"); + break; + case 0x40: + Serial.println("MODE HEAT"); + break; + case 0x30: + Serial.println("MODE COOL"); + break; + case 0x20: + Serial.println("MODE DRY"); + break; + case 0x60: + Serial.println("MODE FAN"); + break; + } + + // Temperature + Serial.print("Temperature: "); + Serial.println(bytes[22] / 2); + + // Fan speed + switch (bytes[24] & 0xF0) { + case 0xA0: + Serial.println("FAN: AUTO"); + break; + case 0x30: + Serial.println("FAN: 1"); + break; + case 0x40: + Serial.println("FAN: 2"); + break; + case 0x50: + Serial.println("FAN: 3"); + break; + case 0x60: + Serial.println("FAN: 4"); + break; + case 0x70: + Serial.println("FAN: 5"); + break; + } + + // Check if the checksum matches + byte checksum = 0x00; + + for (byte i = 0; i < 7; i++) { + checksum += bytes[i]; + } + + if ( bytes[7] == checksum ) { + Serial.println("Checksum 1 matches"); + } else { + Serial.println("Checksum 1 does not match"); + } + + checksum = 0x00; + + for (byte i = 8; i < 15; i++) { + checksum += bytes[i]; + } + + if ( bytes[15] == checksum ) { + Serial.println("Checksum 2 matches"); + } else { + Serial.println("Checksum 2 does not match"); + } + + checksum = 0x00; + + for (byte i = 16; i < 34; i++) { + checksum += bytes[i]; + } + + if ( bytes[34] == checksum ) { + Serial.println("Checksum 3 matches"); + } else { + Serial.println("Checksum 3 does not match"); + } + return true; + } + + return false; +} diff --git a/MitsubishiElectric.cpp b/MitsubishiElectric.cpp new file mode 100644 index 0000000..5120a4d --- /dev/null +++ b/MitsubishiElectric.cpp @@ -0,0 +1,174 @@ +#include + +bool decodeMitsubishiElectric(byte *bytes, int byteCount) +{ + // If this looks like a Mitsubishi FD-25 or FE code... + if ( byteCount == 36 && bytes[0] == 0x23 && (memcmp(bytes, bytes+18, 17) == 0)) { + Serial.println("Looks like a Mitsubishi FD / FE series protocol"); + + // Check if the checksum matches + byte checksum = 0; + + for (int i=0; i<17; i++) { + checksum += bytes[i]; + } + + if (checksum == bytes[17]) { + Serial.println("Checksum matches"); + } else { + Serial.println("Checksum does not match"); + } + + // Power mode + switch (bytes[5]) { + case 0x00: + Serial.println("POWER OFF"); + break; + case 0x20: + Serial.println("POWER ON"); + break; + default: + Serial.println("POWER unknown"); + break; + } + + // Operating mode + switch (bytes[6] & 0x38) { // 0b00111000 + case 0x38: + Serial.println("MODE FAN"); + break; + case 0x20: + Serial.println("MODE AUTO"); + break; + case 0x08: + if (bytes[15] == 0x20) { + Serial.println("MODE MAINTENANCE HEAT (FE only)"); + } else { + Serial.println("MODE HEAT"); + } + break; + case 0x18: + Serial.println("MODE COOL"); + break; + case 0x10: + Serial.println("MODE DRY"); + break; + default: + Serial.println("MODE unknown"); + break; + } + + // I-See + switch (bytes[6] & 0x40) { // 0b01000000 + case 0x40: + Serial.println("I-See: ON"); + break; + case 0x00: + Serial.println("I-See: OFF"); + break; + } + + // Plasma + switch (bytes[15] & 0x40) { // 0b01000000 + case 0x40: + Serial.println("Plasma: ON"); + break; + case 0x00: + Serial.println("Plasma: OFF"); + break; + } + + // Temperature + Serial.print("Temperature: "); + if (bytes[7] == 0x00) { + Serial.println("10"); + } else { + Serial.println(bytes[7] + 16); + } + + // Fan speed + switch (bytes[9] & 0x07) { // 0b00000111 + case 0x00: + Serial.println("FAN AUTO"); + break; + case 0x01: + Serial.println("FAN 1"); + break; + case 0x02: + Serial.println("FAN 2"); + break; + case 0x03: + Serial.println("FAN 3"); + break; + case 0x04: + Serial.println("FAN 4"); + break; + default: + Serial.println("FAN unknown"); + break; + } + + // Vertical air direction + switch (bytes[9] & 0xF8) { // 0b11111000 + case 0x40: // 0b01000 + Serial.println("VANE: AUTO1?"); + break; + case 0x48: // 0b01001 + Serial.println("VANE: UP"); + break; + case 0x50: // 0b01010 + Serial.println("VANE: UP-1"); + break; + case 0x58: // 0b01011 + Serial.println("VANE: UP-2"); + break; + case 0x60: // 0b01100 + Serial.println("VANE: UP-3"); + break; + case 0x68: // 0b01101 + Serial.println("VANE: DOWN"); + break; + case 0x78: // 0b01111 + Serial.println("VANE: SWING"); + break; + case 0x80: // 0b10000 + Serial.println("VANE: AUTO2?"); + break; + case 0xB8: // 0b10111 + Serial.println("VANE: AUTO3?"); + break; + default: + Serial.println("VANE: unknown"); + break; + } + + // Horizontal air direction + switch (bytes[8] & 0xF0) { // 0b11110000 + case 0x10: + Serial.println("WIDE VANE: LEFT"); + break; + case 0x20: + Serial.println("WIDE VANE: MIDDLE LEFT"); + break; + case 0x30: + Serial.println("WIDE VANE: MIDDLE"); + break; + case 0x40: + Serial.println("WIDE VANE: MIDDLE RIGHT"); + break; + case 0x50: + Serial.println("WIDE VANE: RIGHT"); + break; + case 0xC0: + Serial.println("WIDE VANE: SWING"); + break; + default: + Serial.println("WIDE VANE: unknown"); + break; + } + + return true; + } + + return false; +} diff --git a/MitsubishiHeavy.cpp b/MitsubishiHeavy.cpp new file mode 100644 index 0000000..c4fc1bf --- /dev/null +++ b/MitsubishiHeavy.cpp @@ -0,0 +1,68 @@ +#include + +bool decodeMitsubishiHeavy(byte *bytes, int byteCount) +{ + // If this looks like a Mitsubishi Heavy... + if ( byteCount == 11 && bytes[0] == 0x52 && bytes[1] == 0xAE && bytes[2] == 0xC3 && bytes[3] == 0x26) { + Serial.println("Looks like a Mitsubishi Heavy protocol"); + + // Power mode + switch (bytes[9] & 0x08) { + case 0x00: + Serial.println("POWER ON"); + break; + case 0x08: + Serial.println("POWER OFF"); + break; + } + + // Operating mode + switch (bytes[9] & 0x07) { + case 0x07: + Serial.println("MODE AUTO"); + break; + case 0x03: + Serial.println("MODE HEAT"); + break; + case 0x06: + Serial.println("MODE COOL"); + break; + case 0x05: + Serial.println("MODE DRY"); + break; + case 0x01: + Serial.println("MODE FAN"); + break; + } + + // Temperature + Serial.print("Temperature: "); + Serial.println((~((bytes[9] & 0xF0) >> 4) & 0x0F) + 17); + + // Fan speed + switch (bytes[7] & 0xE0) { + case 0xE0: + Serial.println("FAN AUTO"); + break; + case 0xA0: + Serial.println("FAN 1"); + break; + case 0x80: + Serial.println("FAN 2"); + break; + case 0x60: + Serial.println("FAN 3"); + break; + case 0x20: + Serial.println("FAN HIGH SPEED"); + break; + case 0x00: + Serial.println("FAN SILENT"); + break; + } + + return true; + } + + return false; +} diff --git a/PanasonicCPK.cpp b/PanasonicCPK.cpp new file mode 100644 index 0000000..f084dbe --- /dev/null +++ b/PanasonicCPK.cpp @@ -0,0 +1,63 @@ +#include + +bool decodePanasonicCKP(byte *bytes, int byteCount) +{ + // If this looks like a Panasonic CKP code... + if ( byteCount == 16 && bytes[10] == 0x36 ) { + Serial.println("Looks like a Panasonic CKP protocol"); + + switch (bytes[2] & 0x07) + { + case 0x06: + Serial.println("AUTO"); + break; + case 0x04: + Serial.println("HEAT"); + break; + case 0x02: + Serial.println("COOL"); + break; + case 0x03: + Serial.println("DRY"); + break; + case 0x01: + Serial.println("FAN"); + break; + } + + if ((bytes[2] & 0x08) != 0x08) + { + Serial.println("POWER SWITCH"); + } + + switch (bytes[0] & 0xF0) + { + case 0xF0: + Serial.println("FAN AUTO"); + break; + case 0x20: + Serial.println("FAN 1"); + break; + case 0x30: + Serial.println("FAN 2"); + break; + case 0x40: + Serial.println("FAN 3"); + break; + case 0x50: + Serial.println("FAN 4"); + break; + case 0x60: + Serial.println("FAN 5"); + break; + } + + Serial.print("Temperature: "); + Serial.println((bytes[0] & 0x0F) + 15); + + return true; + } + + return false; +} + diff --git a/Sharp.cpp b/Sharp.cpp new file mode 100644 index 0000000..fa2975e --- /dev/null +++ b/Sharp.cpp @@ -0,0 +1,83 @@ +#include + +bool decodeSharp(byte *bytes, int byteCount) +{ + // If this looks like a Sharp code... + if ( byteCount == 13 && bytes[0] == 0xAA && bytes[1] == 0x5A && bytes[2] == 0xCF ) { + Serial.println("Looks like a Sharp protocol"); + + // Power mode + switch (bytes[5]) { + case 0x21: + Serial.println("POWER OFF"); + break; + case 0x31: + Serial.println("POWER ON"); + break; + } + + // Operating mode + switch (bytes[6] & 0x0F) { + case 0x01: + if (bytes[4] == 0x00) { + Serial.println("MODE MAINTENANCE HEAT"); + } else { + Serial.println("MODE HEAT"); + } + break; + case 0x02: + Serial.println("MODE COOL"); + break; + case 0x03: + Serial.println("MODE DRY"); + break; + } + + // Temperature + Serial.print("Temperature: "); + if (bytes[4] == 0x00) { + Serial.println("10"); + } else { + Serial.println(bytes[4] + 17); + } + + switch (bytes[6] & 0xF0) { + case 0x20: + Serial.println("FAN: AUTO"); + break; + case 0x30: + Serial.println("FAN: 1"); + break; + case 0x50: + Serial.println("FAN: 2"); + break; + case 0x70: + Serial.println("FAN: 3"); + break; + } + + // Check if the checksum matches + byte checksum = 0x00; + + for (byte i = 0; i < 12; i++) { + checksum ^= bytes[i]; + } + + checksum ^= bytes[12] & 0x0F; + checksum ^= (checksum >> 4); + checksum &= 0x0F; + + Serial.print("Checksum '0x"); + Serial.print(checksum, HEX); + + if ( ((bytes[12] & 0xF0) >> 4) == checksum ) { + Serial.println("' matches"); + } else { + Serial.print(" does not match "); + Serial.println(((bytes[12] & 0xF0) >> 4), HEX); + } + return true; + } + + return false; +} diff --git a/rawirdecode.ino b/rawirdecode.ino index 08fae9f..1105905 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -1,3 +1,12 @@ +#include + +bool decodeMitsubishiElectric(byte *bytes, int byteCount); +bool decodeMitsubishiHeavy(byte *bytes, int byteCount); +bool decodeDaikin(byte *bytes, int byteCount); +bool decodeSharp(byte *bytes, int byteCount); +bool decodeCarrier(byte *bytes, int byteCount); +bool decodePanasonicCKP(byte *bytes, int byteCount); + /* Raw IR decoder sketch! This sketch/program uses the Arduno and a PNA4602 to @@ -33,69 +42,11 @@ // accurate timing uint16_t RESOLUTION=20; - -#define BYTETOBINARYPATTERN "%d%d%d%d%d%d%d%d" -#define BYTETOBINARY(byte) \ - (byte & 0x80 ? 1 : 0), \ - (byte & 0x40 ? 1 : 0), \ - (byte & 0x20 ? 1 : 0), \ - (byte & 0x10 ? 1 : 0), \ - (byte & 0x08 ? 1 : 0), \ - (byte & 0x04 ? 1 : 0), \ - (byte & 0x02 ? 1 : 0), \ - (byte & 0x01 ? 1 : 0) - // The thresholds for different symbols -// These should work with Panasonic (from DKE on), Fujitsu and Mitsubishi -// Anyway, adjust these to get reliable readings - -#define MARK_THRESHOLD_BIT_HEADER 2000 // Value between BIT MARK and HEADER MARK -#define SPACE_THRESHOLD_ZERO_ONE 800 // Value between ZERO SPACE and ONE SPACE -#define SPACE_THRESHOLD_ONE_HEADER 1300 // Value between ONE SPACE and HEADER SPACE -#define SPACE_THRESHOLD_HEADER_PAUSE 8000 // Value between HEADER SPACE and PAUSE SPACE (Panasonic/Midea only) - -// This set works on the Panasonic CKP -/* -#define MARK_THRESHOLD_BIT_HEADER 2000 // Value between BIT MARK and HEADER MARK -#define SPACE_THRESHOLD_ZERO_ONE 1800 // Value between ZERO SPACE and ONE SPACE -#define SPACE_THRESHOLD_ONE_HEADER 3200 // Value between ONE SPACE and HEADER SPACE -#define SPACE_THRESHOLD_HEADER_PAUSE 8000 // Value between HEADER SPACE and PAUSE SPACE (Panasonic/Midea only) -*/ - -// This set works on Carrier & Midea / Ultimate Pro Plus 13 FP - - -/* -Panasonic CKP timings: -PAUSE SPACE: 13520 -HEADER MARK: 3394 -HEADER SPACE: 3326 -BIT MARK: 812 -ZERO SPACE: 751 -ONE SPACE: 2449 - -Panasonic DKE, JNE and NKE timings: -PAUSE SPACE: 9700 -HEADER MARK: 3439 -HEADER SPACE: 1599 -BIT MARK: 380 -ZERO SPACE: 319 -ONE SPACE: 1197 - -Fujitsu Nocria timings: -HEADER MARK 3327 -HEADER SPACE 1519 -BIT MARK 364 -ZERO SPACE 277 -ONE SPACE 1104 - -Mitsubishi FD-25 timings: -HEADER MARK: 3450 -HEADER SPACE: 1700 -BIT MARK: 500 -ZERO SPACE: 350 -ONE SPACE: 1250 -*/ +int MARK_THRESHOLD_BIT_HEADER = 0; // Value between BIT MARK and HEADER MARK +int SPACE_THRESHOLD_ZERO_ONE = 0; // Value between ZERO SPACE and ONE SPACE +int SPACE_THRESHOLD_ONE_HEADER = 0; // Value between ONE SPACE and HEADER SPACE +int SPACE_THRESHOLD_HEADER_PAUSE = 0; // Value between HEADER SPACE and PAUSE SPACE (Panasonic/Midea only) uint32_t mark_header_avg = 0; @@ -115,38 +66,99 @@ uint16_t space_pause_cnt = 0; char symbols[500]; // decoded symbols uint16_t currentpulse = 0; // index for pulses we're storing +uint8_t modelChoice = 0; + +// Decoded bytes +byte byteCount = 0; +byte bytes[32]; + + void setup(void) { + Serial.begin(9600); delay(1000); - Serial.println(F("Ready to decode IR!\n\n")); + Serial.println(F("Select model to decode (this affects the IR signal timings detection):")); + Serial.println(F("* '1' for Panasonic DKE>, Mitsubishi, Fujitsu etc. codes")); + Serial.println(F("* '2' for Panasonic CKP, Midea etc. codes")); + Serial.println(F("* '3' for entering the bit sequence on the serial monitor (instead of the IR receiver)")); + Serial.println(); + Serial.print(F("Enter choice: ")); + + while (modelChoice == 0) { + int selection = Serial.read(); + + if ( selection != -1 ) { + Serial.print((char)selection); + + switch ((char)selection) { + case '1': + modelChoice = 1; + break; + case '2': + modelChoice = 2; + break; + case '3': + modelChoice = 3; + break; + } + } + } + + Serial.print(F("\n\nReady to decode IR for choice '")); + Serial.print(modelChoice); + Serial.println(F("'\n\n")); + + if (modelChoice == 1) { + MARK_THRESHOLD_BIT_HEADER = 2000; + SPACE_THRESHOLD_ZERO_ONE = 800; + SPACE_THRESHOLD_ONE_HEADER = 1500; + SPACE_THRESHOLD_HEADER_PAUSE = 8000; + } else if (modelChoice == 2) { + MARK_THRESHOLD_BIT_HEADER = 2000; + SPACE_THRESHOLD_ZERO_ONE = 1800; + SPACE_THRESHOLD_ONE_HEADER = 3200; + SPACE_THRESHOLD_HEADER_PAUSE = 8000; + } } void loop(void) { char incoming = 0; -mark_header_avg = 0; -mark_header_cnt = 0; -mark_bit_avg = 0; -mark_bit_cnt = 0; -space_zero_avg = 0; -space_zero_cnt = 0; -space_one_avg = 0; -space_one_cnt = 0; -space_header_avg = 0; -space_header_cnt = 0; -space_pause_avg = 0; -space_pause_cnt = 0; + memset(symbols, sizeof(symbols), 0); + memset(bytes, sizeof(bytes), 0); + + // Initialize the averages every time + mark_header_avg = 0; + mark_header_cnt = 0; + mark_bit_avg = 0; + mark_bit_cnt = 0; + space_zero_avg = 0; + space_zero_cnt = 0; + space_one_avg = 0; + space_one_cnt = 0; + space_header_avg = 0; + space_header_cnt = 0; + space_pause_avg = 0; + space_pause_cnt = 0; // Only Panasonic seems to use the pause space_pause_avg = 0; space_pause_cnt = 0; currentpulse=0; - receivepulses(); - printpulses(); + byteCount=0; + if (modelChoice != 3) { + receivePulses(); + } else { + while ((currentpulse = Serial.readBytesUntil('\n', symbols+1, sizeof(symbols)-1)) == 0) {} + currentpulse++; + } + + printPulses(); + decodeProtocols(); } -void receivepulses(void) { +void receivePulses(void) { uint16_t highpulse, lowpulse; // temporary storage timing while (currentpulse < sizeof(symbols)) @@ -217,33 +229,20 @@ void receivepulses(void) { } } -void printpulses(void) { +void printPulses(void) { int bitCount = 0; byte currentByte = 0; - byte byteCount = 0; - byte bytes[32]; - Serial.print("Number of symbols: "); + Serial.print("\nNumber of symbols: "); Serial.println(currentpulse); + // Print the symbols (0, 1, H, h, W) Serial.println("Symbols:"); Serial.println(symbols+1); - Serial.println("Timings (in us): "); - Serial.print("PAUSE SPACE: "); - Serial.println(space_pause_avg); - Serial.print("HEADER MARK: "); - Serial.println(mark_header_avg); - Serial.print("HEADER SPACE: "); - Serial.println(space_header_avg); - Serial.print("BIT MARK: "); - Serial.println(mark_bit_avg); - Serial.print("ZERO SPACE: "); - Serial.println(space_zero_avg); - Serial.print("ONE SPACE: "); - Serial.println(space_one_avg); - + // Print the decoded bytes + Serial.println("Bytes:"); // Decode the string of bits to a byte array for (int i = 0; i < currentpulse; i++) { @@ -277,468 +276,33 @@ void printpulses(void) { } Serial.println(); - // If this looks like a Mitsubishi FD-25 or FE code... - if ( byteCount == 36 && bytes[0] == 0x23 && (memcmp(bytes, bytes+18, 17) == 0)) { - Serial.println("Looks like a Mitsubishi FD / FE series protocol"); - - // Check if the checksum matches - byte checksum = 0; - - for (int i=0; i<17; i++) { - checksum += bytes[i]; - } - - if (checksum == bytes[17]) { - Serial.println("Checksum matches"); - } else { - Serial.println("Checksum does not match"); - } - - // Power mode - switch (bytes[5]) { - case 0x00: - Serial.println("POWER OFF"); - break; - case 0x20: - Serial.println("POWER ON"); - break; - default: - Serial.println("POWER unknown"); - break; - } - - // Operating mode - switch (bytes[6] & 0x38) { // 0b00111000 - case 0x38: - Serial.println("MODE FAN"); - break; - case 0x20: - Serial.println("MODE AUTO"); - break; - case 0x08: - if (bytes[15] == 0x20) { - Serial.println("MODE MAINTENANCE HEAT (FE only)"); - } else { - Serial.println("MODE HEAT"); - } - break; - case 0x18: - Serial.println("MODE COOL"); - break; - case 0x10: - Serial.println("MODE DRY"); - break; - default: - Serial.println("MODE unknown"); - break; - } - - // I-See - switch (bytes[6] & 0x40) { // 0b01000000 - case 0x40: - Serial.println("I-See: ON"); - break; - case 0x00: - Serial.println("I-See: OFF"); - break; - } - - // Plasma - switch (bytes[15] & 0x40) { // 0b01000000 - case 0x40: - Serial.println("Plasma: ON"); - break; - case 0x00: - Serial.println("Plasma: OFF"); - break; - } - - // Temperature - Serial.print("Temperature: "); - if (bytes[7] == 0x00) { - Serial.println("10"); - } else { - Serial.println(bytes[7] + 16); - } - - // Fan speed - switch (bytes[9] & 0x07) { // 0b00000111 - case 0x00: - Serial.println("FAN AUTO"); - break; - case 0x01: - Serial.println("FAN 1"); - break; - case 0x02: - Serial.println("FAN 2"); - break; - case 0x03: - Serial.println("FAN 3"); - break; - case 0x04: - Serial.println("FAN 4"); - break; - default: - Serial.println("FAN unknown"); - break; - } - - // Vertical air direction - switch (bytes[9] & 0xF8) { // 0b11111000 - case 0x40: // 0b01000 - Serial.println("VANE: AUTO1?"); - break; - case 0x48: // 0b01001 - Serial.println("VANE: UP"); - break; - case 0x50: // 0b01010 - Serial.println("VANE: UP-1"); - break; - case 0x58: // 0b01011 - Serial.println("VANE: UP-2"); - break; - case 0x60: // 0b01100 - Serial.println("VANE: UP-3"); - break; - case 0x68: // 0b01101 - Serial.println("VANE: DOWN"); - break; - case 0x78: // 0b01111 - Serial.println("VANE: SWING"); - break; - case 0x80: // 0b10000 - Serial.println("VANE: AUTO2?"); - break; - case 0xB8: // 0b10111 - Serial.println("VANE: AUTO3?"); - break; - default: - Serial.println("VANE: unknown"); - break; - } - - // Horizontal air direction - switch (bytes[8] & 0xF0) { // 0b11110000 - case 0x10: - Serial.println("WIDE VANE: LEFT"); - break; - case 0x20: - Serial.println("WIDE VANE: MIDDLE LEFT"); - break; - case 0x30: - Serial.println("WIDE VANE: MIDDLE"); - break; - case 0x40: - Serial.println("WIDE VANE: MIDDLE RIGHT"); - break; - case 0x50: - Serial.println("WIDE VANE: RIGHT"); - break; - case 0xC0: - Serial.println("WIDE VANE: SWING"); - break; - default: - Serial.println("WIDE VANE: unknown"); - break; - } - } - - // If this looks like a Carrier code... - if ( byteCount == 18 && bytes[0] == 0x4F && bytes[1] == 0xB0 && (memcmp(bytes, bytes+9, 9) == 0)) { - Serial.println("Looks like a Carrier protocol"); - - // Check if the checksum matches - byte checksum = 0; - - checksum = bitReverse(bytes[0]) + - bitReverse(bytes[1]) + - bitReverse(bytes[2]) + - bitReverse(bytes[3]) + - bitReverse(bytes[4]) + - bitReverse(bytes[5]) + - bitReverse(bytes[6]) + - bitReverse(bytes[7]); - - switch (bytes[6] & 0x0F) { - case 0x00: - Serial.println("FAN: AUTO"); - break; - case 0x02: - Serial.println("FAN: 1"); - break; - case 0x06: - Serial.println("FAN: 2"); - break; - case 0x01: - Serial.println("FAN: 3"); - break; - case 0x05: - Serial.println("FAN: 4"); - break; - case 0x03: - Serial.println("FAN: 5"); - break; - } - - switch (bytes[6] & 0xF0) { - case 0xE0: - Serial.println("MODE: OFF"); - break; - case 0x00: - Serial.println("MODE: AUTO"); - checksum += 0x02; - switch (bytes[6] & 0x0F) { - case 0x02: // FAN1 - case 0x03: // FAN5 - case 0x06: // FAN2 - checksum += 0x80; - break; - } - break; - case 0x80: - Serial.println("MODE: COOL"); - break; - case 0x40: - Serial.println("MODE: DRY"); - checksum += 0x02; - break; - case 0xC0: - Serial.println("MODE: HEAT"); - switch (bytes[6] & 0x0F) { - case 0x05: // FAN4 - case 0x06: // FAN2 - checksum += 0xC0; - break; - } - break; - case 0x20: - Serial.println("MODE: FAN"); - checksum += 0x02; - switch (bytes[6] & 0x0F) { - case 0x02: // FAN1 - case 0x03: // FAN5 - case 0x06: // FAN2 - checksum += 0x80; - break; - } - break; - } - - checksum = bitReverse(checksum); - - const byte temperatures[] = { 17, 25, 21, 29, 19, 27, 23, 00, 18, 26, 22, 30, 20, 28, 24 }; - - - Serial.print("Temperature: "); - Serial.println(temperatures[bytes[5]]); - - char bin1[9]; - char bin2[9]; - char bin3[9]; - - snprintf(bin1, sizeof(bin1), BYTETOBINARYPATTERN, BYTETOBINARY(checksum)); - snprintf(bin2, sizeof(bin2), BYTETOBINARYPATTERN, BYTETOBINARY(bytes[8])); - snprintf(bin3, sizeof(bin3), BYTETOBINARYPATTERN, BYTETOBINARY(bytes[6])); - - - Serial.print("ModeFan "); - Serial.println(bin3); - - - Serial.print("Checksum "); - Serial.print(bin1); - - if (checksum == bytes[8]) { - Serial.println(" matches"); - } else { - Serial.println(" does not match real"); - Serial.print("checksum "); - Serial.println(bin2); - } - - } - - // If this looks like a Sharp code... - if ( byteCount == 13 && bytes[0] == 0xAA && bytes[1] == 0x5A && bytes[2] == 0xCF ) { - Serial.println("Looks like a Sharp protocol"); - - // Power mode - switch (bytes[5]) { - case 0x21: - Serial.println("POWER OFF"); - break; - case 0x31: - Serial.println("POWER ON"); - break; - } - - // Operating mode - switch (bytes[6] & 0x0F) { - case 0x01: - if (bytes[4] == 0x00) { - Serial.println("MODE MAINTENANCE HEAT"); - } else { - Serial.println("MODE HEAT"); - } - break; - case 0x02: - Serial.println("MODE COOL"); - break; - case 0x03: - Serial.println("MODE DRY"); - break; - } - - // Temperature - Serial.print("Temperature: "); - if (bytes[4] == 0x00) { - Serial.println("10"); - } else { - Serial.println(bytes[4] + 17); - } - - switch (bytes[6] & 0xF0) { - case 0x20: - Serial.println("FAN: AUTO"); - break; - case 0x30: - Serial.println("FAN: 1"); - break; - case 0x50: - Serial.println("FAN: 2"); - break; - case 0x70: - Serial.println("FAN: 3"); - break; - } - - // Check if the checksum matches - byte checksum = 0x00; - - for (byte i = 0; i < 12; i++) { - checksum ^= bytes[i]; - } - - checksum ^= bytes[12] & 0x0F; - checksum ^= (checksum >> 4); - checksum &= 0x0F; - - Serial.print("Checksum '0x"); - Serial.print(checksum, HEX); - - if ( ((bytes[12] & 0xF0) >> 4) == checksum ) { - Serial.println("' matches"); - } else { - Serial.print(" does not match "); - Serial.println(((bytes[12] & 0xF0) >> 4), HEX); - } - } - - // If this looks like a Daikin code... - if ( byteCount == 35 && bytes[0] == 0x11 && bytes[1] == 0xDA && bytes[2] == 0x27 ) { - Serial.println("Looks like a Daikin protocol"); - - // Power mode - switch (bytes[21] & 0x01) { - case 0x00: - Serial.println("POWER OFF"); - break; - case 0x01: - Serial.println("POWER ON"); - break; - } - - // Operating mode - switch (bytes[21] & 0x70) { - case 0x00: - Serial.println("MODE AUTO"); - break; - case 0x40: - Serial.println("MODE HEAT"); - break; - case 0x30: - Serial.println("MODE COOL"); - break; - case 0x20: - Serial.println("MODE DRY"); - break; - case 0x60: - Serial.println("MODE FAN"); - break; - } - - // Temperature - Serial.print("Temperature: "); - Serial.println(bytes[22] / 2); - - // Fan speed - switch (bytes[24] & 0xF0) { - case 0xA0: - Serial.println("FAN: AUTO"); - break; - case 0x30: - Serial.println("FAN: 1"); - break; - case 0x40: - Serial.println("FAN: 2"); - break; - case 0x50: - Serial.println("FAN: 3"); - break; - case 0x60: - Serial.println("FAN: 4"); - break; - case 0x70: - Serial.println("FAN: 5"); - break; - } - - // Check if the checksum matches - byte checksum = 0x00; - - for (byte i = 0; i < 7; i++) { - checksum += bytes[i]; - } - - if ( bytes[7] == checksum ) { - Serial.println("Checksum 1 matches"); - } else { - Serial.println("Checksum 1 does not match"); - } - - checksum = 0x00; - - for (byte i = 8; i < 15; i++) { - checksum += bytes[i]; - } - - if ( bytes[15] == checksum ) { - Serial.println("Checksum 2 matches"); - } else { - Serial.println("Checksum 2 does not match"); - } - - checksum = 0x00; - - for (byte i = 16; i < 34; i++) { - checksum += bytes[i]; - } - - if ( bytes[34] == checksum ) { - Serial.println("Checksum 3 matches"); - } else { - Serial.println("Checksum 3 does not match"); - } - } + // Print the timing constants + Serial.println("Timings (in us): "); + Serial.print("PAUSE SPACE: "); + Serial.println(space_pause_avg); + Serial.print("HEADER MARK: "); + Serial.println(mark_header_avg); + Serial.print("HEADER SPACE: "); + Serial.println(space_header_avg); + Serial.print("BIT MARK: "); + Serial.println(mark_bit_avg); + Serial.print("ZERO SPACE: "); + Serial.println(space_zero_avg); + Serial.print("ONE SPACE: "); + Serial.println(space_one_avg); } -byte bitReverse(byte x) +void decodeProtocols() { - // 01010101 | 10101010 - x = ((x >> 1) & 0x55) | ((x << 1) & 0xaa); - // 00110011 | 11001100 - x = ((x >> 2) & 0x33) | ((x << 2) & 0xcc); - // 00001111 | 11110000 - x = ((x >> 4) & 0x0f) | ((x << 4) & 0xf0); - return x; + Serial.println("Decoding known protocols..."); + + if ( ! (decodeMitsubishiElectric(bytes, byteCount) || + decodeMitsubishiHeavy(bytes, byteCount) || + decodeSharp(bytes, byteCount) || + decodeDaikin(bytes, byteCount) || + decodeCarrier(bytes, byteCount) || + decodePanasonicCKP(bytes, byteCount) ) ) + { + Serial.println(F("Unknown protocol")); + } } From c3f12f5fd68a3f740e5b2a438b6a42d8d70969dc Mon Sep 17 00:00:00 2001 From: ToniA Date: Mon, 4 Jan 2016 18:20:12 +0200 Subject: [PATCH 14/94] Mitsubishi Heavy support, fixed memset arguments --- rawirdecode.ino | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/rawirdecode.ino b/rawirdecode.ino index 1105905..e37f359 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -78,9 +78,10 @@ void setup(void) { Serial.begin(9600); delay(1000); Serial.println(F("Select model to decode (this affects the IR signal timings detection):")); - Serial.println(F("* '1' for Panasonic DKE>, Mitsubishi, Fujitsu etc. codes")); + Serial.println(F("* '1' for Panasonic DKE>, Mitsubishi Electric, Fujitsu etc. codes")); Serial.println(F("* '2' for Panasonic CKP, Midea etc. codes")); - Serial.println(F("* '3' for entering the bit sequence on the serial monitor (instead of the IR receiver)")); + Serial.println(F("* '3' for Mitsubishi Heavy etc. codes")); + Serial.println(F("* '9' for entering the bit sequence on the serial monitor (instead of the IR receiver)")); Serial.println(); Serial.print(F("Enter choice: ")); @@ -100,7 +101,10 @@ void setup(void) { case '3': modelChoice = 3; break; - } + case '9': + modelChoice = 9; + break; + } } } @@ -118,14 +122,19 @@ void setup(void) { SPACE_THRESHOLD_ZERO_ONE = 1800; SPACE_THRESHOLD_ONE_HEADER = 3200; SPACE_THRESHOLD_HEADER_PAUSE = 8000; + } else if (modelChoice == 3) { + MARK_THRESHOLD_BIT_HEADER = 2000; + SPACE_THRESHOLD_ZERO_ONE = 800; + SPACE_THRESHOLD_ONE_HEADER = 1400; + SPACE_THRESHOLD_HEADER_PAUSE = 8000; } } void loop(void) { char incoming = 0; - memset(symbols, sizeof(symbols), 0); - memset(bytes, sizeof(bytes), 0); + memset(symbols, 0, sizeof(symbols)); + memset(bytes, 0, sizeof(bytes)); // Initialize the averages every time mark_header_avg = 0; @@ -147,7 +156,7 @@ void loop(void) { currentpulse=0; byteCount=0; - if (modelChoice != 3) { + if (modelChoice != 9) { receivePulses(); } else { while ((currentpulse = Serial.readBytesUntil('\n', symbols+1, sizeof(symbols)-1)) == 0) {} From f8097127280488f34a95089a2c89dd10262b9c03 Mon Sep 17 00:00:00 2001 From: Sateetje Date: Tue, 5 Jan 2016 09:30:02 +0100 Subject: [PATCH 15/94] Update MitsubishiHeavy.cpp Updated Mode Fan --- MitsubishiHeavy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MitsubishiHeavy.cpp b/MitsubishiHeavy.cpp index c4fc1bf..3a9c1e1 100644 --- a/MitsubishiHeavy.cpp +++ b/MitsubishiHeavy.cpp @@ -30,7 +30,7 @@ bool decodeMitsubishiHeavy(byte *bytes, int byteCount) case 0x05: Serial.println("MODE DRY"); break; - case 0x01: + case 0x04: Serial.println("MODE FAN"); break; } From b7f316edb4e0be8ceed95e2e731fba995480f0ab Mon Sep 17 00:00:00 2001 From: ToniA Date: Tue, 5 Jan 2016 11:06:08 +0200 Subject: [PATCH 16/94] Reduce RAM footprint to fit into ATMega328 devices --- Carrier.cpp | 38 +++++++++---------- Daikin.cpp | 42 ++++++++++----------- MitsubishiElectric.cpp | 84 +++++++++++++++++++++--------------------- MitsubishiHeavy.cpp | 30 +++++++-------- PanasonicCPK.cpp | 28 +++++++------- Sharp.cpp | 32 ++++++++-------- rawirdecode.ino | 69 +++++++++++++++++----------------- 7 files changed, 161 insertions(+), 162 deletions(-) diff --git a/Carrier.cpp b/Carrier.cpp index c104030..f8508ac 100644 --- a/Carrier.cpp +++ b/Carrier.cpp @@ -28,7 +28,7 @@ bool decodeCarrier(byte *bytes, int byteCount) { // If this looks like a Carrier code... if ( byteCount == 18 && bytes[0] == 0x4F && bytes[1] == 0xB0 && (memcmp(bytes, bytes+9, 9) == 0)) { - Serial.println("Looks like a Carrier protocol"); + Serial.println(F("Looks like a Carrier protocol")); // Check if the checksum matches byte checksum = 0; @@ -44,31 +44,31 @@ bool decodeCarrier(byte *bytes, int byteCount) switch (bytes[6] & 0x0F) { case 0x00: - Serial.println("FAN: AUTO"); + Serial.println(F("FAN: AUTO")); break; case 0x02: - Serial.println("FAN: 1"); + Serial.println(F("FAN: 1")); break; case 0x06: - Serial.println("FAN: 2"); + Serial.println(F("FAN: 2")); break; case 0x01: - Serial.println("FAN: 3"); + Serial.println(F("FAN: 3")); break; case 0x05: - Serial.println("FAN: 4"); + Serial.println(F("FAN: 4")); break; case 0x03: - Serial.println("FAN: 5"); + Serial.println(F("FAN: 5")); break; } switch (bytes[6] & 0xF0) { case 0xE0: - Serial.println("MODE: OFF"); + Serial.println(F("MODE: OFF")); break; case 0x00: - Serial.println("MODE: AUTO"); + Serial.println(F("MODE: AUTO")); checksum += 0x02; switch (bytes[6] & 0x0F) { case 0x02: // FAN1 @@ -79,14 +79,14 @@ bool decodeCarrier(byte *bytes, int byteCount) } break; case 0x80: - Serial.println("MODE: COOL"); + Serial.println(F("MODE: COOL")); break; case 0x40: - Serial.println("MODE: DRY"); + Serial.println(F("MODE: DRY")); checksum += 0x02; break; case 0xC0: - Serial.println("MODE: HEAT"); + Serial.println(F("MODE: HEAT")); switch (bytes[6] & 0x0F) { case 0x05: // FAN4 case 0x06: // FAN2 @@ -95,7 +95,7 @@ bool decodeCarrier(byte *bytes, int byteCount) } break; case 0x20: - Serial.println("MODE: FAN"); + Serial.println(F("MODE: FAN")); checksum += 0x02; switch (bytes[6] & 0x0F) { case 0x02: // FAN1 @@ -112,7 +112,7 @@ bool decodeCarrier(byte *bytes, int byteCount) const byte temperatures[] = { 17, 25, 21, 29, 19, 27, 23, 00, 18, 26, 22, 30, 20, 28, 24 }; - Serial.print("Temperature: "); + Serial.print(F("Temperature: ")); Serial.println(temperatures[bytes[5]]); char bin1[9]; @@ -124,18 +124,18 @@ bool decodeCarrier(byte *bytes, int byteCount) snprintf(bin3, sizeof(bin3), BYTETOBINARYPATTERN, BYTETOBINARY(bytes[6])); - Serial.print("ModeFan "); + Serial.print(F("ModeFan ")); Serial.println(bin3); - Serial.print("Checksum "); + Serial.print(F("Checksum ")); Serial.print(bin1); if (checksum == bytes[8]) { - Serial.println(" matches"); + Serial.println(F(" matches")); } else { - Serial.println(" does not match real"); - Serial.print("checksum "); + Serial.println(F(" does not match real")); + Serial.print(F("checksum ")); Serial.println(bin2); } return true; diff --git a/Daikin.cpp b/Daikin.cpp index 4a9e9d8..08d4760 100644 --- a/Daikin.cpp +++ b/Daikin.cpp @@ -4,60 +4,60 @@ bool decodeDaikin(byte *bytes, int byteCount) { // If this looks like a Daikin code... if ( byteCount == 35 && bytes[0] == 0x11 && bytes[1] == 0xDA && bytes[2] == 0x27 ) { - Serial.println("Looks like a Daikin protocol"); + Serial.println(F("Looks like a Daikin protocol")); // Power mode switch (bytes[21] & 0x01) { case 0x00: - Serial.println("POWER OFF"); + Serial.println(F("POWER OFF")); break; case 0x01: - Serial.println("POWER ON"); + Serial.println(F("POWER ON")); break; } // Operating mode switch (bytes[21] & 0x70) { case 0x00: - Serial.println("MODE AUTO"); + Serial.println(F("MODE AUTO")); break; case 0x40: - Serial.println("MODE HEAT"); + Serial.println(F("MODE HEAT")); break; case 0x30: - Serial.println("MODE COOL"); + Serial.println(F("MODE COOL")); break; case 0x20: - Serial.println("MODE DRY"); + Serial.println(F("MODE DRY")); break; case 0x60: - Serial.println("MODE FAN"); + Serial.println(F("MODE FAN")); break; } // Temperature - Serial.print("Temperature: "); + Serial.print(F("Temperature: ")); Serial.println(bytes[22] / 2); // Fan speed switch (bytes[24] & 0xF0) { case 0xA0: - Serial.println("FAN: AUTO"); + Serial.println(F("FAN: AUTO")); break; case 0x30: - Serial.println("FAN: 1"); + Serial.println(F("FAN: 1")); break; case 0x40: - Serial.println("FAN: 2"); + Serial.println(F("FAN: 2")); break; case 0x50: - Serial.println("FAN: 3"); + Serial.println(F("FAN: 3")); break; case 0x60: - Serial.println("FAN: 4"); + Serial.println(F("FAN: 4")); break; case 0x70: - Serial.println("FAN: 5"); + Serial.println(F("FAN: 5")); break; } @@ -69,9 +69,9 @@ bool decodeDaikin(byte *bytes, int byteCount) } if ( bytes[7] == checksum ) { - Serial.println("Checksum 1 matches"); + Serial.println(F("Checksum 1 matches")); } else { - Serial.println("Checksum 1 does not match"); + Serial.println(F("Checksum 1 does not match")); } checksum = 0x00; @@ -81,9 +81,9 @@ bool decodeDaikin(byte *bytes, int byteCount) } if ( bytes[15] == checksum ) { - Serial.println("Checksum 2 matches"); + Serial.println(F("Checksum 2 matches")); } else { - Serial.println("Checksum 2 does not match"); + Serial.println(F("Checksum 2 does not match")); } checksum = 0x00; @@ -93,9 +93,9 @@ bool decodeDaikin(byte *bytes, int byteCount) } if ( bytes[34] == checksum ) { - Serial.println("Checksum 3 matches"); + Serial.println(F("Checksum 3 matches")); } else { - Serial.println("Checksum 3 does not match"); + Serial.println(F("Checksum 3 does not match")); } return true; } diff --git a/MitsubishiElectric.cpp b/MitsubishiElectric.cpp index 5120a4d..85e15c3 100644 --- a/MitsubishiElectric.cpp +++ b/MitsubishiElectric.cpp @@ -4,7 +4,7 @@ bool decodeMitsubishiElectric(byte *bytes, int byteCount) { // If this looks like a Mitsubishi FD-25 or FE code... if ( byteCount == 36 && bytes[0] == 0x23 && (memcmp(bytes, bytes+18, 17) == 0)) { - Serial.println("Looks like a Mitsubishi FD / FE series protocol"); + Serial.println(F("Looks like a Mitsubishi FD / FE series protocol")); // Check if the checksum matches byte checksum = 0; @@ -14,74 +14,74 @@ bool decodeMitsubishiElectric(byte *bytes, int byteCount) } if (checksum == bytes[17]) { - Serial.println("Checksum matches"); + Serial.println(F("Checksum matches")); } else { - Serial.println("Checksum does not match"); + Serial.println(F("Checksum does not match")); } // Power mode switch (bytes[5]) { case 0x00: - Serial.println("POWER OFF"); + Serial.println(F("POWER OFF")); break; case 0x20: - Serial.println("POWER ON"); + Serial.println(F("POWER ON")); break; default: - Serial.println("POWER unknown"); + Serial.println(F("POWER unknown")); break; } // Operating mode switch (bytes[6] & 0x38) { // 0b00111000 case 0x38: - Serial.println("MODE FAN"); + Serial.println(F("MODE FAN")); break; case 0x20: - Serial.println("MODE AUTO"); + Serial.println(F("MODE AUTO")); break; case 0x08: if (bytes[15] == 0x20) { - Serial.println("MODE MAINTENANCE HEAT (FE only)"); + Serial.println(F("MODE MAINTENANCE HEAT (FE only)")); } else { - Serial.println("MODE HEAT"); + Serial.println(F("MODE HEAT")); } break; case 0x18: - Serial.println("MODE COOL"); + Serial.println(F("MODE COOL")); break; case 0x10: - Serial.println("MODE DRY"); + Serial.println(F("MODE DRY")); break; default: - Serial.println("MODE unknown"); + Serial.println(F("MODE unknown")); break; } // I-See switch (bytes[6] & 0x40) { // 0b01000000 case 0x40: - Serial.println("I-See: ON"); + Serial.println(F("I-See: ON")); break; case 0x00: - Serial.println("I-See: OFF"); + Serial.println(F("I-See: OFF")); break; } // Plasma switch (bytes[15] & 0x40) { // 0b01000000 case 0x40: - Serial.println("Plasma: ON"); + Serial.println(F("Plasma: ON")); break; case 0x00: - Serial.println("Plasma: OFF"); + Serial.println(F("Plasma: OFF")); break; } // Temperature - Serial.print("Temperature: "); + Serial.print(F("Temperature: ")); if (bytes[7] == 0x00) { - Serial.println("10"); + Serial.println(F("10")); } else { Serial.println(bytes[7] + 16); } @@ -89,81 +89,81 @@ bool decodeMitsubishiElectric(byte *bytes, int byteCount) // Fan speed switch (bytes[9] & 0x07) { // 0b00000111 case 0x00: - Serial.println("FAN AUTO"); + Serial.println(F("FAN AUTO")); break; case 0x01: - Serial.println("FAN 1"); + Serial.println(F("FAN 1")); break; case 0x02: - Serial.println("FAN 2"); + Serial.println(F("FAN 2")); break; case 0x03: - Serial.println("FAN 3"); + Serial.println(F("FAN 3")); break; case 0x04: - Serial.println("FAN 4"); + Serial.println(F("FAN 4")); break; default: - Serial.println("FAN unknown"); + Serial.println(F("FAN unknown")); break; } // Vertical air direction switch (bytes[9] & 0xF8) { // 0b11111000 case 0x40: // 0b01000 - Serial.println("VANE: AUTO1?"); + Serial.println(F("VANE: AUTO1?")); break; case 0x48: // 0b01001 - Serial.println("VANE: UP"); + Serial.println(F("VANE: UP")); break; case 0x50: // 0b01010 - Serial.println("VANE: UP-1"); + Serial.println(F("VANE: UP-1")); break; case 0x58: // 0b01011 - Serial.println("VANE: UP-2"); + Serial.println(F("VANE: UP-2")); break; case 0x60: // 0b01100 - Serial.println("VANE: UP-3"); + Serial.println(F("VANE: UP-3")); break; case 0x68: // 0b01101 - Serial.println("VANE: DOWN"); + Serial.println(F("VANE: DOWN")); break; case 0x78: // 0b01111 - Serial.println("VANE: SWING"); + Serial.println(F("VANE: SWING")); break; case 0x80: // 0b10000 - Serial.println("VANE: AUTO2?"); + Serial.println(F("VANE: AUTO2?")); break; case 0xB8: // 0b10111 - Serial.println("VANE: AUTO3?"); + Serial.println(F("VANE: AUTO3?")); break; default: - Serial.println("VANE: unknown"); + Serial.println(F("VANE: unknown")); break; } // Horizontal air direction switch (bytes[8] & 0xF0) { // 0b11110000 case 0x10: - Serial.println("WIDE VANE: LEFT"); + Serial.println(F("WIDE VANE: LEFT")); break; case 0x20: - Serial.println("WIDE VANE: MIDDLE LEFT"); + Serial.println(F("WIDE VANE: MIDDLE LEFT")); break; case 0x30: - Serial.println("WIDE VANE: MIDDLE"); + Serial.println(F("WIDE VANE: MIDDLE")); break; case 0x40: - Serial.println("WIDE VANE: MIDDLE RIGHT"); + Serial.println(F("WIDE VANE: MIDDLE RIGHT")); break; case 0x50: - Serial.println("WIDE VANE: RIGHT"); + Serial.println(F("WIDE VANE: RIGHT")); break; case 0xC0: - Serial.println("WIDE VANE: SWING"); + Serial.println(F("WIDE VANE: SWING")); break; default: - Serial.println("WIDE VANE: unknown"); + Serial.println(F("WIDE VANE: unknown")); break; } diff --git a/MitsubishiHeavy.cpp b/MitsubishiHeavy.cpp index 3a9c1e1..39e56d3 100644 --- a/MitsubishiHeavy.cpp +++ b/MitsubishiHeavy.cpp @@ -4,60 +4,60 @@ bool decodeMitsubishiHeavy(byte *bytes, int byteCount) { // If this looks like a Mitsubishi Heavy... if ( byteCount == 11 && bytes[0] == 0x52 && bytes[1] == 0xAE && bytes[2] == 0xC3 && bytes[3] == 0x26) { - Serial.println("Looks like a Mitsubishi Heavy protocol"); + Serial.println(F("Looks like a Mitsubishi Heavy protocol")); // Power mode switch (bytes[9] & 0x08) { case 0x00: - Serial.println("POWER ON"); + Serial.println(F("POWER ON")); break; case 0x08: - Serial.println("POWER OFF"); + Serial.println(F("POWER OFF")); break; } // Operating mode switch (bytes[9] & 0x07) { case 0x07: - Serial.println("MODE AUTO"); + Serial.println(F("MODE AUTO")); break; case 0x03: - Serial.println("MODE HEAT"); + Serial.println(F("MODE HEAT")); break; case 0x06: - Serial.println("MODE COOL"); + Serial.println(F("MODE COOL")); break; case 0x05: - Serial.println("MODE DRY"); + Serial.println(F("MODE DRY")); break; case 0x04: - Serial.println("MODE FAN"); + Serial.println(F("MODE FAN")); break; } // Temperature - Serial.print("Temperature: "); + Serial.print(F("Temperature: ")); Serial.println((~((bytes[9] & 0xF0) >> 4) & 0x0F) + 17); // Fan speed switch (bytes[7] & 0xE0) { case 0xE0: - Serial.println("FAN AUTO"); + Serial.println(F("FAN AUTO")); break; case 0xA0: - Serial.println("FAN 1"); + Serial.println(F("FAN 1")); break; case 0x80: - Serial.println("FAN 2"); + Serial.println(F("FAN 2")); break; case 0x60: - Serial.println("FAN 3"); + Serial.println(F("FAN 3")); break; case 0x20: - Serial.println("FAN HIGH SPEED"); + Serial.println(F("FAN HIGH SPEED")); break; case 0x00: - Serial.println("FAN SILENT"); + Serial.println(F("FAN SILENT")); break; } diff --git a/PanasonicCPK.cpp b/PanasonicCPK.cpp index f084dbe..c00e50b 100644 --- a/PanasonicCPK.cpp +++ b/PanasonicCPK.cpp @@ -4,55 +4,55 @@ bool decodePanasonicCKP(byte *bytes, int byteCount) { // If this looks like a Panasonic CKP code... if ( byteCount == 16 && bytes[10] == 0x36 ) { - Serial.println("Looks like a Panasonic CKP protocol"); + Serial.println(F("Looks like a Panasonic CKP protocol")); switch (bytes[2] & 0x07) { case 0x06: - Serial.println("AUTO"); + Serial.println(F("AUTO")); break; case 0x04: - Serial.println("HEAT"); + Serial.println(F("HEAT")); break; case 0x02: - Serial.println("COOL"); + Serial.println(F("COOL")); break; case 0x03: - Serial.println("DRY"); + Serial.println(F("DRY")); break; case 0x01: - Serial.println("FAN"); + Serial.println(F("FAN")); break; } if ((bytes[2] & 0x08) != 0x08) { - Serial.println("POWER SWITCH"); + Serial.println(F("POWER SWITCH")); } switch (bytes[0] & 0xF0) { case 0xF0: - Serial.println("FAN AUTO"); + Serial.println(F("FAN AUTO")); break; case 0x20: - Serial.println("FAN 1"); + Serial.println(F("FAN 1")); break; case 0x30: - Serial.println("FAN 2"); + Serial.println(F("FAN 2")); break; case 0x40: - Serial.println("FAN 3"); + Serial.println(F("FAN 3")); break; case 0x50: - Serial.println("FAN 4"); + Serial.println(F("FAN 4")); break; case 0x60: - Serial.println("FAN 5"); + Serial.println(F("FAN 5")); break; } - Serial.print("Temperature: "); + Serial.print(F("Temperature: ")); Serial.println((bytes[0] & 0x0F) + 15); return true; diff --git a/Sharp.cpp b/Sharp.cpp index fa2975e..dd7fdc7 100644 --- a/Sharp.cpp +++ b/Sharp.cpp @@ -4,15 +4,15 @@ bool decodeSharp(byte *bytes, int byteCount) { // If this looks like a Sharp code... if ( byteCount == 13 && bytes[0] == 0xAA && bytes[1] == 0x5A && bytes[2] == 0xCF ) { - Serial.println("Looks like a Sharp protocol"); + Serial.println(F("Looks like a Sharp protocol")); // Power mode switch (bytes[5]) { case 0x21: - Serial.println("POWER OFF"); + Serial.println(F("POWER OFF")); break; case 0x31: - Serial.println("POWER ON"); + Serial.println(F("POWER ON")); break; } @@ -20,39 +20,39 @@ bool decodeSharp(byte *bytes, int byteCount) switch (bytes[6] & 0x0F) { case 0x01: if (bytes[4] == 0x00) { - Serial.println("MODE MAINTENANCE HEAT"); + Serial.println(F("MODE MAINTENANCE HEAT")); } else { - Serial.println("MODE HEAT"); + Serial.println(F("MODE HEAT")); } break; case 0x02: - Serial.println("MODE COOL"); + Serial.println(F("MODE COOL")); break; case 0x03: - Serial.println("MODE DRY"); + Serial.println(F("MODE DRY")); break; } // Temperature - Serial.print("Temperature: "); + Serial.print(F("Temperature: ")); if (bytes[4] == 0x00) { - Serial.println("10"); + Serial.println(F("10")); } else { Serial.println(bytes[4] + 17); } switch (bytes[6] & 0xF0) { case 0x20: - Serial.println("FAN: AUTO"); + Serial.println(F("FAN: AUTO")); break; case 0x30: - Serial.println("FAN: 1"); + Serial.println(F("FAN: 1")); break; case 0x50: - Serial.println("FAN: 2"); + Serial.println(F("FAN: 2")); break; case 0x70: - Serial.println("FAN: 3"); + Serial.println(F("FAN: 3")); break; } @@ -67,13 +67,13 @@ bool decodeSharp(byte *bytes, int byteCount) checksum ^= (checksum >> 4); checksum &= 0x0F; - Serial.print("Checksum '0x"); + Serial.print(F("Checksum '0x")); Serial.print(checksum, HEX); if ( ((bytes[12] & 0xF0) >> 4) == checksum ) { - Serial.println("' matches"); + Serial.println(F("' matches")); } else { - Serial.print(" does not match "); + Serial.print(F(" does not match ")); Serial.println(((bytes[12] & 0xF0) >> 4), HEX); } return true; diff --git a/rawirdecode.ino b/rawirdecode.ino index e37f359..673842b 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -43,10 +43,10 @@ bool decodePanasonicCKP(byte *bytes, int byteCount); uint16_t RESOLUTION=20; // The thresholds for different symbols -int MARK_THRESHOLD_BIT_HEADER = 0; // Value between BIT MARK and HEADER MARK -int SPACE_THRESHOLD_ZERO_ONE = 0; // Value between ZERO SPACE and ONE SPACE -int SPACE_THRESHOLD_ONE_HEADER = 0; // Value between ONE SPACE and HEADER SPACE -int SPACE_THRESHOLD_HEADER_PAUSE = 0; // Value between HEADER SPACE and PAUSE SPACE (Panasonic/Midea only) +uint16_t MARK_THRESHOLD_BIT_HEADER = 0; // Value between BIT MARK and HEADER MARK +uint16_t SPACE_THRESHOLD_ZERO_ONE = 0; // Value between ZERO SPACE and ONE SPACE +uint16_t SPACE_THRESHOLD_ONE_HEADER = 0; // Value between ONE SPACE and HEADER SPACE +uint16_t SPACE_THRESHOLD_HEADER_PAUSE = 0; // Value between HEADER SPACE and PAUSE SPACE (Panasonic/Midea only) uint32_t mark_header_avg = 0; @@ -131,11 +131,26 @@ void setup(void) { } void loop(void) { - char incoming = 0; memset(symbols, 0, sizeof(symbols)); memset(bytes, 0, sizeof(bytes)); + currentpulse=0; + byteCount=0; + if (modelChoice != 9) { + receivePulses(); + } else { + while ((currentpulse = Serial.readBytesUntil('\n', symbols+1, sizeof(symbols)-1)) == 0) {} + currentpulse++; + } + + printPulses(); + decodeProtocols(); +} + +void receivePulses(void) { + uint16_t highpulse, lowpulse; // temporary storage timing + // Initialize the averages every time mark_header_avg = 0; mark_header_cnt = 0; @@ -154,22 +169,6 @@ void loop(void) { space_pause_avg = 0; space_pause_cnt = 0; - currentpulse=0; - byteCount=0; - if (modelChoice != 9) { - receivePulses(); - } else { - while ((currentpulse = Serial.readBytesUntil('\n', symbols+1, sizeof(symbols)-1)) == 0) {} - currentpulse++; - } - - printPulses(); - decodeProtocols(); -} - -void receivePulses(void) { - uint16_t highpulse, lowpulse; // temporary storage timing - while (currentpulse < sizeof(symbols)) { highpulse = 0; @@ -243,18 +242,18 @@ void printPulses(void) { int bitCount = 0; byte currentByte = 0; - Serial.print("\nNumber of symbols: "); + Serial.print(F("\nNumber of symbols: ")); Serial.println(currentpulse); // Print the symbols (0, 1, H, h, W) - Serial.println("Symbols:"); + Serial.println(F("Symbols:")); Serial.println(symbols+1); // Print the decoded bytes - Serial.println("Bytes:"); + Serial.println(F("Bytes:")); // Decode the string of bits to a byte array - for (int i = 0; i < currentpulse; i++) { + for (uint16_t i = 0; i < currentpulse; i++) { if (symbols[i] == '0' || symbols[i] == '1') { currentByte >>= 1; bitCount++; @@ -276,34 +275,34 @@ void printPulses(void) { // Print the byte array for (int i = 0; i < byteCount; i++) { if (bytes[i] < 0x10) { - Serial.print("0"); + Serial.print(F("0")); } Serial.print(bytes[i],HEX); if ( i < byteCount - 1 ) { - Serial.print(","); + Serial.print(F(",")); } } Serial.println(); // Print the timing constants - Serial.println("Timings (in us): "); - Serial.print("PAUSE SPACE: "); + Serial.println(F("Timings (in us): ")); + Serial.print(F("PAUSE SPACE: ")); Serial.println(space_pause_avg); - Serial.print("HEADER MARK: "); + Serial.print(F("HEADER MARK: ")); Serial.println(mark_header_avg); - Serial.print("HEADER SPACE: "); + Serial.print(F("HEADER SPACE: ")); Serial.println(space_header_avg); - Serial.print("BIT MARK: "); + Serial.print(F("BIT MARK: ")); Serial.println(mark_bit_avg); - Serial.print("ZERO SPACE: "); + Serial.print(F("ZERO SPACE: ")); Serial.println(space_zero_avg); - Serial.print("ONE SPACE: "); + Serial.print(F("ONE SPACE: ")); Serial.println(space_one_avg); } void decodeProtocols() { - Serial.println("Decoding known protocols..."); + Serial.println(F("Decoding known protocols...")); if ( ! (decodeMitsubishiElectric(bytes, byteCount) || decodeMitsubishiHeavy(bytes, byteCount) || From cae72ca1546eaffb1b9515ae28d1129065ada27b Mon Sep 17 00:00:00 2001 From: ToniA Date: Thu, 7 Jan 2016 09:21:31 +0200 Subject: [PATCH 17/94] Added README, added Mitsubishi Heavy air direction decoding --- .gitignore | 1 - MitsubishiHeavy.cpp | 49 +++++++++++++ README.md | 18 +++++ rawirdecode/rawirdecode.ino | 111 ------------------------------ rawirdecode/rawirdecodestruct.ino | 111 ------------------------------ 5 files changed, 67 insertions(+), 223 deletions(-) delete mode 100644 .gitignore create mode 100644 README.md delete mode 100644 rawirdecode/rawirdecode.ino delete mode 100644 rawirdecode/rawirdecodestruct.ino diff --git a/.gitignore b/.gitignore deleted file mode 100644 index ccc9fd9..0000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.DS_Store \ No newline at end of file diff --git a/MitsubishiHeavy.cpp b/MitsubishiHeavy.cpp index 39e56d3..4795ca5 100644 --- a/MitsubishiHeavy.cpp +++ b/MitsubishiHeavy.cpp @@ -61,6 +61,55 @@ bool decodeMitsubishiHeavy(byte *bytes, int byteCount) break; } + // Vertical air direction + Serial.print(F("Vertical air direction: ")); + switch ((bytes[5] & 0b00000010) | (bytes[5] & 0b00011000)) { + case 0x0A: + Serial.println(F("AUTO")); + break; + case 0x02: + Serial.println(F("UP")); + break; + case 0x18: + Serial.println(F("MIDDLE UP")); + break; + case 0x10: + Serial.println(F("MIDDLE")); + break; + case 0x08: + Serial.println(F("MIDDLE DOWN")); + break; + case 0x00: + Serial.println(F("DOWN")); + break; + case 0x1A: + Serial.println(F("STOP")); + break; + } + + // Horizontal air direction + Serial.print(F("Horizontal air direction: ")); + switch (bytes[5] & 0b11001100) { + case 0x04: + Serial.println(F("AUTO")); + break; + case 0x48: + Serial.println(F("MIDDLE")); + break; + case 0xC8: + Serial.println(F("LEFT")); + break; + case 0x88: + Serial.println(F("MIDDLE LEFT")); + break; + case 0x08: + Serial.println(F("MIDDLE RIGHT")); + break; + case 0xC4: + Serial.println(F("RIGHT")); + break; + } + return true; } diff --git a/README.md b/README.md new file mode 100644 index 0000000..3150287 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# IR decoder + +Decodes long IR codes, for example from air conditioner / heat pump devices. + +Shows the timings, the symbols, and also the decoded signal for certain air conditioners. + + +## Instructions + +* Connect an IR receiver into the Arduino +* Start the sketch, and enter 1, 2 or 3 into the 'Serial Monitor', to select which timings to use + * Try out the alternatives until you get sensible output + * The signal should always start with 'Hh', and within the signal there should only be a couple of 'Hh' pairs (if any) + * 'H' and 'h' should be there only in pairs 'Hh' + * 'H' stands for 'header mark' and 'h' for 'header space' +* Point your IR remote to the IR receiver and send the code + +![Schema](arduino_irreceiver.png) diff --git a/rawirdecode/rawirdecode.ino b/rawirdecode/rawirdecode.ino deleted file mode 100644 index e493326..0000000 --- a/rawirdecode/rawirdecode.ino +++ /dev/null @@ -1,111 +0,0 @@ - -/* Raw IR decoder sketch! - - This sketch/program uses the Arduno and a PNA4602 to - decode IR received. This can be used to make a IR receiver - (by looking for a particular code) - or transmitter (by pulsing an IR LED at ~38KHz for the - durations detected - - Code is public domain, check out www.ladyada.net and adafruit.com - for more tutorials! - */ - -// We need to use the 'raw' pin reading methods -// because timing is very important here and the digitalRead() -// procedure is slower! -//uint8_t IRpin = 2; -// Digital pin #2 is the same as Pin D2 see -// http://arduino.cc/en/Hacking/PinMapping168 for the 'raw' pin mapping -#define IRpin_PIN PIND -#define IRpin 2 - -// the maximum pulse we'll listen for - 65 milliseconds is a long time -#define MAXPULSE 65000 - -// what our timing resolution should be, larger is better -// as its more 'precise' - but too large and you wont get -// accurate timing -#define RESOLUTION 20 - -// we will store up to 100 pulse pairs (this is -a lot-) -uint16_t pulses[100][2]; // pair is high and low pulse -uint8_t currentpulse = 0; // index for pulses we're storing - -void setup(void) { - Serial.begin(9600); - Serial.println("Ready to decode IR!"); -} - -void loop(void) { - uint16_t highpulse, lowpulse; // temporary storage timing - highpulse = lowpulse = 0; // start out with no pulse length - - -// while (digitalRead(IRpin)) { // this is too slow! - while (IRpin_PIN & (1 << IRpin)) { - // pin is still HIGH - - // count off another few microseconds - highpulse++; - delayMicroseconds(RESOLUTION); - - // If the pulse is too long, we 'timed out' - either nothing - // was received or the code is finished, so print what - // we've grabbed so far, and then reset - if ((highpulse >= MAXPULSE) && (currentpulse != 0)) { - printpulses(); - currentpulse=0; - return; - } - } - // we didn't time out so lets stash the reading - pulses[currentpulse][0] = highpulse; - - // same as above - while (! (IRpin_PIN & _BV(IRpin))) { - // pin is still LOW - lowpulse++; - delayMicroseconds(RESOLUTION); - if ((lowpulse >= MAXPULSE) && (currentpulse != 0)) { - printpulses(); - currentpulse=0; - return; - } - } - pulses[currentpulse][1] = lowpulse; - - // we read one high-low pulse successfully, continue! - currentpulse++; -} - -void printpulses(void) { - Serial.println("\n\r\n\rReceived: \n\rOFF \tON"); - for (uint8_t i = 0; i < currentpulse; i++) { - Serial.print(pulses[i][0] * RESOLUTION, DEC); - Serial.print(" usec, "); - Serial.print(pulses[i][1] * RESOLUTION, DEC); - Serial.println(" usec"); - } - - // print it in a 'array' format - Serial.println("int IRsignal[] = {"); - Serial.println("// ON, OFF "); - for (uint8_t i = 0; i < currentpulse-1; i++) { - //Serial.print("\t"); // tab - Serial.print("pulseIR("); - Serial.print(pulses[i][1] * RESOLUTION , DEC); - Serial.print(");"); - Serial.println(""); - //Serial.print("\t"); - Serial.print("delayMicroseconds("); - Serial.print(pulses[i+1][0] * RESOLUTION , DEC); - Serial.println(");"); - - } - //Serial.print("\t"); // tab - Serial.print("pulseIR("); - Serial.print(pulses[currentpulse-1][1] * RESOLUTION, DEC); - Serial.print(");"); - -} diff --git a/rawirdecode/rawirdecodestruct.ino b/rawirdecode/rawirdecodestruct.ino deleted file mode 100644 index e493326..0000000 --- a/rawirdecode/rawirdecodestruct.ino +++ /dev/null @@ -1,111 +0,0 @@ - -/* Raw IR decoder sketch! - - This sketch/program uses the Arduno and a PNA4602 to - decode IR received. This can be used to make a IR receiver - (by looking for a particular code) - or transmitter (by pulsing an IR LED at ~38KHz for the - durations detected - - Code is public domain, check out www.ladyada.net and adafruit.com - for more tutorials! - */ - -// We need to use the 'raw' pin reading methods -// because timing is very important here and the digitalRead() -// procedure is slower! -//uint8_t IRpin = 2; -// Digital pin #2 is the same as Pin D2 see -// http://arduino.cc/en/Hacking/PinMapping168 for the 'raw' pin mapping -#define IRpin_PIN PIND -#define IRpin 2 - -// the maximum pulse we'll listen for - 65 milliseconds is a long time -#define MAXPULSE 65000 - -// what our timing resolution should be, larger is better -// as its more 'precise' - but too large and you wont get -// accurate timing -#define RESOLUTION 20 - -// we will store up to 100 pulse pairs (this is -a lot-) -uint16_t pulses[100][2]; // pair is high and low pulse -uint8_t currentpulse = 0; // index for pulses we're storing - -void setup(void) { - Serial.begin(9600); - Serial.println("Ready to decode IR!"); -} - -void loop(void) { - uint16_t highpulse, lowpulse; // temporary storage timing - highpulse = lowpulse = 0; // start out with no pulse length - - -// while (digitalRead(IRpin)) { // this is too slow! - while (IRpin_PIN & (1 << IRpin)) { - // pin is still HIGH - - // count off another few microseconds - highpulse++; - delayMicroseconds(RESOLUTION); - - // If the pulse is too long, we 'timed out' - either nothing - // was received or the code is finished, so print what - // we've grabbed so far, and then reset - if ((highpulse >= MAXPULSE) && (currentpulse != 0)) { - printpulses(); - currentpulse=0; - return; - } - } - // we didn't time out so lets stash the reading - pulses[currentpulse][0] = highpulse; - - // same as above - while (! (IRpin_PIN & _BV(IRpin))) { - // pin is still LOW - lowpulse++; - delayMicroseconds(RESOLUTION); - if ((lowpulse >= MAXPULSE) && (currentpulse != 0)) { - printpulses(); - currentpulse=0; - return; - } - } - pulses[currentpulse][1] = lowpulse; - - // we read one high-low pulse successfully, continue! - currentpulse++; -} - -void printpulses(void) { - Serial.println("\n\r\n\rReceived: \n\rOFF \tON"); - for (uint8_t i = 0; i < currentpulse; i++) { - Serial.print(pulses[i][0] * RESOLUTION, DEC); - Serial.print(" usec, "); - Serial.print(pulses[i][1] * RESOLUTION, DEC); - Serial.println(" usec"); - } - - // print it in a 'array' format - Serial.println("int IRsignal[] = {"); - Serial.println("// ON, OFF "); - for (uint8_t i = 0; i < currentpulse-1; i++) { - //Serial.print("\t"); // tab - Serial.print("pulseIR("); - Serial.print(pulses[i][1] * RESOLUTION , DEC); - Serial.print(");"); - Serial.println(""); - //Serial.print("\t"); - Serial.print("delayMicroseconds("); - Serial.print(pulses[i+1][0] * RESOLUTION , DEC); - Serial.println(");"); - - } - //Serial.print("\t"); // tab - Serial.print("pulseIR("); - Serial.print(pulses[currentpulse-1][1] * RESOLUTION, DEC); - Serial.print(");"); - -} From 3da5b7b03cc7bfcbacf9f56da4e6160e77ffe01c Mon Sep 17 00:00:00 2001 From: Sateetje Date: Fri, 8 Jan 2016 14:19:43 +0100 Subject: [PATCH 18/94] Update MitsubishiHeavy.cpp Horizontal Auto > 4C --- MitsubishiHeavy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MitsubishiHeavy.cpp b/MitsubishiHeavy.cpp index 4795ca5..3bbd9b8 100644 --- a/MitsubishiHeavy.cpp +++ b/MitsubishiHeavy.cpp @@ -90,7 +90,7 @@ bool decodeMitsubishiHeavy(byte *bytes, int byteCount) // Horizontal air direction Serial.print(F("Horizontal air direction: ")); switch (bytes[5] & 0b11001100) { - case 0x04: + case 0x4C: Serial.println(F("AUTO")); break; case 0x48: From 188b6469563fcc9045df401948e57aca8c772288 Mon Sep 17 00:00:00 2001 From: Sateetje Date: Fri, 8 Jan 2016 14:33:15 +0100 Subject: [PATCH 19/94] Update MitsubishiHeavy.cpp Some horizontal directions added en tested (LeftRight; RightLeft; Stop; 3DAuto) --- MitsubishiHeavy.cpp | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/MitsubishiHeavy.cpp b/MitsubishiHeavy.cpp index 4795ca5..339b50e 100644 --- a/MitsubishiHeavy.cpp +++ b/MitsubishiHeavy.cpp @@ -90,17 +90,14 @@ bool decodeMitsubishiHeavy(byte *bytes, int byteCount) // Horizontal air direction Serial.print(F("Horizontal air direction: ")); switch (bytes[5] & 0b11001100) { - case 0x04: - Serial.println(F("AUTO")); - break; - case 0x48: - Serial.println(F("MIDDLE")); - break; case 0xC8: Serial.println(F("LEFT")); break; case 0x88: Serial.println(F("MIDDLE LEFT")); + break; + case 0x48: + Serial.println(F("MIDDLE")); break; case 0x08: Serial.println(F("MIDDLE RIGHT")); @@ -108,6 +105,21 @@ bool decodeMitsubishiHeavy(byte *bytes, int byteCount) case 0xC4: Serial.println(F("RIGHT")); break; + case 0x84: + Serial.println(F("LEFT RIGHT")); + break; + case 0x44: + Serial.println(F("RIGHT LEFT")); + break; + case 0x4C: + Serial.println(F("AUTO")); + break; + case 0xCC: + Serial.println(F("STOP")); + break; + case 0x04: + Serial.println(F("3D AUTO")); + break; } return true; From f85692c5a433a24ee257572f1aad3b376675b513 Mon Sep 17 00:00:00 2001 From: Sateetje Date: Fri, 8 Jan 2016 14:35:44 +0100 Subject: [PATCH 20/94] Update MitsubishiHeavy.cpp Typo --- MitsubishiHeavy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MitsubishiHeavy.cpp b/MitsubishiHeavy.cpp index 4795ca5..8bb758f 100644 --- a/MitsubishiHeavy.cpp +++ b/MitsubishiHeavy.cpp @@ -63,7 +63,7 @@ bool decodeMitsubishiHeavy(byte *bytes, int byteCount) // Vertical air direction Serial.print(F("Vertical air direction: ")); - switch ((bytes[5] & 0b00000010) | (bytes[5] & 0b00011000)) { + switch ((bytes[5] & 0b00000010) | (bytes[7] & 0b00011000)) { case 0x0A: Serial.println(F("AUTO")); break; From 7c412a77f97c639f94f7cd0848407be006cbb664 Mon Sep 17 00:00:00 2001 From: Sateetje Date: Fri, 8 Jan 2016 14:38:34 +0100 Subject: [PATCH 21/94] Update MitsubishiHeavy.cpp Added/Changed some comments --- MitsubishiHeavy.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/MitsubishiHeavy.cpp b/MitsubishiHeavy.cpp index 4795ca5..3bff897 100644 --- a/MitsubishiHeavy.cpp +++ b/MitsubishiHeavy.cpp @@ -3,6 +3,7 @@ bool decodeMitsubishiHeavy(byte *bytes, int byteCount) { // If this looks like a Mitsubishi Heavy... + // Model SRKxxZJ-S Remote Control RKX502A001C if ( byteCount == 11 && bytes[0] == 0x52 && bytes[1] == 0xAE && bytes[2] == 0xC3 && bytes[3] == 0x26) { Serial.println(F("Looks like a Mitsubishi Heavy protocol")); @@ -54,10 +55,10 @@ bool decodeMitsubishiHeavy(byte *bytes, int byteCount) Serial.println(F("FAN 3")); break; case 0x20: - Serial.println(F("FAN HIGH SPEED")); + Serial.println(F("FAN HIGH SPEED/HI POWER MODE")); break; case 0x00: - Serial.println(F("FAN SILENT")); + Serial.println(F("FAN SILENT/ECONO MODE")); break; } From 35986dca23342709cc6c1777d1f4a4fcbcc6f4e8 Mon Sep 17 00:00:00 2001 From: ToniA Date: Fri, 8 Jan 2016 15:50:40 +0200 Subject: [PATCH 22/94] Resolved conflict on pull request #3 --- MitsubishiHeavy.cpp | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/MitsubishiHeavy.cpp b/MitsubishiHeavy.cpp index 9d9a7c4..369b64e 100644 --- a/MitsubishiHeavy.cpp +++ b/MitsubishiHeavy.cpp @@ -91,17 +91,14 @@ bool decodeMitsubishiHeavy(byte *bytes, int byteCount) // Horizontal air direction Serial.print(F("Horizontal air direction: ")); switch (bytes[5] & 0b11001100) { - case 0x4C: - Serial.println(F("AUTO")); - break; - case 0x48: - Serial.println(F("MIDDLE")); - break; case 0xC8: Serial.println(F("LEFT")); break; case 0x88: Serial.println(F("MIDDLE LEFT")); + break; + case 0x48: + Serial.println(F("MIDDLE")); break; case 0x08: Serial.println(F("MIDDLE RIGHT")); @@ -109,6 +106,21 @@ bool decodeMitsubishiHeavy(byte *bytes, int byteCount) case 0xC4: Serial.println(F("RIGHT")); break; + case 0x84: + Serial.println(F("LEFT RIGHT")); + break; + case 0x44: + Serial.println(F("RIGHT LEFT")); + break; + case 0x4C: + Serial.println(F("AUTO")); + break; + case 0xCC: + Serial.println(F("STOP")); + break; + case 0x04: + Serial.println(F("3D AUTO")); + break; } return true; From ff928809d660d948183a4490e181c11f941cac18 Mon Sep 17 00:00:00 2001 From: Sateetje Date: Sat, 23 Jan 2016 23:16:24 +0100 Subject: [PATCH 23/94] Update MitsubishiHeavy.cpp -Added Mitsubishi Heavy SRKxxZM-S protocol -Some textual changes to ZJ-S protocol Remark: protocol is tested with original remote control --- MitsubishiHeavy.cpp | 184 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 175 insertions(+), 9 deletions(-) diff --git a/MitsubishiHeavy.cpp b/MitsubishiHeavy.cpp index 369b64e..e54363b 100644 --- a/MitsubishiHeavy.cpp +++ b/MitsubishiHeavy.cpp @@ -3,10 +3,10 @@ bool decodeMitsubishiHeavy(byte *bytes, int byteCount) { // If this looks like a Mitsubishi Heavy... - // Model SRKxxZJ-S Remote Control RKX502A001C - if ( byteCount == 11 && bytes[0] == 0x52 && bytes[1] == 0xAE && bytes[2] == 0xC3 && bytes[3] == 0x26) { - Serial.println(F("Looks like a Mitsubishi Heavy protocol")); - + if ( byteCount == 11 && bytes[0] == 0x52 && bytes[1] == 0xAE && bytes[2] == 0xC3 && bytes[3] == 0x26 && bytes[4] == 0xD9) { + Serial.println(F("Looks like a Mitsubishi Heavy ZJ-S protocol")); + Serial.println(F("Model SRKxxZJ-S Remote Control RKX502A001C")); + // Power mode switch (bytes[9] & 0x08) { case 0x00: @@ -55,10 +55,10 @@ bool decodeMitsubishiHeavy(byte *bytes, int byteCount) Serial.println(F("FAN 3")); break; case 0x20: - Serial.println(F("FAN HIGH SPEED/HI POWER MODE")); + Serial.println(F("HI POWER MODE")); break; case 0x00: - Serial.println(F("FAN SILENT/ECONO MODE")); + Serial.println(F("ECONO MODE")); break; } @@ -66,7 +66,7 @@ bool decodeMitsubishiHeavy(byte *bytes, int byteCount) Serial.print(F("Vertical air direction: ")); switch ((bytes[5] & 0b00000010) | (bytes[7] & 0b00011000)) { case 0x0A: - Serial.println(F("AUTO")); + Serial.println(F("SWING")); break; case 0x02: Serial.println(F("UP")); @@ -113,7 +113,7 @@ bool decodeMitsubishiHeavy(byte *bytes, int byteCount) Serial.println(F("RIGHT LEFT")); break; case 0x4C: - Serial.println(F("AUTO")); + Serial.println(F("SWING")); break; case 0xCC: Serial.println(F("STOP")); @@ -124,7 +124,173 @@ bool decodeMitsubishiHeavy(byte *bytes, int byteCount) } return true; - } + } else if ( byteCount == 19 && bytes[0] == 0x52 && bytes[1] == 0xAE && bytes[2] == 0xC3 && bytes[3] == 0x1A && bytes[4] == 0xE5) { + Serial.println(F("Looks like a Mitsubishi Heavy ZM-S protocol")); + Serial.println(F("Model SRKxxZM-S Remote Control RLA502A700B")); + + // Power mode + switch (bytes[5] & 0x08) { + case 0x00: + Serial.println(F("POWER ON")); + break; + case 0x08: + Serial.println(F("POWER OFF")); + break; + } + + // Operating mode + switch (bytes[5] & 0x07) { + case 0x07: + Serial.println(F("MODE AUTO")); + break; + case 0x03: + Serial.println(F("MODE HEAT")); + break; + case 0x06: + Serial.println(F("MODE COOL")); + break; + case 0x05: + Serial.println(F("MODE DRY")); + break; + case 0x04: + Serial.println(F("MODE FAN")); + break; + } + + // Temperature + Serial.print(F("Temperature: ")); + Serial.println((~(bytes[7]) & 0x0F) + 17); + + // Fan speed + switch (bytes[9] & 0x0F) { + case 0x0F: + Serial.println(F("FAN AUTO")); + break; + case 0x0E: + Serial.println(F("FAN 1")); + break; + case 0x0D: + Serial.println(F("FAN 2")); + break; + case 0x0C: + Serial.println(F("FAN 3")); + break; + case 0x0B: + Serial.println(F("FAN 4")); + break; + case 0x07: + Serial.println(F("HI POWER MODE")); + break; + case 0x09: + Serial.println(F("ECONO MODE")); + break; + } + + // Vertical air direction + Serial.print(F("Vertical air direction: ")); + switch (bytes[11] & 0xE0) { + case 0xE0: + Serial.println(F("SWING")); + break; + case 0xC0: + Serial.println(F("UP")); + break; + case 0xA0: + Serial.println(F("MIDDLE UP")); + break; + case 0x80: + Serial.println(F("MIDDLE")); + break; + case 0x60: + Serial.println(F("MIDDLE DOWN")); + break; + case 0x40: + Serial.println(F("DOWN")); + break; + case 0x20: + Serial.println(F("STOP")); + break; + } + + // Horizontal air direction + Serial.print(F("Horizontal air direction: ")); + switch (bytes[13] & 0x0F) { + case 0x0E: + Serial.println(F("LEFT")); + break; + case 0x0D: + Serial.println(F("MIDDLE LEFT")); + break; + case 0x0C: + Serial.println(F("MIDDLE")); + break; + case 0x0B: + Serial.println(F("MIDDLE RIGHT")); + break; + case 0x0A: + Serial.println(F("RIGHT")); + break; + case 0x08: + Serial.println(F("LEFT RIGHT")); + break; + case 0x09: + Serial.println(F("RIGHT LEFT")); + break; + case 0x0F: + Serial.println(F("SWING")); + break; + case 0x07: + Serial.println(F("STOP")); + break; + } + // 3D Auto + Serial.print(F("3D Auto: ")); + switch (bytes[11] & 0b00010010) { + case 0x00: + Serial.println(F("ON")); + break; + case 0x12: + Serial.println(F("OFF")); + break; + } + + // Night setback + Serial.print(F("Night setback: ")); + switch (bytes[15] & 0x40) { + case 0x00: + Serial.println(F("ON")); + break; + case 0x40: + Serial.println(F("OFF")); + break; + } + + // Silent mode + Serial.print(F("Silent mode: ")); + switch (bytes[15] & 0x80) { + case 0x00: + Serial.println(F("ON")); + break; + case 0x80: + Serial.println(F("OFF")); + break; + } + + // Clean and alergen + Serial.print(F("Clean or Alergen: ")); + switch (bytes[5] & 0x60) { + case 0x00: + Serial.println(F("CLEAN")); + break; + case 0x20: + Serial.println(F("ALERGEN")); + break; + case 0x60: + Serial.println(F("OFF")); + break; + } + return true; + } return false; } From 8847580e06d68a45583e5039968a6b832b68a3b0 Mon Sep 17 00:00:00 2001 From: ToniA Date: Sun, 24 Jan 2016 19:02:47 +0200 Subject: [PATCH 24/94] Add choice '4' for Hyundai --- rawirdecode.ino | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/rawirdecode.ino b/rawirdecode.ino index 673842b..eb4a028 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -81,6 +81,7 @@ void setup(void) { Serial.println(F("* '1' for Panasonic DKE>, Mitsubishi Electric, Fujitsu etc. codes")); Serial.println(F("* '2' for Panasonic CKP, Midea etc. codes")); Serial.println(F("* '3' for Mitsubishi Heavy etc. codes")); + Serial.println(F("* '4' for Hyyndai etc. codes")); Serial.println(F("* '9' for entering the bit sequence on the serial monitor (instead of the IR receiver)")); Serial.println(); Serial.print(F("Enter choice: ")); @@ -101,6 +102,9 @@ void setup(void) { case '3': modelChoice = 3; break; + case '4': + modelChoice = 4; + break; case '9': modelChoice = 9; break; @@ -127,6 +131,11 @@ void setup(void) { SPACE_THRESHOLD_ZERO_ONE = 800; SPACE_THRESHOLD_ONE_HEADER = 1400; SPACE_THRESHOLD_HEADER_PAUSE = 8000; + } else if (modelChoice == 4) { + MARK_THRESHOLD_BIT_HEADER = 2000; + SPACE_THRESHOLD_ZERO_ONE = 800; + SPACE_THRESHOLD_ONE_HEADER = 2400; + SPACE_THRESHOLD_HEADER_PAUSE = 8000; } } @@ -231,6 +240,8 @@ void receivePulses(void) { mark_header_avg = (lowpulse + mark_header_cnt * mark_header_avg) / ++mark_header_cnt; } else { mark_bit_avg = (lowpulse + mark_bit_cnt * mark_bit_avg) / ++mark_bit_cnt; + symbols[currentpulse] = 'M'; + currentpulse++; } // we read one high-low pulse successfully, continue! From b4a42da0ff1892d6bee540b4ae79f45ad92a9f20 Mon Sep 17 00:00:00 2001 From: ToniA Date: Sun, 24 Jan 2016 19:54:29 +0200 Subject: [PATCH 25/94] MARK symbol should not be printed, it slipped in by mistake :) --- rawirdecode.ino | 2 -- 1 file changed, 2 deletions(-) diff --git a/rawirdecode.ino b/rawirdecode.ino index eb4a028..9d88ead 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -240,8 +240,6 @@ void receivePulses(void) { mark_header_avg = (lowpulse + mark_header_cnt * mark_header_avg) / ++mark_header_cnt; } else { mark_bit_avg = (lowpulse + mark_bit_cnt * mark_bit_avg) / ++mark_bit_cnt; - symbols[currentpulse] = 'M'; - currentpulse++; } // we read one high-low pulse successfully, continue! From 47b69f3c414ba353023786d595f2d674d6abc013 Mon Sep 17 00:00:00 2001 From: ToniA Date: Fri, 29 Jan 2016 21:28:11 +0200 Subject: [PATCH 26/94] Hyundai decoding --- Hyundai.cpp | 83 +++++++++++++++++++++++++++++++++++++++++++++++++ rawirdecode.ino | 4 ++- 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 Hyundai.cpp diff --git a/Hyundai.cpp b/Hyundai.cpp new file mode 100644 index 0000000..0a353f5 --- /dev/null +++ b/Hyundai.cpp @@ -0,0 +1,83 @@ +#include + +bool decodeHyundai(byte *bytes, int pulseCount) +{ + // If this looks like a Hyundai code... + // Remote control Y512F2, Hyundai split-unit air conditioner + if ( pulseCount == 38 ) { + Serial.println(F("Looks like a Hyundai protocol")); + + // Power mode + switch (bytes[0] & 0x08) { + case 0x00: + Serial.println(F("POWER OFF")); + break; + case 0x08: + Serial.println(F("POWER ON")); + break; + } + + // Operating mode + switch (bytes[0] & 0x07) { + case 0x00: + Serial.println(F("MODE AUTO")); + break; + case 0x04: + Serial.println(F("MODE HEAT")); + break; + case 0x01: + Serial.println(F("MODE COOL")); + break; + case 0x02: + Serial.println(F("MODE DRY")); + break; + case 0x03: + Serial.println(F("MODE FAN")); + break; + } + + // Temperature + Serial.print(F("Temperature: ")); + Serial.println((bytes[1] & 0x0F) + 16); + + // Fan speed + switch (bytes[0] & 0x30) { + case 0x00: + Serial.println(F("FAN: AUTO")); + break; + case 0x10: + Serial.println(F("FAN: 1")); + break; + case 0x20: + Serial.println(F("FAN: 2")); + break; + case 0x30: + Serial.println(F("FAN: 3")); + break; + } + + // Sleep mode + switch (bytes[0] & 0x80) { + case 0x80: + Serial.println(F("SLEEP: ON")); + break; + case 0x00: + Serial.println(F("SLEEP: OFF")); + break; + } + + // Air direction + switch (bytes[0] & 0x40) { + case 0x40: + Serial.println(F("SWING: ON")); + break; + case 0x00: + Serial.println(F("SWING: OFF")); + break; + } + + return true; + } + + return false; +} diff --git a/rawirdecode.ino b/rawirdecode.ino index 9d88ead..7b63605 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -6,6 +6,7 @@ bool decodeDaikin(byte *bytes, int byteCount); bool decodeSharp(byte *bytes, int byteCount); bool decodeCarrier(byte *bytes, int byteCount); bool decodePanasonicCKP(byte *bytes, int byteCount); +bool decodeHyundai(byte *bytes, int pulseCount); /* Raw IR decoder sketch! @@ -318,7 +319,8 @@ void decodeProtocols() decodeSharp(bytes, byteCount) || decodeDaikin(bytes, byteCount) || decodeCarrier(bytes, byteCount) || - decodePanasonicCKP(bytes, byteCount) ) ) + decodePanasonicCKP(bytes, byteCount) || + decodeHyundai(bytes, currentpulse)) ) { Serial.println(F("Unknown protocol")); } From 7972052afbe2b2a479f333f56c1bcdb0eab68dcf Mon Sep 17 00:00:00 2001 From: Sateetje Date: Sat, 30 Jan 2016 19:50:29 +0100 Subject: [PATCH 27/94] Update MitsubishiHeavy.cpp The vertical flow bits are also in byte 13 at the same location. --- MitsubishiHeavy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MitsubishiHeavy.cpp b/MitsubishiHeavy.cpp index e54363b..e83ddf2 100644 --- a/MitsubishiHeavy.cpp +++ b/MitsubishiHeavy.cpp @@ -188,7 +188,7 @@ bool decodeMitsubishiHeavy(byte *bytes, int byteCount) // Vertical air direction Serial.print(F("Vertical air direction: ")); - switch (bytes[11] & 0xE0) { + switch (bytes[11] & bytes[13] & 0xE0) { case 0xE0: Serial.println(F("SWING")); break; From d141cbe2f64f8ced9c8505aa99542a36e8aa5e28 Mon Sep 17 00:00:00 2001 From: ToniA Date: Sun, 31 Jan 2016 11:23:45 +0200 Subject: [PATCH 28/94] Mitsubishi Electric MSY series decoding --- MitsubishiElectric.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/MitsubishiElectric.cpp b/MitsubishiElectric.cpp index 85e15c3..414b7b2 100644 --- a/MitsubishiElectric.cpp +++ b/MitsubishiElectric.cpp @@ -3,8 +3,10 @@ bool decodeMitsubishiElectric(byte *bytes, int byteCount) { // If this looks like a Mitsubishi FD-25 or FE code... - if ( byteCount == 36 && bytes[0] == 0x23 && (memcmp(bytes, bytes+18, 17) == 0)) { - Serial.println(F("Looks like a Mitsubishi FD / FE series protocol")); + if ( byteCount == 36 && bytes[0] == 0x23 && + ( (memcmp(bytes, bytes+18, 17) == 0) || + ((memcmp(bytes, bytes+18, 14) == 0) && bytes[32] == 0x24) ) ){ + Serial.println(F("Looks like a Mitsubishi FD / FE / MSY series protocol")); // Check if the checksum matches byte checksum = 0; From e4025ca777124dee5202d6b6354236e71daec41b Mon Sep 17 00:00:00 2001 From: ToniA Date: Sun, 31 Jan 2016 11:36:37 +0200 Subject: [PATCH 29/94] Use just bytes[11] for the vertical air direction, bytes[13] is redundant --- MitsubishiHeavy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MitsubishiHeavy.cpp b/MitsubishiHeavy.cpp index e83ddf2..0eaf39e 100644 --- a/MitsubishiHeavy.cpp +++ b/MitsubishiHeavy.cpp @@ -188,7 +188,7 @@ bool decodeMitsubishiHeavy(byte *bytes, int byteCount) // Vertical air direction Serial.print(F("Vertical air direction: ")); - switch (bytes[11] & bytes[13] & 0xE0) { + switch ((bytes[11] & 0b11100000)) { case 0xE0: Serial.println(F("SWING")); break; From 08cb8daf1d59d6d9b24a36b7f72f1f02f3fe2664 Mon Sep 17 00:00:00 2001 From: Patrick Hamers Date: Wed, 3 Feb 2016 20:36:47 +0100 Subject: [PATCH 30/94] Update MitsubishiHeavy.cpp Added Clean mode for ZJ --- MitsubishiHeavy.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/MitsubishiHeavy.cpp b/MitsubishiHeavy.cpp index 0eaf39e..c491f2b 100644 --- a/MitsubishiHeavy.cpp +++ b/MitsubishiHeavy.cpp @@ -123,6 +123,17 @@ bool decodeMitsubishiHeavy(byte *bytes, int byteCount) break; } + // Clean + Serial.print(F("Clean: ")); + switch (bytes[5] & 0x20) { + case 0x00: + Serial.println(F("ON")); + break; + case 0x20: + Serial.println(F("OFF")); + break; + } + return true; } else if ( byteCount == 19 && bytes[0] == 0x52 && bytes[1] == 0xAE && bytes[2] == 0xC3 && bytes[3] == 0x1A && bytes[4] == 0xE5) { Serial.println(F("Looks like a Mitsubishi Heavy ZM-S protocol")); From c9db067da958dd93b1997fba06221afd12e97f19 Mon Sep 17 00:00:00 2001 From: ToniA Date: Sat, 27 Feb 2016 12:29:35 +0200 Subject: [PATCH 31/94] Support Chinese Fuego, Vivax, Classe, NEO, Galanz, Simbio, Beko etc. codes --- Fuego.cpp | 104 ++++++++++++++++++++++++++++++++++++++++++++++++ rawirdecode.ino | 5 ++- 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 Fuego.cpp diff --git a/Fuego.cpp b/Fuego.cpp new file mode 100644 index 0000000..a69bbe4 --- /dev/null +++ b/Fuego.cpp @@ -0,0 +1,104 @@ +#include + +bool decodeFuego(byte *bytes, int byteCount) +{ + // If this looks like a Chinese Fuego, Vivax, Classe, NEO, Galanz, Simbio, Beko code... + // Remote control GZ-1002B-E3 + if ( byteCount == 14 && + bytes[0] == 0x23 && + bytes[1] == 0xCB && + bytes[2] == 0x26 ) { + Serial.println(F("Looks like a Fuego etc. protocol")); + + // Check if the checksum matches + byte checksum = 0; + + for (int i=0; i<14; i++) { + checksum += bytes[i]; + } + + checksum >>= 1; + + if (checksum == bytes[13]) { + Serial.println(F("Checksum matches")); + } else { + Serial.println(F("Checksum does not match")); + } + + // Power mode + switch (bytes[5] & 0x04) { + case 0x00: + Serial.println(F("POWER OFF")); + break; + case 0x04: + Serial.println(F("POWER ON")); + break; + } + + // Operating mode + switch (bytes[6] & 0x07) { + case 0x00: //??? + Serial.println(F("MODE AUTO")); + break; + case 0x01: + Serial.println(F("MODE HEAT")); + break; + case 0x03: + Serial.println(F("MODE COOL")); + break; + case 0x02: + Serial.println(F("MODE DRY")); + break; + case 0x07: + Serial.println(F("MODE FAN")); + break; + } + + // Temperature + Serial.print(F("Temperature: ")); + Serial.println(31 - (bytes[7] & 0x0F)); + + // Fan speed + switch (bytes[8] & 0x0F) { + case 0x08: + Serial.println(F("FAN: AUTO")); + break; + case 0x0A: + Serial.println(F("FAN: 1")); + break; + case 0x0B: + Serial.println(F("FAN: 2")); + break; + case 0x0D: + Serial.println(F("FAN: 3")); + break; + } + + // Vertical air direction + Serial.print(F("Vertical air direction: ")); + switch (bytes[8] & 0x38) { + case 0x38: + Serial.println(F("SWING")); + break; + case 0x00: + Serial.println(F("UP")); + break; + case 0x08: + Serial.println(F("MIDDLE UP")); + break; + case 0x18: + Serial.println(F("MIDDLE")); + break; + case 0x20: + Serial.println(F("MIDDLE DOWN")); + break; + case 0x28: + Serial.println(F("DOWN")); + break; + } + + return true; + } + + return false; +} diff --git a/rawirdecode.ino b/rawirdecode.ino index 7b63605..456f85f 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -7,6 +7,8 @@ bool decodeSharp(byte *bytes, int byteCount); bool decodeCarrier(byte *bytes, int byteCount); bool decodePanasonicCKP(byte *bytes, int byteCount); bool decodeHyundai(byte *bytes, int pulseCount); +bool decodeFuego(byte *bytes, int byteCount); + /* Raw IR decoder sketch! @@ -320,7 +322,8 @@ void decodeProtocols() decodeDaikin(bytes, byteCount) || decodeCarrier(bytes, byteCount) || decodePanasonicCKP(bytes, byteCount) || - decodeHyundai(bytes, currentpulse)) ) + decodeHyundai(bytes, currentpulse) || + decodeFuego(bytes, byteCount) )) { Serial.println(F("Unknown protocol")); } From f867fe5d6fcad299deeef3a5df670e324448c13b Mon Sep 17 00:00:00 2001 From: ToniA Date: Mon, 29 Feb 2016 20:12:11 +0200 Subject: [PATCH 32/94] Improve header space detection, simplify Fuego checksum calculation --- Fuego.cpp | 14 ++++++-------- rawirdecode.ino | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Fuego.cpp b/Fuego.cpp index a69bbe4..3bc4da0 100644 --- a/Fuego.cpp +++ b/Fuego.cpp @@ -13,12 +13,10 @@ bool decodeFuego(byte *bytes, int byteCount) // Check if the checksum matches byte checksum = 0; - for (int i=0; i<14; i++) { + for (int i=0; i<13; i++) { checksum += bytes[i]; } - checksum >>= 1; - if (checksum == bytes[13]) { Serial.println(F("Checksum matches")); } else { @@ -59,17 +57,17 @@ bool decodeFuego(byte *bytes, int byteCount) Serial.println(31 - (bytes[7] & 0x0F)); // Fan speed - switch (bytes[8] & 0x0F) { - case 0x08: + switch (bytes[8] & 0x07) { + case 0x00: Serial.println(F("FAN: AUTO")); break; - case 0x0A: + case 0x02: Serial.println(F("FAN: 1")); break; - case 0x0B: + case 0x03: Serial.println(F("FAN: 2")); break; - case 0x0D: + case 0x05: Serial.println(F("FAN: 3")); break; } diff --git a/rawirdecode.ino b/rawirdecode.ino index 456f85f..03af42f 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -208,7 +208,7 @@ void receivePulses(void) { symbols[currentpulse] = 'W'; // Cumulative moving average, see http://en.wikipedia.org/wiki/Moving_average#Cumulative_moving_average space_pause_avg = (highpulse + space_pause_cnt * space_pause_avg) / ++space_pause_cnt; - } else if ( highpulse > SPACE_THRESHOLD_ONE_HEADER ) { + } else if ( (currentpulse > 0 && symbols[currentpulse-1] == 'H') || highpulse > SPACE_THRESHOLD_ONE_HEADER ) { symbols[currentpulse] = 'h'; // Cumulative moving average, see http://en.wikipedia.org/wiki/Moving_average#Cumulative_moving_average space_header_avg = (highpulse + space_header_cnt * space_header_avg) / ++space_header_cnt; From 57d0701bc9f966b8c4430cec4d08a24f1b6f6077 Mon Sep 17 00:00:00 2001 From: ToniA Date: Thu, 15 Sep 2016 20:13:03 +0300 Subject: [PATCH 33/94] Gree decoder --- Gree.cpp | 99 +++++++++++++++++++++++++++++++++++++++++++++++++ rawirdecode.ino | 2 + 2 files changed, 101 insertions(+) create mode 100644 Gree.cpp diff --git a/Gree.cpp b/Gree.cpp new file mode 100644 index 0000000..425816f --- /dev/null +++ b/Gree.cpp @@ -0,0 +1,99 @@ +#include + +bool decodeGree(byte *bytes, int pulseCount) +{ + // If this looks like a Gree code... + if ( pulseCount == 71 ) { + Serial.println(F("Looks like a Gree protocol")); + + // Check if the checksum matches + uint8_t checksum = ( + (bytes[0] & 0x0F) + + (bytes[1] & 0x0F) + + (bytes[2] & 0x0F) + + (bytes[3] & 0x0F) + + (bytes[5] & 0xF0) >> 4 + + (bytes[6] & 0xF0) >> 4 + + (bytes[7] & 0xF0) >> 4 + + 0x0A) & 0xF0; + + if (checksum == bytes[8]) { + Serial.println(F("Checksum matches")); + } else { + Serial.println(F("Checksum does not match")); + } + + // Power mode + switch (bytes[0] & 0x08) { + case 0x00: + Serial.println(F("POWER OFF")); + break; + case 0x08: + Serial.println(F("POWER ON")); + break; + } + + // Operating mode + switch (bytes[0] & 0x07) { + case 0x00: + Serial.println(F("MODE AUTO")); + break; + case 0x04: + Serial.println(F("MODE HEAT")); + break; + case 0x01: + Serial.println(F("MODE COOL")); + break; + case 0x02: + Serial.println(F("MODE DRY")); + break; + case 0x03: + Serial.println(F("MODE FAN")); + break; + } + + // Temperature + Serial.print(F("Temperature: ")); + Serial.println((bytes[1] & 0x0F) + 16); + + // Fan speed + switch (bytes[0] & 0x30) { + case 0x00: + Serial.println(F("FAN: AUTO")); + break; + case 0x10: + Serial.println(F("FAN: 1")); + break; + case 0x20: + Serial.println(F("FAN: 2")); + break; + case 0x30: + Serial.println(F("FAN: 3")); + break; + } + + // Sleep mode + switch (bytes[0] & 0x80) { + case 0x80: + Serial.println(F("SLEEP: ON")); + break; + case 0x00: + Serial.println(F("SLEEP: OFF")); + break; + } + + // Air direction + switch (bytes[0] & 0x40) { + case 0x40: + Serial.println(F("SWING: ON")); + break; + case 0x00: + Serial.println(F("SWING: OFF")); + break; + } + + return true; + } + + return false; +} diff --git a/rawirdecode.ino b/rawirdecode.ino index 03af42f..3a8a461 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -7,6 +7,7 @@ bool decodeSharp(byte *bytes, int byteCount); bool decodeCarrier(byte *bytes, int byteCount); bool decodePanasonicCKP(byte *bytes, int byteCount); bool decodeHyundai(byte *bytes, int pulseCount); +bool decodeGree(byte *bytes, int pulseCount); bool decodeFuego(byte *bytes, int byteCount); @@ -323,6 +324,7 @@ void decodeProtocols() decodeCarrier(bytes, byteCount) || decodePanasonicCKP(bytes, byteCount) || decodeHyundai(bytes, currentpulse) || + decodeGree(bytes, currentpulse) || decodeFuego(bytes, byteCount) )) { Serial.println(F("Unknown protocol")); From 6b161a4be87d6c739b5e556b679ae5c526e2ecdb Mon Sep 17 00:00:00 2001 From: ToniA Date: Sat, 17 Sep 2016 18:08:19 +0300 Subject: [PATCH 34/94] Carrier decoder for A/C only Carrier model --- Carrier.cpp | 71 ++++++++++++++++++++++++++++++++++++++++++++++--- rawirdecode.ino | 1 + 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/Carrier.cpp b/Carrier.cpp index f8508ac..1c6a0d2 100644 --- a/Carrier.cpp +++ b/Carrier.cpp @@ -12,7 +12,6 @@ (byte & 0x02 ? 1 : 0), \ (byte & 0x01 ? 1 : 0) - byte bitReverse(byte x) { // 01010101 | 10101010 @@ -24,11 +23,11 @@ byte bitReverse(byte x) return x; } -bool decodeCarrier(byte *bytes, int byteCount) +bool decodeCarrier1(byte *bytes, int byteCount) { // If this looks like a Carrier code... if ( byteCount == 18 && bytes[0] == 0x4F && bytes[1] == 0xB0 && (memcmp(bytes, bytes+9, 9) == 0)) { - Serial.println(F("Looks like a Carrier protocol")); + Serial.println(F("Looks like a Carrier protocol #1")); // Check if the checksum matches byte checksum = 0; @@ -143,3 +142,69 @@ bool decodeCarrier(byte *bytes, int byteCount) return false; } + +bool decodeCarrier2(byte *bytes, int byteCount) +{ + // If this looks like a Carrier code... + if ( byteCount == 12 && bytes[0] == 0x4D && bytes[1] == 0xB2 && (memcmp(bytes, bytes+6, 6) == 0)) { + Serial.println(F("Looks like a Carrier protocol #2")); + + switch (bytes[2] & 0x07) { + case 0x05: + Serial.println(F("FAN: AUTO")); + break; + case 0x00: + Serial.println(F("FAN: AUTO/DRY AUTO")); + break; + case 0x01: + Serial.println(F("FAN: 1")); + break; + case 0x02: + Serial.println(F("FAN: 2")); + break; + case 0x04: + Serial.println(F("FAN: 3")); + break; + } + + switch (bytes[4] & 0x30) { + case 0x10: + Serial.println(F("MODE: AUTO")); + break; + case 0x00: + Serial.println(F("MODE: COOL")); + break; + case 0x20: + if ((bytes[4] & 0x0F) == 0x07) { + Serial.println(F("MODE: FAN")); + } else { + Serial.println(F("MODE: DRY")); + } + break; + } + + const byte temperatures[] = { 17, 28, 24, 25, 20, 29, 21, 31, 18, 27, 23, 26, 19, 30, 22 }; + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 + + Serial.print(F("Temperature: ")); + Serial.println(temperatures[bytes[4] & 0x0F]); + + // Check if the checksum matches + uint8_t checksum = ~bytes[4]; + + if (checksum == bytes[5]) { + Serial.println(F("Checksum matches")); + } else { + Serial.println(F("Checksum does not match")); + } + + return true; + } + + return false; +} + +bool decodeCarrier(byte *bytes, int byteCount) +{ + return decodeCarrier1(bytes, byteCount) || decodeCarrier2(bytes, byteCount); +} diff --git a/rawirdecode.ino b/rawirdecode.ino index 3a8a461..7ee38f0 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -322,6 +322,7 @@ void decodeProtocols() decodeSharp(bytes, byteCount) || decodeDaikin(bytes, byteCount) || decodeCarrier(bytes, byteCount) || + decodeCarrier(bytes, byteCount) || decodePanasonicCKP(bytes, byteCount) || decodeHyundai(bytes, currentpulse) || decodeGree(bytes, currentpulse) || From e7ae540798d30f929975a6e670644ad50287c25c Mon Sep 17 00:00:00 2001 From: ToniA Date: Mon, 19 Sep 2016 20:08:36 +0300 Subject: [PATCH 35/94] Carrier OFF code --- Carrier.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Carrier.cpp b/Carrier.cpp index 1c6a0d2..2ce3723 100644 --- a/Carrier.cpp +++ b/Carrier.cpp @@ -149,6 +149,15 @@ bool decodeCarrier2(byte *bytes, int byteCount) if ( byteCount == 12 && bytes[0] == 0x4D && bytes[1] == 0xB2 && (memcmp(bytes, bytes+6, 6) == 0)) { Serial.println(F("Looks like a Carrier protocol #2")); + switch (bytes[2] & 0x20) { + case 0x00: + Serial.println(F("POWER: OFF")); + break; + case 0x20: + Serial.println(F("POWER: ON")); + break; + } + switch (bytes[2] & 0x07) { case 0x05: Serial.println(F("FAN: AUTO")); From 195c82d32ada59108562e3466e302fec4dca8feb Mon Sep 17 00:00:00 2001 From: ToniA Date: Sat, 24 Sep 2016 16:43:15 +0300 Subject: [PATCH 36/94] Second Carrier checksum byte --- Carrier.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Carrier.cpp b/Carrier.cpp index 2ce3723..c393fbb 100644 --- a/Carrier.cpp +++ b/Carrier.cpp @@ -199,9 +199,10 @@ bool decodeCarrier2(byte *bytes, int byteCount) Serial.println(temperatures[bytes[4] & 0x0F]); // Check if the checksum matches - uint8_t checksum = ~bytes[4]; + uint8_t checksum1 = ~bytes[2]; + uint8_t checksum2 = ~bytes[4]; - if (checksum == bytes[5]) { + if (checksum1 == bytes[3] && checksum2 == bytes[5]) { Serial.println(F("Checksum matches")); } else { Serial.println(F("Checksum does not match")); From 79915f6bc04b0971569ce30ded7e24dec68d87cb Mon Sep 17 00:00:00 2001 From: ToniA Date: Sun, 25 Sep 2016 21:04:16 +0300 Subject: [PATCH 37/94] Toshiba decoder --- Toshiba.cpp | 78 +++++++++++++++++++++++++++++++++++++++++++++++++ rawirdecode.ino | 4 ++- 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 Toshiba.cpp diff --git a/Toshiba.cpp b/Toshiba.cpp new file mode 100644 index 0000000..9c1fb1c --- /dev/null +++ b/Toshiba.cpp @@ -0,0 +1,78 @@ +#include + +byte bitReverse(byte x); + +// Toshiba RAS-10PKVP-ND + +bool decodeToshiba(byte *bytes, int byteCount) +{ + // If this looks like a Sharp code... + if ( byteCount == 9 && bytes[0] == 0x4F && bytes[1] == 0xB0 && bytes[2] == 0xC0 && bytes[3] == 0x3F && bytes[4] == 0x80 ) { + Serial.println(F("Looks like a Toshiba protocol")); + + // Power & operating mode + switch (bytes[6] & 0xF0) { + case 0x00: + Serial.println(F("MODE AUTO")); + break; + case 0x80: + Serial.println(F("MODE COOL")); + break; + case 0x40: + Serial.println(F("MODE DRY")); + break; + case 0xC0: + Serial.println(F("MODE HEAT")); + break; + case 0xE0: + Serial.println(F("POWER OFF")); + break; + } + + // Temperature + Serial.print(F("Temperature: ")); + Serial.println((bitReverse((bytes[5] & 0x07)) >> 4) + 17); + + // Fan speed + switch (bytes[6] & 0x07) { + case 0x00: + Serial.println(F("FAN: AUTO")); + break; + case 0x02: + Serial.println(F("FAN: 1")); + break; + case 0x06: + Serial.println(F("FAN: 2")); + break; + case 0x01: + Serial.println(F("FAN: 3")); + break; + case 0x05: + Serial.println(F("FAN: 4")); + break; + case 0x03: + Serial.println(F("FAN: 5")); + break; + } + + // Check if the checksum matches + byte checksum = 0x00; + + for (byte i = 0; i < 8; i++) { + checksum ^= bytes[i]; + } + + Serial.print(F("Checksum '0x")); + Serial.print(checksum, HEX); + + if ( bytes[8] == checksum ) { + Serial.println(F("' matches")); + } else { + Serial.print(F(" does not match ")); + Serial.println(bytes[8], HEX); + } + return true; + } + + return false; +} diff --git a/rawirdecode.ino b/rawirdecode.ino index 7ee38f0..45582b6 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -9,6 +9,7 @@ bool decodePanasonicCKP(byte *bytes, int byteCount); bool decodeHyundai(byte *bytes, int pulseCount); bool decodeGree(byte *bytes, int pulseCount); bool decodeFuego(byte *bytes, int byteCount); +bool decodeToshiba(byte *bytes, int byteCount); /* Raw IR decoder sketch! @@ -326,7 +327,8 @@ void decodeProtocols() decodePanasonicCKP(bytes, byteCount) || decodeHyundai(bytes, currentpulse) || decodeGree(bytes, currentpulse) || - decodeFuego(bytes, byteCount) )) + decodeFuego(bytes, byteCount) || + decodeToshiba(bytes, byteCount) )) { Serial.println(F("Unknown protocol")); } From 44072f26ee43f5f40731dc81207de095770863cd Mon Sep 17 00:00:00 2001 From: ToniA Date: Tue, 27 Sep 2016 18:31:33 +0300 Subject: [PATCH 38/94] Fix Fuego etc. vertical air direction codes --- Fuego.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Fuego.cpp b/Fuego.cpp index 3bc4da0..a928ded 100644 --- a/Fuego.cpp +++ b/Fuego.cpp @@ -75,13 +75,13 @@ bool decodeFuego(byte *bytes, int byteCount) // Vertical air direction Serial.print(F("Vertical air direction: ")); switch (bytes[8] & 0x38) { - case 0x38: - Serial.println(F("SWING")); - break; case 0x00: - Serial.println(F("UP")); + Serial.println(F("AUTO")); break; case 0x08: + Serial.println(F("UP")); + break; + case 0x10: Serial.println(F("MIDDLE UP")); break; case 0x18: @@ -93,6 +93,9 @@ bool decodeFuego(byte *bytes, int byteCount) case 0x28: Serial.println(F("DOWN")); break; + case 0x38: + Serial.println(F("SWING")); + break; } return true; From f6b4d1b81b41b0bf9de06ece67991afa86058dd5 Mon Sep 17 00:00:00 2001 From: ToniA Date: Fri, 30 Sep 2016 19:26:50 +0300 Subject: [PATCH 39/94] Sharp -> Toshiba comment in Toshiba decoder --- Toshiba.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Toshiba.cpp b/Toshiba.cpp index 9c1fb1c..27acc7a 100644 --- a/Toshiba.cpp +++ b/Toshiba.cpp @@ -6,7 +6,7 @@ byte bitReverse(byte x); bool decodeToshiba(byte *bytes, int byteCount) { - // If this looks like a Sharp code... + // If this looks like a Toshiba code... if ( byteCount == 9 && bytes[0] == 0x4F && bytes[1] == 0xB0 && bytes[2] == 0xC0 && bytes[3] == 0x3F && bytes[4] == 0x80 ) { Serial.println(F("Looks like a Toshiba protocol")); From f6686dbbac7755d7ea4f5901377d3ffc102121e2 Mon Sep 17 00:00:00 2001 From: ToniA Date: Tue, 7 Feb 2017 21:11:34 +0200 Subject: [PATCH 40/94] Initial Nibe decoding (not fully working yet) --- Nibe.cpp | 142 ++++++++++++++++++++++++++++++++++++++++++++++++ rawirdecode.ino | 13 +++-- 2 files changed, 149 insertions(+), 6 deletions(-) create mode 100644 Nibe.cpp diff --git a/Nibe.cpp b/Nibe.cpp new file mode 100644 index 0000000..4163e7f --- /dev/null +++ b/Nibe.cpp @@ -0,0 +1,142 @@ +#include +bool decodeNibe(char* symbols, int bitCount) +{ + byte bytes[11]; + byte currentBit = 0; + byte currentByte = 0; + byte byteCount = 0; + + // If this looks like a Nibe code... + if ( bitCount == 93 ) + { + // Decode the string of bits to a byte array + for (uint16_t i = 5; i < bitCount; i++) { + if (symbols[i] == '0' || symbols[i] == '1') { + currentByte >>= 1; + currentBit++; + + if (symbols[i] == '1') { + currentByte |= 0x80; + } + + Serial.print(symbols[i]); + + if (currentBit >= 8) { + bytes[byteCount++] = currentByte; + currentBit = 0; + currentByte = 0; + + Serial.print(" "); + } + } + } + + // Print the byte array + Serial.print(F("\nNibe bytes: ")); + for (int i = 0; i < byteCount; i++) { + if (bytes[i] < 0x10) { + Serial.print(F("0")); + } + Serial.print(bytes[i], HEX); + if ( i < byteCount - 1 ) { + Serial.print(F(",")); + } + } + Serial.println(); + + // Power mode + switch (bytes[9] & 0x08) { + case 0x00: + Serial.println(F("POWER OFF")); + break; + case 0x08: + Serial.println(F("POWER ON")); + break; + } + + // Operating mode + switch (bytes[2] & 0x0E) { + case 0x08: + Serial.println(F("MODE HEAT")); + break; + case 0x04: + Serial.println(F("MODE COOL")); + break; + case 0x02: + Serial.println(F("MODE DRY")); + break; + } + + // Temperature + + byte temperature = ((bytes[2] & 0xF0) >> 4) | ((bytes[3] & 0x01) << 5) - 12; + Serial.print(F("Temperature: ")); + Serial.println(temperature); + + // Fan speed + switch (bytes[3] & 0x06) { + case 0x00: + Serial.println(F("FAN: AUTO")); + break; + case 0x02: + Serial.println(F("FAN: 1")); + break; + case 0x04: + Serial.println(F("FAN: 2")); + break; + case 0x06: + Serial.println(F("FAN: 3")); + break; + } + + // Flap position + switch (bytes[4] & 0x0F) { + case 0x00: + Serial.println(F("FLAP: AUTO")); + break; + case 0x01: + Serial.println(F("FLAP: 1")); + break; + case 0x02: + Serial.println(F("FLAP: 2")); + break; + case 0x03: + Serial.println(F("FLAP: 3")); + break; + case 0x04: + Serial.println(F("FLAP: 4")); + break; + case 0x05: + Serial.println(F("FLAP: 5")); + break; + case 0x06: + Serial.println(F("FLAP: 6")); + break; + case 0x07: + Serial.println(F("FLAP: All")); + break; + } + + // Check if the checksum matches + byte checksum = 0x00; + + for (byte i = 0; i < 10; i++) { + checksum += bytes[i]; + } + + Serial.print(F("Checksum: ")); + Serial.println(checksum, HEX); + if ( bytes[10] == checksum ) { + Serial.println(F("Checksum matches")); + } else { + Serial.println(F("Checksum does not match")); + } + + return true; + } + return false; +} + + + + diff --git a/rawirdecode.ino b/rawirdecode.ino index 45582b6..3b277e1 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -10,6 +10,7 @@ bool decodeHyundai(byte *bytes, int pulseCount); bool decodeGree(byte *bytes, int pulseCount); bool decodeFuego(byte *bytes, int byteCount); bool decodeToshiba(byte *bytes, int byteCount); +bool decodeNibe(char* symbols, int bitCount); /* Raw IR decoder sketch! @@ -35,8 +36,7 @@ bool decodeToshiba(byte *bytes, int byteCount); #define IRpin_PIN PINE #define IRpin 4 #else -#define IRpin_PIN PIND -#define IRpin 2 +#error "This software requires Arduino Mega (ATmega 2560 or ATmega 1280)" #endif // the maximum pulse we'll listen for - 65 milliseconds is a long time @@ -67,15 +67,15 @@ uint16_t space_header_cnt = 0; uint32_t space_pause_avg = 0; uint16_t space_pause_cnt = 0; -// we will store up to 500 symbols -char symbols[500]; // decoded symbols +// we will store up to 1024 symbols +char symbols[1024]; // decoded symbols uint16_t currentpulse = 0; // index for pulses we're storing uint8_t modelChoice = 0; // Decoded bytes byte byteCount = 0; -byte bytes[32]; +byte bytes[128]; void setup(void) { @@ -328,7 +328,8 @@ void decodeProtocols() decodeHyundai(bytes, currentpulse) || decodeGree(bytes, currentpulse) || decodeFuego(bytes, byteCount) || - decodeToshiba(bytes, byteCount) )) + decodeToshiba(bytes, byteCount) || + decodeNibe(symbols, currentpulse))) { Serial.println(F("Unknown protocol")); } From c1a53286b467977a35cdcfff38e200ca0f089c2c Mon Sep 17 00:00:00 2001 From: Toni Date: Sat, 11 Feb 2017 14:21:07 +0200 Subject: [PATCH 41/94] Description of mode '9' --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 3150287..9719c0e 100644 --- a/README.md +++ b/README.md @@ -14,5 +14,8 @@ Shows the timings, the symbols, and also the decoded signal for certain air cond * 'H' and 'h' should be there only in pairs 'Hh' * 'H' stands for 'header mark' and 'h' for 'header space' * Point your IR remote to the IR receiver and send the code +* Mode '9' can be used to decode known signals, in that case you can send the symbols from the terminal, like entering this: + + Hh001101011010111100000111001001010100000000000111000000001111111011010100000001000111001011 ![Schema](arduino_irreceiver.png) From 39e65c9fa677e8c14c57d73a40c1b1fc45a384d9 Mon Sep 17 00:00:00 2001 From: ToniA Date: Sat, 11 Feb 2017 14:57:42 +0200 Subject: [PATCH 42/94] Remove the requirement to have an Arduino Mega --- rawirdecode.ino | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rawirdecode.ino b/rawirdecode.ino index 3b277e1..e93c4df 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -36,7 +36,8 @@ bool decodeNibe(char* symbols, int bitCount); #define IRpin_PIN PINE #define IRpin 4 #else -#error "This software requires Arduino Mega (ATmega 2560 or ATmega 1280)" +#define IRpin_PIN PIND +#define IRpin 2 #endif // the maximum pulse we'll listen for - 65 milliseconds is a long time From 3eb24974d8de5b97035f88b7bbc8f7e3f8a97aa5 Mon Sep 17 00:00:00 2001 From: ToniA Date: Sat, 18 Feb 2017 14:38:10 +0200 Subject: [PATCH 43/94] Hitachi decoder --- Hitachi.cpp | 109 ++++++++++++++++++++++++++++++++++++++++++++++++ rawirdecode.ino | 3 +- 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 Hitachi.cpp diff --git a/Hitachi.cpp b/Hitachi.cpp new file mode 100644 index 0000000..4cdd5b9 --- /dev/null +++ b/Hitachi.cpp @@ -0,0 +1,109 @@ +#include // Hitachi RAR-5E1 remote + +bool decodeHitachi(byte *bytes, int byteCount) +{ + if (byteCount == 28 && bytes[0] == 0x01 && bytes[1] == 0x10) { + Serial.println(F("Looks like a Hitachi protocol")); + + // Operating mode + Serial.print(F("Mode: ")); + switch (bytes[10]) { + case 0x02: + Serial.println(F("Auto")); + break; + case 0x03: + Serial.println(F("Heat")); + break; + case 0x04: + Serial.println(F("Cool")); + break; + case 0x05: + Serial.println(F("Dry")); + break; + } + + // Fan speed + Serial.print(F("Fan speed: ")); + switch (bytes[13]) { + case 0x01: + Serial.println(F("Auto")); + break; + case 0x02: + Serial.println(F("1")); + break; + case 0x03: + Serial.println(F("2")); + break; + case 0x04: + Serial.println(F("3")); + break; + case 0x05: + Serial.println(F("4")); + break; + } + + // Vertical air swing + Serial.print(F("Vertical air swing: ")); + switch (bytes[14] & 0x01) { + case 0x00: + Serial.println(F("Off")); + break; + case 0x01: + Serial.println(F("On")); + break; + } + + // Horisontal air Swing + Serial.print(F("Horisontal air swing: ")); + switch (bytes[15] & 0x01) { + case 0x00: + Serial.println(F("Off")); + break; + case 0x01: + Serial.println(F("On")); + break; + } + + // Power + Serial.print(F("Power: ")); + switch (bytes[17]) { + case 0x00: + Serial.println(F("Off")); + break; + case 0x80: + Serial.println(F("On")); + break; + } + + // ECO mode + Serial.print(F("ECO: ")); + switch (bytes[25]) { + case 0x00: + Serial.println(F("Off")); + break; + case 0x02: + Serial.println(F("On")); + break; + } + + //Temperature + Serial.print("Temperature: "); + Serial.println(bytes[11] >> 1); + + int checksum = 1086; + + for (byte i = 0; i < 27; i++) { + checksum -= bytes[i]; + } + + if ( bytes[27] == checksum ) { + Serial.println(F("Checksum matches")); + } else { + Serial.println(F("Checksum does not match")); + } + return true; + } + + return false; +} + diff --git a/rawirdecode.ino b/rawirdecode.ino index e93c4df..9d80e5a 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -11,6 +11,7 @@ bool decodeGree(byte *bytes, int pulseCount); bool decodeFuego(byte *bytes, int byteCount); bool decodeToshiba(byte *bytes, int byteCount); bool decodeNibe(char* symbols, int bitCount); +bool decodeHitachi(byte *bytes, int byteCount); /* Raw IR decoder sketch! @@ -330,7 +331,7 @@ void decodeProtocols() decodeGree(bytes, currentpulse) || decodeFuego(bytes, byteCount) || decodeToshiba(bytes, byteCount) || - decodeNibe(symbols, currentpulse))) + decodeHitachi(bytes, byteCount))) { Serial.println(F("Unknown protocol")); } From 21edfa6cd404fecd8159f105f8e8f9c828c6b0fe Mon Sep 17 00:00:00 2001 From: ToniA Date: Sat, 18 Feb 2017 15:01:59 +0200 Subject: [PATCH 44/94] Nibe decoder. Note that the checksum calculation is not ready yet --- Nibe.cpp | 139 +++++++++++++++++++++++++++++++++++------------- rawirdecode.ino | 3 +- 2 files changed, 104 insertions(+), 38 deletions(-) diff --git a/Nibe.cpp b/Nibe.cpp index 4163e7f..a168f9e 100644 --- a/Nibe.cpp +++ b/Nibe.cpp @@ -1,5 +1,6 @@ #include -bool decodeNibe(char* symbols, int bitCount) + +bool decodeNibe(byte *originalBytes, char* symbols, int bitCount) { byte bytes[11]; byte currentBit = 0; @@ -44,7 +45,27 @@ bool decodeNibe(char* symbols, int bitCount) } Serial.println(); - // Power mode + // Night (low night temp I think) + switch (bytes[9] & 0x01) { + case 0x00: + Serial.println(F("NIGHT MODE OFF")); + break; + case 0x01: + Serial.println(F("NIGHT MODE ON")); + break; + } + + // Filter + switch (bytes[9] & 0x04) { + case 0x00: + Serial.println(F("FILTER OFF")); + break; + case 0x04: + Serial.println(F("FILTER ON")); + break; + } + + // Power mode switch (bytes[9] & 0x08) { case 0x00: Serial.println(F("POWER OFF")); @@ -54,89 +75,133 @@ bool decodeNibe(char* symbols, int bitCount) break; } - // Operating mode - switch (bytes[2] & 0x0E) { - case 0x08: - Serial.println(F("MODE HEAT")); + // iFeel + switch (bytes[9] & 0x32) { + case 0x00: + Serial.println(F("IFEEL OFF")); break; - case 0x04: + case 0x32: + Serial.println(F("IFEEL ON")); + break; + } + + // Operating mode + switch (bytes[2] & 0x0F) { + case 0x00: Serial.println(F("MODE COOL")); break; case 0x02: Serial.println(F("MODE DRY")); break; - } - - // Temperature - - byte temperature = ((bytes[2] & 0xF0) >> 4) | ((bytes[3] & 0x01) << 5) - 12; - Serial.print(F("Temperature: ")); - Serial.println(temperature); + case 0x04: + Serial.println(F("MODE COOL AUTO")); + break; + case 0x08: + Serial.println(F("MODE HEAT")); + break; + } // Fan speed + Serial.print(F("Fan speed: ")); switch (bytes[3] & 0x06) { case 0x00: - Serial.println(F("FAN: AUTO")); + Serial.println(F("AUTO")); break; case 0x02: - Serial.println(F("FAN: 1")); + Serial.println(F("1")); break; case 0x04: - Serial.println(F("FAN: 2")); + Serial.println(F("2")); break; case 0x06: - Serial.println(F("FAN: 3")); + Serial.println(F("3")); break; } - // Flap position - switch (bytes[4] & 0x0F) { + // Vertical air direction + Serial.print(F("Vertical air direction: ")); + switch (bytes[4]) { case 0x00: - Serial.println(F("FLAP: AUTO")); + Serial.println(F("AUTO")); break; case 0x01: - Serial.println(F("FLAP: 1")); + Serial.println(F("UP")); break; case 0x02: - Serial.println(F("FLAP: 2")); + Serial.println(F("MIDDLE UP")); break; case 0x03: - Serial.println(F("FLAP: 3")); + Serial.println(F("MIDDLE")); break; case 0x04: - Serial.println(F("FLAP: 4")); + Serial.println(F("MIDDLE")); break; case 0x05: - Serial.println(F("FLAP: 5")); + Serial.println(F("MIDDLE DOWN")); break; case 0x06: - Serial.println(F("FLAP: 6")); + Serial.println(F("DOWN")); break; case 0x07: - Serial.println(F("FLAP: All")); + Serial.println(F("SWING")); break; } - // Check if the checksum matches - byte checksum = 0x00; + Serial.print("Temperature: "); + int8_t temperature = (((bytes[2] & 0xF0) >> 4) + ((bytes[3] & 0x01) << 4)) + 4; + Serial.println(temperature); + - for (byte i = 0; i < 10; i++) { - checksum += bytes[i]; + + // TODO: How to calculate the checksum? This algorithm seems to get bits 0, 6 and 7 right, but how about the other bits? + // Here's some material for testing: + // + // Hh001101011010111100000101100001010101000000000111000000001111100110100000000001000101001001 + // Hh001101011010111100000111100001010101000000000111000000001111100110100000000001000101001011 + // Hh001101011010111100000100010001010101000000000111000000001110010110100000000001000111011000 + // Hh001101011010111100000110010001010101000000000111000000001110010110100000000001000111011010 + // Hh001101011010111100000101010001010101000000000111000000001110010110100000000001000111011001 + // Hh001101011010111100000111010001010101000000000111000000001110010110100000000001000111011011 + // Hh001101011010111100000100110001010101000000000111000000001110010110100000000001000100111000 + // Hh001101011010111100000110110001010101000000000111000000001110010110100000000001000100111010 + // Hh001101011010111100000101110001010101000000000111000000001110010110100000000001000100111001 + // Hh001101011010111100000111110001010101000000000111000000001110010110100000000001000100111011 + // Hh001101011010111100000100001001010101000000000111000000001111010110100000000001000110100100 + // Hh001101011010111100000110001001010101000000000111000000001111010110100000000001000110100110 + // Hh001101011010111100000101001001010101000000000111000000001111010110100000000001000110100101 + // Hh001101011010111100000111001001010101000000000111000000001111010110100000000001000110100111 + // Hh001101011010111100000100101001010101000000000111000000001111010110100000000001000101100100 + // Hh001101011010111100000110101001010101000000000111000000001111010110100000000001000101100110 + // Hh001101011010111100000101101001010101000000000111000000001111010110100000000001000101100101 + // Hh001101011010111100000111101001010101000000000111000000001111110110100000000001000101101111 + // Hh001101011010111100000100011001010101000000000111000000001111110110100000000001000111101100 + // Hh001101011010111100000110011001010101000000000111000000001111110110100000000001000111101110 + // Hh001101011010111100000101011001010101000000000111000000001111110110100000000001000111101101 + // Hh001101011010111100000111011001010101000000000111000000001111110110100000000001000111101111 + // Hh001101011010111100000100111001010101000000000111000000001111110110100000000001000100011100 + + byte checksum = 0b10011010; + + for (int i=0; i<10; i++) { + checksum ^= originalBytes[i]; } - Serial.print(F("Checksum: ")); - Serial.println(checksum, HEX); - if ( bytes[10] == checksum ) { - Serial.println(F("Checksum matches")); + Serial.print(bytes[10], BIN); + Serial.println(F(" <- original checksum")); + Serial.print(checksum, BIN); + Serial.println(F(" <- calculated checksum")); + + if (((checksum & 0b11000000) == (bytes[10] & 0b11000000)) && ((checksum & 0b00000001) == (bytes[10] & 0b00000001))) { + Serial.println(F("Checksum matches at least partially")); } else { Serial.println(F("Checksum does not match")); } return true; } + return false; } - diff --git a/rawirdecode.ino b/rawirdecode.ino index 9d80e5a..2c3b442 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -10,7 +10,7 @@ bool decodeHyundai(byte *bytes, int pulseCount); bool decodeGree(byte *bytes, int pulseCount); bool decodeFuego(byte *bytes, int byteCount); bool decodeToshiba(byte *bytes, int byteCount); -bool decodeNibe(char* symbols, int bitCount); +bool decodeNibe(byte *bytes, char* symbols, int bitCount); bool decodeHitachi(byte *bytes, int byteCount); @@ -331,6 +331,7 @@ void decodeProtocols() decodeGree(bytes, currentpulse) || decodeFuego(bytes, byteCount) || decodeToshiba(bytes, byteCount) || + decodeNibe(bytes, symbols, currentpulse) || decodeHitachi(bytes, byteCount))) { Serial.println(F("Unknown protocol")); From 11384431a04f04fa4b95ca263e68c02d7998ac27 Mon Sep 17 00:00:00 2001 From: ToniA Date: Tue, 28 Feb 2017 08:35:39 +0200 Subject: [PATCH 45/94] Samsung timings --- rawirdecode.ino | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/rawirdecode.ino b/rawirdecode.ino index 45582b6..1cf828a 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -86,7 +86,8 @@ void setup(void) { Serial.println(F("* '1' for Panasonic DKE>, Mitsubishi Electric, Fujitsu etc. codes")); Serial.println(F("* '2' for Panasonic CKP, Midea etc. codes")); Serial.println(F("* '3' for Mitsubishi Heavy etc. codes")); - Serial.println(F("* '4' for Hyyndai etc. codes")); + Serial.println(F("* '4' for Hyundai etc. codes")); + Serial.println(F("* '5' for Samsung etc. codes")); Serial.println(F("* '9' for entering the bit sequence on the serial monitor (instead of the IR receiver)")); Serial.println(); Serial.print(F("Enter choice: ")); @@ -110,6 +111,9 @@ void setup(void) { case '4': modelChoice = 4; break; + case '5': + modelChoice = 5; + break; case '9': modelChoice = 9; break; @@ -141,6 +145,11 @@ void setup(void) { SPACE_THRESHOLD_ZERO_ONE = 800; SPACE_THRESHOLD_ONE_HEADER = 2400; SPACE_THRESHOLD_HEADER_PAUSE = 8000; + } else if (modelChoice == 5) { + MARK_THRESHOLD_BIT_HEADER = 2000; + SPACE_THRESHOLD_ZERO_ONE = 800; + SPACE_THRESHOLD_ONE_HEADER = 2400; + SPACE_THRESHOLD_HEADER_PAUSE = 10000; } } From 30ed80ba2a6804cfab64b92abc6d3dc328ff62e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Bj=C3=B8rlykke=20Kristiansen?= Date: Tue, 28 Feb 2017 23:42:55 +0100 Subject: [PATCH 46/94] Fujitsu decoder based on AR-RAE1E remote --- Fujitsu.cpp | 150 ++++++++++++++++++++++++++++++++++++++++++++++++ rawirdecode.ino | 2 + 2 files changed, 152 insertions(+) create mode 100644 Fujitsu.cpp diff --git a/Fujitsu.cpp b/Fujitsu.cpp new file mode 100644 index 0000000..bef86b8 --- /dev/null +++ b/Fujitsu.cpp @@ -0,0 +1,150 @@ +#include + +static byte header[] = {0x14,0x63,0x00,0x10,0x10}; + +bool decodeFujitsu(byte *bytes, int byteCount) +{ + if(byteCount > sizeof(header) && memcmp(bytes, header, sizeof(header)) == 0) { + Serial.println(F("Looks like a Fujitsu protocol")); + + byte checksum = 0; + + //Calculate checksum + if (byteCount == 7) { + checksum = 0xFF - bytes[5]; + } else { + for (int i=7; i> 4) + 16); + + // Operating mode + switch (bytes[9] & 0xFF) { + case 0x00: + Serial.println(F("MODE AUTO")); + break; + case 0x01: + Serial.println(F("MODE COOL")); + break; + case 0x02: + Serial.println(F("MODE DRY")); + break; + case 0x03: + Serial.println(F("MODE FAN")); + break; + case 0x04: + Serial.println(F("MODE HEAT")); + break; + case 0x08: + Serial.println(F("MODE COIL DRY")); + break; + case 0x0B: + Serial.println(F("MODE 10c HEAT")); + break; + case 0x10: + Serial.println(F("MODE SLEEP")); + break; + case 0x20: + Serial.println(F("SET TIMER (OFF)")); + break; + case 0x30: + Serial.println(F("SET TIMER (ON)")); + break; + case 0x40: + Serial.println(F("SET TIMER (OFF -> ON)")); + break; + default: + Serial.println(F("MODE unknown")); + break; + } + + // Fan speed + switch (bytes[10] & 0x07) { + case 0x00: + Serial.println(F("FAN AUTO")); + break; + case 0x01: + Serial.println(F("FAN 1")); + break; + case 0x02: + Serial.println(F("FAN 2")); + break; + case 0x03: + Serial.println(F("FAN 3")); + break; + case 0x04: + Serial.println(F("FAN 4")); + break; + default: + Serial.println(F("FAN unknown")); + break; + } + + // Fan swing + switch (bytes[10] & 0xF0) { + case 0x10: + Serial.println(F("VANE: SWING")); + break; + case 0x00: + Serial.println(F("VANE: STILL")); + break; + default: + Serial.println(F("VANE: unknown")); + break; + } + + /* + * Not sure what's going on here, but incrementing time on the remote by 5 + * minutes, increments this value by 5 as well. + */ + int time = (int)bytes[12] << 8 + bytes[11]; + Serial.print("Time: "); + Serial.println(time); + + //Economy mode? I'm guessing more modes are encoded here on fancier models. + switch (bytes[14]) { + case 0x00: + Serial.println(F("ECONOMY: ON")); + break; + case 0x20: + Serial.println(F("ECONOMY: OFF")); + break; + default: + Serial.println(F("ECONOMY: unknown")); + break; + } + + return true; + } + + return false; +} diff --git a/rawirdecode.ino b/rawirdecode.ino index ed0d9ae..1cea997 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -1,6 +1,7 @@ #include bool decodeMitsubishiElectric(byte *bytes, int byteCount); +bool decodeFujitsu(byte *bytes, int byteCount); bool decodeMitsubishiHeavy(byte *bytes, int byteCount); bool decodeDaikin(byte *bytes, int byteCount); bool decodeSharp(byte *bytes, int byteCount); @@ -330,6 +331,7 @@ void decodeProtocols() Serial.println(F("Decoding known protocols...")); if ( ! (decodeMitsubishiElectric(bytes, byteCount) || + decodeFujitsu(bytes, byteCount) || decodeMitsubishiHeavy(bytes, byteCount) || decodeSharp(bytes, byteCount) || decodeDaikin(bytes, byteCount) || From ea841d19d95018284a5f5458cb590b549c5509d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Bj=C3=B8rlykke=20Kristiansen?= Date: Wed, 1 Mar 2017 22:21:47 +0100 Subject: [PATCH 47/94] Extended Fujitsu decoder to support AR-RAH2E --- Fujitsu.cpp | 43 ++++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/Fujitsu.cpp b/Fujitsu.cpp index bef86b8..cb7fac2 100644 --- a/Fujitsu.cpp +++ b/Fujitsu.cpp @@ -36,13 +36,20 @@ bool decodeFujitsu(byte *bytes, int byteCount) Serial.println(F("POWER ON")); break; case 0x6C: - Serial.println(F("SWING SET")); + Serial.println(F("SWING SET VERTICAL")); + break; + case 0x79: + Serial.println(F("SWING SET HORIZONTAL")); break; default: Serial.println(F("POWER unknown")); break; } + if(byteCount < 8) { + return true; + } + // Temperature Serial.print(F("Temperature: ")); Serial.println((bytes[8] >> 4) + 16); @@ -93,16 +100,16 @@ bool decodeFujitsu(byte *bytes, int byteCount) Serial.println(F("FAN AUTO")); break; case 0x01: - Serial.println(F("FAN 1")); + Serial.println(F("FAN HIGH")); break; case 0x02: - Serial.println(F("FAN 2")); + Serial.println(F("FAN MED")); break; case 0x03: - Serial.println(F("FAN 3")); + Serial.println(F("FAN LOW")); break; case 0x04: - Serial.println(F("FAN 4")); + Serial.println(F("FAN QUIET")); break; default: Serial.println(F("FAN unknown")); @@ -112,7 +119,13 @@ bool decodeFujitsu(byte *bytes, int byteCount) // Fan swing switch (bytes[10] & 0xF0) { case 0x10: - Serial.println(F("VANE: SWING")); + Serial.println(F("VANE: VERTICAL")); + break; + case 0x20: + Serial.println(F("VANE: HORIZONTAL")); + break; + case 0x30: + Serial.println(F("VANE: BOTH")); break; case 0x00: Serial.println(F("VANE: STILL")); @@ -122,16 +135,16 @@ bool decodeFujitsu(byte *bytes, int byteCount) break; } - /* - * Not sure what's going on here, but incrementing time on the remote by 5 - * minutes, increments this value by 5 as well. - */ - int time = (int)bytes[12] << 8 + bytes[11]; - Serial.print("Time: "); - Serial.println(time); + /* + * Not sure what's going on here, but incrementing time on the remote by 5 + * minutes, increments this value by 5 as well. + */ + int time = (int)bytes[12] << 8 + bytes[11]; + Serial.print("Time: "); + Serial.println(time); - //Economy mode? I'm guessing more modes are encoded here on fancier models. - switch (bytes[14]) { + //Economy mode? I'm guessing more modes are encoded here on fancier models. + switch (bytes[14]) { case 0x00: Serial.println(F("ECONOMY: ON")); break; From 00c63d2688fa4bac7ad0f59795ff204c576d8f14 Mon Sep 17 00:00:00 2001 From: ToniA Date: Thu, 2 Mar 2017 12:51:17 +0200 Subject: [PATCH 48/94] Samsung decoder, note that the checksum calculator is not yet fully working --- Samsung.cpp | 97 +++++++++++++++++++++++++++++++++++++++++++++++++ rawirdecode.ino | 4 +- 2 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 Samsung.cpp diff --git a/Samsung.cpp b/Samsung.cpp new file mode 100644 index 0000000..7a5c018 --- /dev/null +++ b/Samsung.cpp @@ -0,0 +1,97 @@ +#include + +byte bitReverse(byte x); + +bool decodeSamsung(byte *bytes, int byteCount) +{ + // If this looks like a Samsung code... + if (bytes[0] == 0x02 + && ((byteCount == 21 && bytes[1] == 0xB2) || (byteCount == 14 && bytes[1] == 0x92)) + && bytes[2] == 0x0F) { + Serial.println(F("Looks like a Samsung protocol")); + + // Power mode + if (byteCount == 21) + { + Serial.println(F("POWER OFF")); + return true; + } + Serial.println(F("POWER ON")); + + // Operating mode + switch (bytes[12] & 0xF0) { + case 0x00: + Serial.println(F("MODE AUTO")); + break; + case 0x10: + Serial.println(F("MODE COOL")); + break; + case 0x20: + Serial.println(F("MODE DRY")); + break; + case 0x30: + Serial.println(F("MODE FAN")); + break; + case 0x40: + Serial.println(F("MODE HEAT")); + break; + } + + // Temperature + Serial.print(F("Temperature: ")); + Serial.println((bytes[11] >> 4) + 16); + + // Fan speed + switch (bytes[12] & 0x0F) { + case 0x01: + Serial.println(F("FAN: AUTO")); + break; + case 0x05: + Serial.println(F("FAN: 1")); + break; + case 0x09: + Serial.println(F("FAN: 2")); + break; + case 0x0B: + Serial.println(F("FAN: 3")); + break; + case 0x0F: + Serial.println(F("FAN: 4")); + break; + } + + // Check if the checksum matches + byte originalChecksum = bytes[8]; + bytes[8] = 0; + byte checksum = 0x00; + + // Calculate the byte 8 checksum + // Count the number of ONE bits on message bytes 7-14 + for (uint8_t j=7; j<15; j++) { + uint8_t samsungByte = bytes[j]; + for (uint8_t i=0; i<8; i++) { + if ( (samsungByte & 0x01) == 0x01 ) { + checksum++; + } + samsungByte >>= 1; + } + } + + checksum = 34 - checksum; + checksum <<= 4; + checksum |= 0x02; + + Serial.print(F("Checksum '0x")); + Serial.print(checksum, HEX); + + if ( originalChecksum == checksum ) { + Serial.println(F("' matches")); + } else { + Serial.print(F("' does not match 0x")); + Serial.println(originalChecksum, HEX); + } + return true; + } + + return false; +} diff --git a/rawirdecode.ino b/rawirdecode.ino index 1cea997..a00de9b 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -13,6 +13,7 @@ bool decodeFuego(byte *bytes, int byteCount); bool decodeToshiba(byte *bytes, int byteCount); bool decodeNibe(byte *bytes, char* symbols, int bitCount); bool decodeHitachi(byte *bytes, int byteCount); +bool decodeSamsung(byte *bytes, int byteCount); /* Raw IR decoder sketch! @@ -343,7 +344,8 @@ void decodeProtocols() decodeFuego(bytes, byteCount) || decodeToshiba(bytes, byteCount) || decodeNibe(bytes, symbols, currentpulse) || - decodeHitachi(bytes, byteCount))) + decodeHitachi(bytes, byteCount) || + decodeSamsung(bytes, byteCount))) { Serial.println(F("Unknown protocol")); } From c409f3ea4802995c25f8f94b97901b7b4f22815b Mon Sep 17 00:00:00 2001 From: ToniA Date: Sat, 4 Mar 2017 17:52:26 +0200 Subject: [PATCH 49/94] Samsung turbo mode --- Samsung.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Samsung.cpp b/Samsung.cpp index 7a5c018..6f4b5c0 100644 --- a/Samsung.cpp +++ b/Samsung.cpp @@ -1,6 +1,6 @@ #include -byte bitReverse(byte x); +// Samsung with remote ARH-465 bool decodeSamsung(byte *bytes, int byteCount) { @@ -60,6 +60,17 @@ bool decodeSamsung(byte *bytes, int byteCount) break; } + // Turbo mode + Serial.print(F("Turbo mode: ")); + switch (bytes[9] & 0xF0) { + case 0xA0: + Serial.println(F("ON")); + break; + case 0xF0: + Serial.println(F("OFF")); + break; + } + // Check if the checksum matches byte originalChecksum = bytes[8]; bytes[8] = 0; From 446ab2f8553a1d593d9d711e566201ba6a356f43 Mon Sep 17 00:00:00 2001 From: ToniA Date: Sun, 5 Mar 2017 18:26:33 +0200 Subject: [PATCH 50/94] Airflow & Turbo mode, checksum calculator --- Samsung.cpp | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/Samsung.cpp b/Samsung.cpp index 6f4b5c0..f2f1f23 100644 --- a/Samsung.cpp +++ b/Samsung.cpp @@ -60,8 +60,8 @@ bool decodeSamsung(byte *bytes, int byteCount) break; } - // Turbo mode - Serial.print(F("Turbo mode: ")); + // Airflow mode + Serial.print(F("Airflow: ")); switch (bytes[9] & 0xF0) { case 0xA0: Serial.println(F("ON")); @@ -71,14 +71,25 @@ bool decodeSamsung(byte *bytes, int byteCount) break; } + // Turbo mode + Serial.print(F("Turbo mode: ")); + switch (bytes[10] & 0x0F) { + case 0x07: + Serial.println(F("ON")); + break; + case 0x01: + Serial.println(F("OFF")); + break; + } + // Check if the checksum matches byte originalChecksum = bytes[8]; - bytes[8] = 0; byte checksum = 0x00; // Calculate the byte 8 checksum - // Count the number of ONE bits on message bytes 7-14 - for (uint8_t j=7; j<15; j++) { + // Count the number of ONE bits + bytes[9] &= 0b11111110; + for (uint8_t j=9; j<13; j++) { uint8_t samsungByte = bytes[j]; for (uint8_t i=0; i<8; i++) { if ( (samsungByte & 0x01) == 0x01 ) { @@ -88,7 +99,7 @@ bool decodeSamsung(byte *bytes, int byteCount) } } - checksum = 34 - checksum; + checksum = 28 - checksum; checksum <<= 4; checksum |= 0x02; From 42c146b38e0322ab7d422862f0b7f1baa21d8fa1 Mon Sep 17 00:00:00 2001 From: ToniA Date: Tue, 28 Mar 2017 19:00:42 +0300 Subject: [PATCH 51/94] Ballu decoder (incomplete) --- Ballu.cpp | 55 +++++++++++++++++++++++++++++++++++++++++++++++++ rawirdecode.ino | 4 +++- 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 Ballu.cpp diff --git a/Ballu.cpp b/Ballu.cpp new file mode 100644 index 0000000..2aa9d33 --- /dev/null +++ b/Ballu.cpp @@ -0,0 +1,55 @@ +#include + +bool decodeBallu(byte *bytes, int byteCount) +{ + // If this looks like a Ballu code... + if ( byteCount == 21 && bytes[0] == 0x83 && bytes[1] == 0x06 ) { + Serial.println(F("Looks like a Ballu protocol")); + + // Power mode + // TBD + + // Operating mode + switch (bytes[3] & 0x07) { + case 0x00: + Serial.println(F("MODE HEAT")); + break; + case 0x02: + Serial.println(F("MODE COOL")); + break; + case 0x03: + Serial.println(F("MODE DRY")); + break; + case 0x04: + Serial.println(F("MODE FAN")); + break; + } + + // Temperature + Serial.print(F("Temperature: ")); + Serial.println((bytes[3] >> 4) + 16); + + // Fan speed + switch (bytes[02] & 0x03) { + case 0xA0: + Serial.println(F("FAN: AUTO")); + break; + case 0x01: + Serial.println(F("FAN: 1")); + break; + case 0x02: + Serial.println(F("FAN: 2")); + break; + case 0x03: + Serial.println(F("FAN: 3")); + break; + } + + // Check if the checksum matches + // TBD + + return true; + } + + return false; +} diff --git a/rawirdecode.ino b/rawirdecode.ino index a00de9b..b71bd20 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -14,6 +14,7 @@ bool decodeToshiba(byte *bytes, int byteCount); bool decodeNibe(byte *bytes, char* symbols, int bitCount); bool decodeHitachi(byte *bytes, int byteCount); bool decodeSamsung(byte *bytes, int byteCount); +bool decodeBallu(byte *bytes, int byteCount); /* Raw IR decoder sketch! @@ -345,7 +346,8 @@ void decodeProtocols() decodeToshiba(bytes, byteCount) || decodeNibe(bytes, symbols, currentpulse) || decodeHitachi(bytes, byteCount) || - decodeSamsung(bytes, byteCount))) + decodeSamsung(bytes, byteCount) || + decodeBallu(bytes, byteCount))) { Serial.println(F("Unknown protocol")); } From 93e2e7b63d86c44066f5d688bb39ead1831d7da0 Mon Sep 17 00:00:00 2001 From: ToniA Date: Sun, 16 Apr 2017 20:30:27 +0300 Subject: [PATCH 52/94] Carrier #2 decoder to recognize turbo and frostguard modes --- Carrier.cpp | 2 +- Samsung.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Carrier.cpp b/Carrier.cpp index c393fbb..831b297 100644 --- a/Carrier.cpp +++ b/Carrier.cpp @@ -146,7 +146,7 @@ bool decodeCarrier1(byte *bytes, int byteCount) bool decodeCarrier2(byte *bytes, int byteCount) { // If this looks like a Carrier code... - if ( byteCount == 12 && bytes[0] == 0x4D && bytes[1] == 0xB2 && (memcmp(bytes, bytes+6, 6) == 0)) { + if ( byteCount == 12 && ((bytes[0] == 0x4D && bytes[1] == 0xB2) || (bytes[0] == 0xAD && bytes[1] == 0x52)) && (memcmp(bytes, bytes+6, 6) == 0)) { Serial.println(F("Looks like a Carrier protocol #2")); switch (bytes[2] & 0x20) { diff --git a/Samsung.cpp b/Samsung.cpp index f2f1f23..93940ec 100644 --- a/Samsung.cpp +++ b/Samsung.cpp @@ -69,7 +69,7 @@ bool decodeSamsung(byte *bytes, int byteCount) case 0xF0: Serial.println(F("OFF")); break; - } + } // Turbo mode Serial.print(F("Turbo mode: ")); @@ -80,7 +80,7 @@ bool decodeSamsung(byte *bytes, int byteCount) case 0x01: Serial.println(F("OFF")); break; - } + } // Check if the checksum matches byte originalChecksum = bytes[8]; From 53f6658d415a5b58b7f4e1af44c8823eaee462c9 Mon Sep 17 00:00:00 2001 From: ToniA Date: Mon, 17 Apr 2017 10:31:57 +0300 Subject: [PATCH 53/94] Carrier #2 'frost guard' & turbo modes --- Carrier.cpp | 106 +++++++++++++++++++++++++++++----------------------- 1 file changed, 60 insertions(+), 46 deletions(-) diff --git a/Carrier.cpp b/Carrier.cpp index 831b297..70b48ce 100644 --- a/Carrier.cpp +++ b/Carrier.cpp @@ -149,55 +149,69 @@ bool decodeCarrier2(byte *bytes, int byteCount) if ( byteCount == 12 && ((bytes[0] == 0x4D && bytes[1] == 0xB2) || (bytes[0] == 0xAD && bytes[1] == 0x52)) && (memcmp(bytes, bytes+6, 6) == 0)) { Serial.println(F("Looks like a Carrier protocol #2")); - switch (bytes[2] & 0x20) { - case 0x00: - Serial.println(F("POWER: OFF")); - break; - case 0x20: - Serial.println(F("POWER: ON")); - break; + if (bytes[0] == 0xAD && bytes[1] == 0x52) + { + if (bytes[4] == 0x55) + { + Serial.println(F("MODE: Frost guard")); + } + else + { + Serial.println(F("MODE: Turbo mode")); + } } - - switch (bytes[2] & 0x07) { - case 0x05: - Serial.println(F("FAN: AUTO")); - break; - case 0x00: - Serial.println(F("FAN: AUTO/DRY AUTO")); - break; - case 0x01: - Serial.println(F("FAN: 1")); - break; - case 0x02: - Serial.println(F("FAN: 2")); - break; - case 0x04: - Serial.println(F("FAN: 3")); - break; + else + { + switch (bytes[2] & 0x20) { + case 0x00: + Serial.println(F("POWER: OFF")); + break; + case 0x20: + Serial.println(F("POWER: ON")); + break; + } + + switch (bytes[2] & 0x07) { + case 0x05: + Serial.println(F("FAN: AUTO")); + break; + case 0x00: + Serial.println(F("FAN: AUTO/DRY AUTO")); + break; + case 0x01: + Serial.println(F("FAN: 1")); + break; + case 0x02: + Serial.println(F("FAN: 2")); + break; + case 0x04: + Serial.println(F("FAN: 3")); + break; + } + + switch (bytes[4] & 0x30) { + case 0x10: + Serial.println(F("MODE: AUTO")); + break; + case 0x00: + Serial.println(F("MODE: COOL")); + break; + case 0x20: + if ((bytes[4] & 0x0F) == 0x07) { + Serial.println(F("MODE: FAN")); + } else { + Serial.println(F("MODE: DRY")); + } + break; + } + + const byte temperatures[] = { 17, 28, 24, 25, 20, 29, 21, 31, 18, 27, 23, 26, 19, 30, 22 }; + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 + + Serial.print(F("Temperature: ")); + Serial.println(temperatures[bytes[4] & 0x0F]); } - switch (bytes[4] & 0x30) { - case 0x10: - Serial.println(F("MODE: AUTO")); - break; - case 0x00: - Serial.println(F("MODE: COOL")); - break; - case 0x20: - if ((bytes[4] & 0x0F) == 0x07) { - Serial.println(F("MODE: FAN")); - } else { - Serial.println(F("MODE: DRY")); - } - break; - } - - const byte temperatures[] = { 17, 28, 24, 25, 20, 29, 21, 31, 18, 27, 23, 26, 19, 30, 22 }; - // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 - - Serial.print(F("Temperature: ")); - Serial.println(temperatures[bytes[4] & 0x0F]); - // Check if the checksum matches uint8_t checksum1 = ~bytes[2]; uint8_t checksum2 = ~bytes[4]; From 7079b57705994c60f98b1c4fd9c9e916f35e77a7 Mon Sep 17 00:00:00 2001 From: ToniA Date: Mon, 17 Apr 2017 10:34:46 +0300 Subject: [PATCH 54/94] Carrier #2 'HEAT' mode --- Carrier.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Carrier.cpp b/Carrier.cpp index 70b48ce..0f705d7 100644 --- a/Carrier.cpp +++ b/Carrier.cpp @@ -196,6 +196,9 @@ bool decodeCarrier2(byte *bytes, int byteCount) case 0x00: Serial.println(F("MODE: COOL")); break; + case 0x30: + Serial.println(F("MODE: HEAT")); + break; case 0x20: if ((bytes[4] & 0x0F) == 0x07) { Serial.println(F("MODE: FAN")); From 9798b6caa4569731f28f790474a67df4c892343b Mon Sep 17 00:00:00 2001 From: ToniA Date: Sun, 30 Apr 2017 12:28:07 +0300 Subject: [PATCH 55/94] Decoder for AUX YKR-N/002E --- AUXAC.cpp | 77 +++++++++++++++++++++++++++++++++++++++++++++++++ rawirdecode.ino | 4 ++- 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 AUXAC.cpp diff --git a/AUXAC.cpp b/AUXAC.cpp new file mode 100644 index 0000000..1a82f4d --- /dev/null +++ b/AUXAC.cpp @@ -0,0 +1,77 @@ +#include + +// AUX YKR-N/002E + +bool decodeAUX(byte *bytes, int byteCount) +{ + // If this looks like a AUX code... + if ( byteCount == 13 && bytes[0] == 0xC3 ) { + Serial.println(F("Looks like a AUX protocol")); + + // Power mode + switch (bytes[9] & 0x20) { + case 0x00: + Serial.println(F("POWER OFF")); + break; + case 0x20: + Serial.println(F("POWER ON")); + break; + } + + // Operating mode + switch (bytes[6] & 0xE0) { + case 0x80: + Serial.println(F("MODE HEAT")); + break; + case 0x20: + Serial.println(F("MODE COOL")); + break; + case 0x40: + Serial.println(F("MODE DRY")); + break; + case 0xC0: + Serial.println(F("MODE FAN")); + break; +// case 0xC0: // TBD +// Serial.println(F("MODE AUTO")); +// break; + } + + // Temperature + Serial.print(F("Temperature: ")); + Serial.println((bytes[1] >> 3) + 8); + + // Fan speed + switch (bytes[04] & 0xE0) { + case 0xA0: + Serial.println(F("FAN: AUTO")); + break; + case 0x60: + Serial.println(F("FAN: 1")); + break; + case 0x40: + Serial.println(F("FAN: 2")); + break; + case 0x20: + Serial.println(F("FAN: 3")); + break; + } + + // Check if the checksum matches + byte checksum = 0x00; + + for (byte i = 0; i < 12; i++) { + checksum += bytes[i]; + } + + if ( bytes[12] == checksum ) { + Serial.println(F("Checksum matches")); + } else { + Serial.println(F("Checksum does not match")); + } + + return true; + } + + return false; +} diff --git a/rawirdecode.ino b/rawirdecode.ino index b71bd20..48396e4 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -15,6 +15,7 @@ bool decodeNibe(byte *bytes, char* symbols, int bitCount); bool decodeHitachi(byte *bytes, int byteCount); bool decodeSamsung(byte *bytes, int byteCount); bool decodeBallu(byte *bytes, int byteCount); +bool decodeAUX(byte *bytes, int byteCount); /* Raw IR decoder sketch! @@ -347,7 +348,8 @@ void decodeProtocols() decodeNibe(bytes, symbols, currentpulse) || decodeHitachi(bytes, byteCount) || decodeSamsung(bytes, byteCount) || - decodeBallu(bytes, byteCount))) + decodeBallu(bytes, byteCount) || + decodeAUX(bytes, byteCount))) { Serial.println(F("Unknown protocol")); } From 2c2481d8668f72f01405899d105909b34fb56c34 Mon Sep 17 00:00:00 2001 From: ToniA Date: Sat, 6 May 2017 11:15:40 +0300 Subject: [PATCH 56/94] AUX AUTO, Turbo and airflow decoding --- AUXAC.cpp | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/AUXAC.cpp b/AUXAC.cpp index 1a82f4d..e419259 100644 --- a/AUXAC.cpp +++ b/AUXAC.cpp @@ -18,8 +18,18 @@ bool decodeAUX(byte *bytes, int byteCount) break; } + // Turbo mode + switch (bytes[5] & 0x40) { + case 0x40: + Serial.println(F("TURBO: ON")); + break; + } + // Operating mode switch (bytes[6] & 0xE0) { + case 0x00: + Serial.println(F("MODE AUTO")); + break; case 0x80: Serial.println(F("MODE HEAT")); break; @@ -32,9 +42,6 @@ bool decodeAUX(byte *bytes, int byteCount) case 0xC0: Serial.println(F("MODE FAN")); break; -// case 0xC0: // TBD -// Serial.println(F("MODE AUTO")); -// break; } // Temperature @@ -57,6 +64,26 @@ bool decodeAUX(byte *bytes, int byteCount) break; } + // Horizontal swing + switch (bytes[02] & 0xE0) { + case 0x00: + Serial.println(F("Horizontal swing: Off")); + break; + case 0xE0: + Serial.println(F("Horizontal swing: On")); + break; + } + + // Vertical swing + switch (bytes[01] & 0x07) { + case 0x00: + Serial.println(F("Vertical swing: Off")); + break; + case 0x07: + Serial.println(F("Vertical swing: On")); + break; + } + // Check if the checksum matches byte checksum = 0x00; From 8b13ad962fd48175784d194aa3693d10881f33d8 Mon Sep 17 00:00:00 2001 From: Matthew Edwards Date: Sun, 2 Jul 2017 16:28:59 +1200 Subject: [PATCH 57/94] Daikin: Add "outdoor unit quiet" fan mode and powerful, econo, swing, quiet buttons --- Daikin.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Daikin.cpp b/Daikin.cpp index 08d4760..3a070b7 100644 --- a/Daikin.cpp +++ b/Daikin.cpp @@ -41,6 +41,10 @@ bool decodeDaikin(byte *bytes, int byteCount) // Fan speed switch (bytes[24] & 0xF0) { + case 0xB0: + // Outdoor unit quiet + Serial.println(F("FAN: QUIET")); + break; case 0xA0: Serial.println(F("FAN: AUTO")); break; @@ -61,6 +65,14 @@ bool decodeDaikin(byte *bytes, int byteCount) break; } + // Other flags + Serial.print(F("FLAGS: ")); + if (bytes[29] & 0x01) Serial.print(F("POWERFUL ")); + if (bytes[32] & 0x04) Serial.print(F("ECONO ")); + if (bytes[24] & 0x0F) Serial.print(F("SWING ")); + if (bytes[29] & 0x20) Serial.print(F("QUIET")); + Serial.println(); + // Check if the checksum matches byte checksum = 0x00; From 18a07915be61907935a49a6eb1feea447af43e1e Mon Sep 17 00:00:00 2001 From: Andrew Errington Date: Wed, 13 Dec 2017 03:47:16 +0000 Subject: [PATCH 58/94] More documentation and better decording of Fujitsu packets --- Fujitsu.cpp | 614 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 545 insertions(+), 69 deletions(-) diff --git a/Fujitsu.cpp b/Fujitsu.cpp index cb7fac2..77c6eb8 100644 --- a/Fujitsu.cpp +++ b/Fujitsu.cpp @@ -1,6 +1,193 @@ #include -static byte header[] = {0x14,0x63,0x00,0x10,0x10}; +/* +Decoder for Fujitsu remote control + +Pulls apart packet and shows the meaning of each part. + +Fujitsu Model AR-RAH1E remote control codes by Andrew Errington 2017 +Based on Fujitsu AR-RY16 codes by David Abrams 2009 + +Carrier frequency 38 kHz +/- 1% +Carrier period 26.3 us + +Basic unit 410 us +/- 3% (approx. 16 cycles of carrier) + +Leader: 125 (0x7C) cycles carrier on +followed by 62 (0x3E) cycles carrier off + +Trailer 16 (0x10) cycles carrier on +followed by at least 305 (0x130) cycles carrier off + +Data '1' bit 16 (0x10) cycles carrier on + 16 (0x10) cycles carrier off + +Data '0' bit 16 (0x10) cycles carrier on + 46 (0x2E) cycles carrier off + +There are two kinds of packet. A short packet is 7 bytes long and +usually encodes a simple command, such as toggling a feature on or off. +A full packet is 16 bytes long, and encodes the full state of the +remote control including the mode, fan speed, temperature, etc. + +Short packet (7 bytes) + +Byte 0 Marker code (M1) 0x14 +Byte 1 Marker code (M2) 0x63 +Byte 2 Signal code 0x00 +Byte 3 Custom code (C1) 0x10 +Byte 4 Sub custom code (C2) 0x10 +Byte 5 Command code (D) 0xXX +Byte 6 Checksum 0x?? + + +The a/c units and remote can be configured with one of four signal +codes (A, b, c, or d). This allows different a/c units to be controlled +by different remotes in the same room. + +Signal code (Byte 2<5:4>) + +0x00 Signal code A (default) +0x01 Signal code b +0x02 Signal code c +0x03 Signal code d + + +Byte 5 contains the command. If it's not 0xFE then we have a short +packet, with a simple function. + +Command codes (Byte 5<7:0>) + +0x02 Off +0x03 Test +0x09 Toggle economy mode +0x3F Filter Reset* +0x2D Super quiet* +0x6C Step horizontal vane up/down +0x79 Step vertical vane left/right +0xFE Full packet + +* Not for AR-RAH1E + + +Checksum (Byte 6<7:0>) (short packet) + +Checksum = 0xFF - Byte 5 + + + +Full packet (16 bytes) + +Byte 0 Marker code (M1) 0x14 +Byte 1 Marker code (M2) 0x63 +Byte 2 Signal code 0x00 +Byte 3 Custom code (C1) 0x10 +Byte 4 Sub custom code (C2) 0x10 +Byte 5 Command code (D) 0xFE (indicates full packet) +Byte 6 Unknown 0x09 +Byte 7 Unknown 0x30 +Byte 8 <7:4> temperature + <3:1> unknown + <0> on/off state +Byte 9 <7:4> timer mode + <3:0> mode +Byte 10 <7:4> swing mode + <3:0> fan mode +Byte 11 Timer off value low byte +Byte 12 <7:4> timer on value bits 3:0 + <3> new timer off value + <2:0> timer off value bits 10:8 +Byte 13 <7> new timer on value + <6:0> timer on value bits 10:4 +Byte 14 Unknown 0x20 +Byte 15 Checksum 0x?? + + +Temperature (Byte 8<7:4>) + +0x00 16C (60F)* 0x08 24C (76F) +0x01 17C (62F)* 0x09 25C (78F) +0x02 18C (64F) 0x0A 26C (80F) +0x03 19C (66F) 0x0B 27C (82F) +0x04 20C (68F) 0x0C 28C (84F) +0x05 21C (70F) 0x0D 29C (86F) +0x06 22C (72F) 0x0E 30C (88F) +0x07 23C (74F) 0x0F Unknown + +Byte 8<0>, if 1, unit was off and is now on, if 0 unit was already on + +* For AR-RAH1E, heat setting is 16-30C, cool setting is 18-30C + + +Timer mode (Byte 9<7:4>) + +0x00 Timer off +0x01 Sleep mode +0x02 Timer off time +0x03 Timer on time +0x04 Off -> On + + +Mode (Byte 9<3:0>) + +0x00 Auto +0x01 Cool +0x02 Dry +0x03 Fan +0x04 Heat + + +Swing mode (Byte 10<7:4>) + +0x00 Off +0x01 Horizontal vanes oscillation up/down +0x02 Vertical vanes oscillation left/right +0x03 Both hor. and vert. vanes oscillation + + +Fan mode (Byte 10<3:0>) + +0x00 Auto +0x01 High +0x02 Medium +0x03 Low +0x04 Quiet + + +Timer modes (Byte 11-13) + +Timer off + +Timer off value (Byte 12<2:0> Byte 11<7:0>) +11-bit value (0 to 595) +For Sleep mode, number of minutes to run before turning off. +For Timer Off mode, number of minutes from now until turn off. + +New timer off value (Byte 12<3>) +Set when new Sleep or Timer Off value is sent. + +Timer on + +Timer on value (Byte 13<6:0> Byte 12<7:4>) +11-bit value, number of minutes from now until turn on. + +New timer on value (Byte 13<7>) +Set when new Timer On value is sent. + + +Unknown (Byte 14) + +For AR-RY16 and AR-RAH1E constant 0x20 +Some reports indicate this may be an Eco setting for some models + + +Checksum (Byte 15) + +Checksum = 0 - (sum of Bytes 7 to 14) + +*/ + +static byte header[] = {0x14,0x63}; bool decodeFujitsu(byte *bytes, int byteCount) { @@ -21,41 +208,249 @@ bool decodeFujitsu(byte *bytes, int byteCount) if (checksum == bytes[byteCount-1]) { Serial.println(F("Checksum matches")); } else { - Serial.print(F("Checksum does not match: ")); + Serial.print(F("Checksum does not match: 0x")); Serial.print(checksum, HEX); - Serial.print(F(" vs ")); + Serial.print(F(" vs 0x")); Serial.println(bytes[byteCount-1], HEX); } - // Power mode? Main command? - switch (bytes[5]) { + + // Now pick apart the packet + + if (bytes[5] == 0xFE) { + if (byteCount != 16) { + Serial.println(F("Long command (byte 5 is 0xFE), but packet is not 16 bytes long!")); + return false; + } else { + Serial.println(F("Long command (byte 5 is 0xFE).")); + } + } + + if (bytes[5] != 0xFE) { + if (byteCount != 7) { + Serial.println(F("Short command (byte 5 is not 0xFE), but packet is not 7 bytes long!")); + return false; + } else { + Serial.println(F("Short command (byte 5 is not 0xFE).")); + } + } + + + // Bytes 0, 1, 3, and 4 are (probably) constant + + Serial.print(F("Byte 0: 0x")); + Serial.print(bytes[0], HEX); + Serial.print(F(" expected 0x14 ")); + if (bytes[0] == 0x14) { + Serial.println(F("[OK]")); + } else { + Serial.println(F("[Error]")); + } + + Serial.print(F("Byte 1: 0x")); + Serial.print(bytes[1], HEX); + Serial.print(F(" expected 0x63 ")); + if (bytes[1] == 0x63) { + Serial.println(F("[OK]")); + } else { + Serial.println(F("[Error]")); + } + + // Byte 2 contains the Remote Control Unit Signal Code (Abcd) + // Default is A + + Serial.print(F("Byte 2: 0x")); + Serial.println(bytes[2], HEX); + + // Remote Control Unit Signal Code + Serial.print(F("Signal Code, byte 2<5:4>: 0x")); + Serial.print((bytes[2] & 0x30) >> 4, HEX); + Serial.print(F("\t\t")); + + switch ((bytes[2] & 0x30) >> 4) { + case 0x00: + Serial.println(F("A (Default)")); + break; + case 0x01: + Serial.println(F("b")); + break; case 0x02: - Serial.println(F("POWER OFF")); + Serial.println(F("c")); + break; + case 0x03: + Serial.println(F("d")); + break; + } + + + Serial.print(F("Byte 3: 0x")); + Serial.print(bytes[3], HEX); + Serial.print(F(" expected 0x10 ")); + if (bytes[3] == 0x10) { + Serial.println(F("[OK]")); + } else { + Serial.println(F("[Error]")); + } + + Serial.print(F("Byte 4: 0x")); + Serial.print(bytes[4], HEX); + Serial.print(F(" expected 0x10 ")); + if (bytes[4] == 0x10) { + Serial.println(F("[OK]")); + } else { + Serial.println(F("[Error]")); + } + + + // Byte 5 encodes the command. + // If it's not 0xFE then it's a short command (byte 5 is the command) + // If it is 0xFE then it's a long command and data is encoded + // in the following bytes. + + Serial.print(F("Byte 5: 0x")); + Serial.print(bytes[5], HEX); + + if (bytes[5] != 0xFE) { + // Short command + Serial.print(F(" Short command:\t\t")); + + switch (bytes[5]) { + case 0x02: + Serial.println(F("POWER OFF")); + break; + case 0x03: + Serial.println(F("TEST")); + break; + case 0x09: + Serial.println(F("TOGGLE ECONOMY MODE")); + break; + case 0x2D: + // Not AR-RAH1E + Serial.println(F("SUPER QUIET")); + break; + case 0x39: + // Not AR-RAH1E + Serial.println(F("HIGH POWER")); + break; + case 0x3F: + // Not AR-RAH1E + Serial.println(F("FILTER RESET")); + break; + case 0x6C: + Serial.println(F("STEP HORIZONTAL VANE UP/DOWN")); + break; + case 0x79: + Serial.println(F("STEP VERTICAL VANE LEFT/RIGHT")); + break; + default: + Serial.println(F("UNKNOWN")); + break; + } + + // Byte 6 is checksum in short command mode + + Serial.print(F("Byte 6 (Checksum): 0x")); + Serial.println(bytes[6], HEX); + + return true; + + } + + + // Otherwise, long command + + Serial.println(F(" Long command")); + + + // Bytes 6 and 7 are constant + + Serial.print(F("Byte 6: 0x")); + Serial.print(bytes[6], HEX); + Serial.print(F(" expected 0x09 ")); + if (bytes[6] == 0x09) { + Serial.println(F("[OK]")); + } else { + Serial.println(F("[Error]")); + } + + Serial.print(F("Byte 7: 0x")); + Serial.print(bytes[7], HEX); + Serial.print(F(" expected 0x30 ")); + if (bytes[7] == 0x30) { + Serial.println(F("[OK]")); + } else { + Serial.println(F("[Error]")); + } + + + // Byte 8 encodes temperature + + Serial.print(F("Byte 8: 0x")); + Serial.println(bytes[8], HEX); + + Serial.print(F("Temperature, byte 8<7:4>: 0x")); + Serial.print(bytes[8] >> 4, HEX); + + if ((bytes[8] >> 4) != 0x0F) { + Serial.print(F("\t\t")); + Serial.print((bytes[8] >> 4) + 16); + Serial.print(F("C (")); + Serial.print((bytes[8] >> 3) + 60); + Serial.println(F("F)")); + } else { + Serial.println(F(" UNKNOWN")); + } + + Serial.print(F("Unit state, byte 8 <0>: 0x")); + Serial.print(bytes[8] & 0x01,HEX); + + if ((bytes[8] & 0x01) == 0x01) { + Serial.println(F("\t\tUnit was off and is now on.")); + } else { + Serial.println(F("\t\tUnit was already on.")); + } + + Serial.print(F("Unknown bits, Byte 8 <3:1>: 0x")); + Serial.println(bytes[8] & 0x0E >> 1,HEX); + + + // Byte 9 encodes timer and main operating mode + + Serial.print(F("Byte 9: 0x")); + Serial.println(bytes[9], HEX); + + // Timer mode + Serial.print(F("Timer mode, byte 9<7:4>: 0x")); + Serial.print(bytes[9] >> 4, HEX); + Serial.print(F("\t\t")); + + switch (bytes[9] >> 4) { + case 0x00: + Serial.println(F("TIMER OFF")); break; - case 0xFE: - Serial.println(F("POWER ON")); + case 0x01: + Serial.println(F("TIMER SLEEP")); break; - case 0x6C: - Serial.println(F("SWING SET VERTICAL")); + case 0x02: + Serial.println(F("SET TIMER OFF TIME")); break; - case 0x79: - Serial.println(F("SWING SET HORIZONTAL")); + case 0x03: + Serial.println(F("SET TIMER ON TIME")); + break; + case 0x04: + Serial.println(F("SET TIMER (OFF -> ON)")); break; default: - Serial.println(F("POWER unknown")); + Serial.println(F("UNKNOWN")); break; } - if(byteCount < 8) { - return true; - } - - // Temperature - Serial.print(F("Temperature: ")); - Serial.println((bytes[8] >> 4) + 16); + // Main mode + Serial.print(F("Main mode, byte 9<3:0>: 0x")); + Serial.print(bytes[9] & 0x0F, HEX); + Serial.print(F("\t\t")); - // Operating mode - switch (bytes[9] & 0xFF) { + switch (bytes[9] & 0x0F) { case 0x00: Serial.println(F("MODE AUTO")); break; @@ -75,26 +470,52 @@ bool decodeFujitsu(byte *bytes, int byteCount) Serial.println(F("MODE COIL DRY")); break; case 0x0B: - Serial.println(F("MODE 10c HEAT")); + Serial.println(F("MODE 10C HEAT")); break; - case 0x10: - Serial.println(F("MODE SLEEP")); + default: + Serial.println(F("MODE UNKNOWN")); + break; + } + + + // Byte 10 encodes swing mode and fan speed + + Serial.print(F("Byte 10: 0x")); + Serial.println(bytes[10], HEX); + + // Swing mode + // This may actually be two individual bits + // i.e. byte 10<4> 1 = oscillate horizontal vane 0 = horizontal vane still + // byte 10<5> 1 = oscillate vertical vane 0 = vertical vane still + // byte 10(7:6> unknown + // For now it is treated as a 4-bit field with four known values + Serial.print(F("Swing mode, byte 10<7:4>: 0x")); + Serial.print(bytes[10] >> 4, HEX); + Serial.print(F("\t\t")); + + switch (bytes[10] >> 4) { + case 0x00: + Serial.println(F("OSCILLATION OFF/VANES STILL")); break; - case 0x20: - Serial.println(F("SET TIMER (OFF)")); + case 0x01: + Serial.println(F("OSCILLATE HORIZONTAL VANE UP/DOWN")); break; - case 0x30: - Serial.println(F("SET TIMER (ON)")); + case 0x02: + Serial.println(F("OSCILLATE VERTICAL VANE LEFT/RIGHT")); break; - case 0x40: - Serial.println(F("SET TIMER (OFF -> ON)")); + case 0x03: + Serial.println(F("OSCILLATE HORIZONTAL AND VERTICAL VANES")); break; default: - Serial.println(F("MODE unknown")); + Serial.println(F("VANE UNKNOWN")); break; } // Fan speed + Serial.print(F("Fan speed, byte 9<10:0>: 0x")); + Serial.print(bytes[10] & 0x0F, HEX); + Serial.print(F("\t\t")); + switch (bytes[10] & 0x07) { case 0x00: Serial.println(F("FAN AUTO")); @@ -112,52 +533,107 @@ bool decodeFujitsu(byte *bytes, int byteCount) Serial.println(F("FAN QUIET")); break; default: - Serial.println(F("FAN unknown")); + Serial.println(F("FAN UNKNOWN")); break; } - // Fan swing - switch (bytes[10] & 0xF0) { - case 0x10: - Serial.println(F("VANE: VERTICAL")); - break; - case 0x20: - Serial.println(F("VANE: HORIZONTAL")); - break; - case 0x30: - Serial.println(F("VANE: BOTH")); - break; - case 0x00: - Serial.println(F("VANE: STILL")); - break; - default: - Serial.println(F("VANE: unknown")); - break; + + // Bytes 11, 12 and 13 encode the timer operation + + Serial.print(F("Byte 11: 0x")); + Serial.println(bytes[11], HEX); + + Serial.print(F("Byte 12: 0x")); + Serial.println(bytes[12], HEX); + + Serial.print(F("Byte 13: 0x")); + Serial.println(bytes[13], HEX); + + + Serial.print(F("Timer off, byte 12<2:0> 0x")); + Serial.print(bytes[12] & 0x03, HEX); + + Serial.print(F(" byte 11<7:0>: 0x")); + Serial.print(bytes[11], HEX); + Serial.print(F(" 0x")); + Serial.print(bytes[12] & 0x03, HEX); + Serial.println(bytes[11], HEX); + + unsigned int timer_off = (unsigned int) (bytes[12] & 0x03); + timer_off = (timer_off << 8) + bytes[11]; + Serial.print(F("Off time: 0x")); + Serial.print(timer_off, HEX); + Serial.print(F("\t\t\t\t")); + Serial.print((int)timer_off/60); + Serial.print(F("h ")); + Serial.print(timer_off%60); + Serial.print(F("m (")); + Serial.print(timer_off); + Serial.println(F(" minutes)")); + + + Serial.print(F("Timer off value flag, byte 12<3>: 0x")); + Serial.print(bytes[12] & 0x08 >> 3,HEX); + + if ((bytes[12] & 0x08) == 0x08) { + Serial.println(F("\tNew timer OFF value.")); + } else { + Serial.println(F("\tTimer OFF value unchanged.")); } - /* - * Not sure what's going on here, but incrementing time on the remote by 5 - * minutes, increments this value by 5 as well. - */ - int time = (int)bytes[12] << 8 + bytes[11]; - Serial.print("Time: "); - Serial.println(time); - - //Economy mode? I'm guessing more modes are encoded here on fancier models. - switch (bytes[14]) { - case 0x00: - Serial.println(F("ECONOMY: ON")); - break; - case 0x20: - Serial.println(F("ECONOMY: OFF")); - break; - default: - Serial.println(F("ECONOMY: unknown")); - break; + + Serial.print(F("Timer on, byte 13<6:0> 0x")); + Serial.print(bytes[13] & 0x3F, HEX); + Serial.print(F(" byte 12<7:4>: 0x")); + Serial.print(bytes[12]>>4, HEX); + Serial.print(F(" 0x")); + Serial.print(bytes[13] & 0x3F << 4); + Serial.println(bytes[12] >> 4, HEX); + + unsigned int timer_on = (unsigned int) (bytes[13] & 0x3F); + timer_on = (timer_on << 4) + (bytes[12] >> 4); + Serial.print(F("On time: 0x")); + Serial.print(timer_on, HEX); + Serial.print(F("\t\t\t\t")); + Serial.print((int)timer_on/60); + Serial.print(F("h ")); + Serial.print(timer_on%60); + Serial.print(F("m (")); + Serial.print(timer_on); + Serial.println(F(" minutes)")); + + + Serial.print(F("Timer on value flag, byte 13<7>: 0x")); + Serial.print(bytes[13] & 0x80 >> 7,HEX); + + if ((bytes[13] & 0x80) == 0x80) { + Serial.println(F("\tNew timer ON value.")); + } else { + Serial.println(F("\tTimer ON value unchanged.")); + } + + + // Byte 14 is constant + // Might be a bit for ECO mode on certain models. + Serial.print(F("Byte 14: 0x")); + Serial.print(bytes[14], HEX); + Serial.print(F(" expected 0x20 ")); + if (bytes[14] == 0x20) { + Serial.println(F("[OK]")); + } else { + Serial.println(F("[Error]")); } + + // Byte 15 is the checksum + + Serial.print(F("Byte 15 (Checksum): 0x")); + Serial.println(bytes[15], HEX); + return true; + } return false; + } From 3762fe5c961b6ae4c76ea643f3088b8544860f5d Mon Sep 17 00:00:00 2001 From: Nateonas <40019341+Nateonas@users.noreply.github.com> Date: Tue, 12 Jun 2018 22:14:18 +0200 Subject: [PATCH 59/94] Update README.md - added required hardware section - added serial monitor options 4 and 5 --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9719c0e..87aa964 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,16 @@ Decodes long IR codes, for example from air conditioner / heat pump devices. Shows the timings, the symbols, and also the decoded signal for certain air conditioners. +Required hardware: +- Arduino (any compatible will do, but Arduino Uno or Nano is easiest for prototyping) +- Infrared receiver, for example VS1838 will do fine, see also https://arduino-info.wikispaces.com/IR-RemoteControl +- Breadboard, wiring +- IR remote control from the aircon/heatpump you plan to decode ## Instructions * Connect an IR receiver into the Arduino -* Start the sketch, and enter 1, 2 or 3 into the 'Serial Monitor', to select which timings to use +* Start the sketch, and enter 1, 2, 3, 4 or 5 into the 'Serial Monitor', to select which timings to use * Try out the alternatives until you get sensible output * The signal should always start with 'Hh', and within the signal there should only be a couple of 'Hh' pairs (if any) * 'H' and 'h' should be there only in pairs 'Hh' From 5f6c3144543bde1f8d3b4bcb9f2143af28da9177 Mon Sep 17 00:00:00 2001 From: Jeroen Bosch Date: Tue, 12 Jun 2018 23:44:36 +0200 Subject: [PATCH 60/94] add panasonic CS decoder --- PanasonicCS.cpp | 175 ++++++++++++++++++++++++++++++++++++++++++++++++ rawirdecode.ino | 2 + 2 files changed, 177 insertions(+) create mode 100644 PanasonicCS.cpp diff --git a/PanasonicCS.cpp b/PanasonicCS.cpp new file mode 100644 index 0000000..8d95498 --- /dev/null +++ b/PanasonicCS.cpp @@ -0,0 +1,175 @@ +/** + * Code based on examples + * Checked with Panasonic Inverter Air Conditioner CS-RE18GKE indoor and CU-RE18GKE outdoor split unit + * Model known as WA-18HP/I/ST (year 2007-2008) + * Remote: A75C3010 + * + * designed with help off http://www.instructables.com/id/Reverse-engineering-of-an-Air-Conditioning-control/ + * + */ +#include + +bool decodePanasonicCS(byte *bytes, int byteCount) +{ + // Test if looks like Pansonic code + // check via byteCount and fixed values + if (byteCount == 27 && bytes[10 == 0xE0]){ // I just picked a value here + Serial.println(F("Look like a Panasonic CS protocol")); + + // check on-off + // 13th byte from start, first byte with info + // this is not a toggle, but a state + if ((bytes[13] & 0x01) == 0x01) { // check if the right bit set + Serial.println(F("Powering ON!")); + } else { + Serial.println(F("Powering OFF")); + } + + // check mode + // if this byte is used for other things, first mask the correct bits + Serial.print(F("Mode: ")); + switch ((bytes[13]) & 0xF0){ // masks the first 4 bits 1111 0000 + case 0x00: + Serial.println(F("Auto")); + break; + case 0x40: + Serial.println(F("Heat")); + break; + case 0x30: + Serial.println(F("Cool")); + break; + case 0x20: + Serial.println(F("Dry")); + break; + default: + Serial.println(F("Error")); + break; + } + // check temp + Serial.print(F("Temperature: ")); + byte temp = (bytes[14] & 0x1E); + temp = temp >> 1; + Serial.println((temp) + 16); // masks the middle 5 bits: 0x1E = 0001 1110 + + // check fanspeed + Serial.print(F("Fan speed: ")); + switch (bytes[16] & 0xF0){ // 0xF0 = 1111 0000 eerste 4 bits + case 0xA0: + Serial.println(F("FAN AUTO")); + break; + case 0x30: + Serial.println(F("FAN 1")); + break; + case 0x40: + Serial.println(F("FAN 2")); + break; + case 0x50: + Serial.println(F("FAN 3")); + break; + case 0x60: + Serial.println(F("FAN 4")); + break; + case 0x70: + Serial.println(F("FAN 5")); + break; + default: + Serial.println(F("Error")); + break; + } + + // check vertical swing + Serial.print(F("Vertical swing: ")); + switch (bytes[16] & 0x0F){ // 0x0F = 0000 1111 + case 0x0F: + Serial.println(F("AUTO")); + break; + case 0x01: + Serial.println(F("Straight")); + break; + case 0x02: + Serial.println(F("Down 1")); + break; + case 0x03: + Serial.println(F("Down 2")); + break; + case 0x04: + Serial.println(F("Down 3")); + break; + case 0x05: + Serial.println(F("Down 4")); + break; + default: + Serial.println(F("Error")); + break; + } + + // check horizontal swing + Serial.print(F("Horizontal swing: ")); + switch (bytes[17] & 0x0F){ // 0x0F = 0000 1111 + case 0x0D: + Serial.println(F("AUTO")); + break; + case 0x06: + Serial.println(F("Center")); + break; + case 0x09: + Serial.println(F("Left")); + break; + case 0x0A: + Serial.println(F("Left center")); + break; + case 0x0B: + Serial.println(F("Right center")); + break; + case 0x0C: + Serial.println(F("Right")); + break; + default: + Serial.println(F("Error")); + break; + } + + // timer A (start) + Serial.print(F("Timer A active: ")); + if((bytes[13] & 0x02) == 0x02){ //check if second bit in byte is active + Serial.println(F("Yes")); + } else { + Serial.println(F("No")); + } + + // timer B (end) + Serial.print(F("Timer B active: ")); + if((bytes[13] & 0x04) == 0x04){ //check if third bit in byte is active + Serial.println(F("Yes")); + } else { + Serial.println(F("No")); + } + // no need to investigate actual timings if you're going to automate :-) + + // checksum algorithm + // Assumption: bitwise sum of payload bytes (so ignore header, and last byte) + byte checksum = 0x06; // found differce, so start with 0x06 + Serial.print(F("Add bytes: ")); + for (int i = 13; i < 26; i++){ + if (bytes[i] < 0x10) { + Serial.print(F("0")); + } + Serial.print(bytes[i],HEX); + Serial.print(F("+")); + checksum = checksum + bytes[i]; + } + Serial.println(F(".")); + Serial.print(F("Checksum Truncate: ")); + checksum = (checksum & 0xFF); // mask out only first byte) + Serial.println(checksum,HEX); + + if (checksum == bytes[26]){ + Serial.println(F("----- Checksum OK ------")); + } + + // good end + return true; + } + + return false; +} diff --git a/rawirdecode.ino b/rawirdecode.ino index 48396e4..dcdb323 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -7,6 +7,7 @@ bool decodeDaikin(byte *bytes, int byteCount); bool decodeSharp(byte *bytes, int byteCount); bool decodeCarrier(byte *bytes, int byteCount); bool decodePanasonicCKP(byte *bytes, int byteCount); +bool decodePanasonicCS(byte *bytes, int byteCount); bool decodeHyundai(byte *bytes, int pulseCount); bool decodeGree(byte *bytes, int pulseCount); bool decodeFuego(byte *bytes, int byteCount); @@ -341,6 +342,7 @@ void decodeProtocols() decodeCarrier(bytes, byteCount) || decodeCarrier(bytes, byteCount) || decodePanasonicCKP(bytes, byteCount) || + decodePanasonicCS(bytes, byteCount) || decodeHyundai(bytes, currentpulse) || decodeGree(bytes, currentpulse) || decodeFuego(bytes, byteCount) || From d39e9514ffbffe0652309e9749d1e9f8d7160e4b Mon Sep 17 00:00:00 2001 From: Nateonas <40019341+Nateonas@users.noreply.github.com> Date: Wed, 13 Jun 2018 21:32:08 +0200 Subject: [PATCH 61/94] Additional info on what to expect after send an Ir code --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 87aa964..093a07d 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,10 @@ Required hardware: * 'H' and 'h' should be there only in pairs 'Hh' * 'H' stands for 'header mark' and 'h' for 'header space' * Point your IR remote to the IR receiver and send the code -* Mode '9' can be used to decode known signals, in that case you can send the symbols from the terminal, like entering this: + * If the symbols are known, then the decoder shows its meaning on the serial monitor + * If the symbols are unknown, then you can help by writing a decoder for the unknown remote + +-> Mode '9' can be used to decode known signals, in that case you can send the symbols from the terminal, like entering this: Hh001101011010111100000111001001010100000000000111000000001111111011010100000001000111001011 From 2d35445898e0835085e99fbe3d4cf4190ff78529 Mon Sep 17 00:00:00 2001 From: Nateonas Date: Fri, 29 Jun 2018 17:06:53 +0200 Subject: [PATCH 62/94] ZH/LT-01 decoder - Support for decoding ZH/LT-01 remote controls (being used by many ACs) - Support for directly connecting a VS1838-IR-receiver to the Arduino pins (2,3,4), eliminating the need for writing and stabilising the IR receiver. --- ZHLT01Remote.cpp | 375 +++++++++++++++++++++++++++++++++++++++++++++++ rawirdecode.ino | 26 +++- 2 files changed, 398 insertions(+), 3 deletions(-) create mode 100644 ZHLT01Remote.cpp diff --git a/ZHLT01Remote.cpp b/ZHLT01Remote.cpp new file mode 100644 index 0000000..5361775 --- /dev/null +++ b/ZHLT01Remote.cpp @@ -0,0 +1,375 @@ + +/******************************************************************************** + * Airconditional remote control decoder for: + * + * ZH/LT-01 Remote control https://www.google.com/search?q=zh/lt-01 + * + * The ZH/LT-01 remote control is used for many locally branded Split airconditioners, + * so it is better to name this protocol by the name of the protocol rather then the + * name of the Airconditioner. For this project I used a Eurom-airconditioner, which is + * Dutch-branded and sold in the Netherlands at Hornbach. + * + * For airco-brands: + * Eurom + * Chigo + * Tristar + * Tecnomaster + * Elgin + * Geant + * Tekno + * Topair + * Proma + * Sumikura + * JBS + * Turbo Air + * Nakatomy + * Celestial Air + * Ager + * Blueway + * Airlux + * Etc. + * + *********************************************************************************** + * SUMMARY FUNCTIONAL DESCRIPTION + ********************************************************************************** + * The remote sends a 12 Byte message which contains all possible settings every + * time. + * + * Byte 11 (and 10) contain the remote control identifier and are always 0xD5 and + * 0x2A respectively for the ZH/LT-01 remote control. + * Every UNeven Byte (01,03,05,07 and 09) hold command data + * Every EVEN Byte (00,02,04,06,08 and 10) hold a checksum of the corresponding + * command-, or identifier-byte by inverting the bits, for example: + * + * The identifier byte[11] = 0xD5 = B1101 0101 + * The checksum byte[10] = 0x2A = B0010 1010 + * + * So, you can check the message by: + * - inverting the bits of the checksum byte with the corresponding command-, or + * identifier byte, they should me the same, or + * - Summing up the checksum byte and the corresponding command-, or identifier byte, + * they should always add up to 0xFF = B11111111 = 255 + * + * Control bytes: + * [01] - Timer (1-24 hours, Off) + * [03] - LAMP ON/OFF, TURBO ON/OFF, HOLD ON/OFF + * [05] - Indicates which button the user pressed on the remote control + * [07] - POWER ON/OFF, FAN AUTO/3/2/1, SLEEP ON/OFF, AIRFLOW ON/OFF, + * VERTICAL SWING/WIND/FIXED + * [09] - MODE AUTO/COOL/VENT/DRY/HEAT, TEMPERATURE (16 - 32°C) + * + * TIMINGS + * (Both Hyundai (option 4) and Samsung (option 5) timings work) + * Space: Not used + * Header Mark: 6200 us + * Header Space: 7500 us + * Bit Mark: 475 us + * Zero Space: 525 us + * One Space: 1650 us + * + * ****************************************************************************** + * Written by: Marcel van der Kooij + * Date: 2018-06-07 + * Version: 1.0 + *******************************************************************************/ + +#include + +bool decodeZHLT01remote(byte *bytes, int byteCount) +{ + +/******************************************************************************** + * Is this a ZH/LT-01 Remote? + * Message should by 12 bytes, 100 symbols. + * Remote identifier + * Byte[11]: Always 0xD5, which makes Byte[10] always 0x2A (see checksum) + * + *******************************************************************************/ + + if (byteCount == 12 && bytes[10] == 0x2A && bytes[11] == 0xD5) { + Serial.println(F("Looks like a ZH/LT-01 remote control protocol")); + +/******************************************************************************* + * Byte[01]: Timer (1-24 hours, Not used, Off, Changed, Set) + * TIMER ON/OFF: B1xxxxxxx + * TIMER CHANGED: Bxx1xxxxx + * + * Number of hours is determined by bit0-4: + * 0x01 = 1 hour + * 0x18 = 24 hours + *******************************************************************************/ + + Serial.print(F("bytes[1] bit5-7 Timer: ")); + switch (bytes[1] & B10100000) { + case 0x00: + Serial.println(F("Not used")); + break; + case B00100000: + Serial.println(F("Off")); + break; + case B10000000: + Serial.println(F("Set")); + break; + case B10100000: + Serial.println(F("Changed")); + break; + default: + Serial.println("Unknown"); + } + + Serial.print(F("bytes[1] bit0-4 Hours: ")); + Serial.println(bytes[1] & B00011111); + + + +/******************************************************************************** + * Byte[03]: LAMP, TURBO, HOLD ON/OFF + * LAMP ON: Bxxxxxxx1 (bit0) + * LAMP OFF: Bxxxxxxx0 + * HOLD ON: Bxxxxx1xx (bit2) + * HOLD ON: Bxxxxx0xx + * TURBO ON: Bxxxx1xxx (bit3) + * TURBO ON: Bxxxx0xxx + *******************************************************************************/ + // LAMP ON/OFF + Serial.print(F("bytes[3] bit0 Lamp: ")); + switch (bytes[3]& B00000001) { + case B00000000: + Serial.println(F("Off")); + break; + case B00000001: + Serial.println(F("On")); + break; + default: + Serial.println(F("Unknown")); + } + + // HOLD ON/OFF + Serial.print(F("bytes[3] bit2 Hold: ")); + switch (bytes[3]& B00000100) { + case B00000000: + Serial.println(F("Off")); + break; + case B00000100: + Serial.println(F("On")); + break; + default: + Serial.println(F("Unknown")); + } + + // TURBO ON/OFF + Serial.print(F("bytes[3] bit3 Turbo: ")); + switch (bytes[3]& B00001000) { + case B00000000: + Serial.println(F("Off")); + break; + case B00001000: + Serial.println(F("On")); + break; + default: + Serial.println(F("Unknown")); + } + +/******************************************************************************** + * Byte[05]: Button pressed + * 0x00: POWER + * 0x01: MODE + * 0x02: Temperature Up + * 0x03: Temperature Down + * 0x04: Vertical Swing + * 0x05: FAN + * 0x06: TIMER + * 0x07: Horizontal swing + * 0x08: HOLD + * 0x09: SLEEP + * 0x0A: TURBO + * 0x0B: LAMP + *******************************************************************************/ + + Serial.print(F("bytes[5] Button pressed: ")); + switch (bytes[5]) { + case 0x00: + Serial.println(F("POWER")); + break; + case 0x01: + Serial.println(F("MODE")); + break; + case 0x02: + Serial.println(F("TEMP UP")); + break; + case 0x03: + Serial.println(F("TEMP DOWN")); + break; + case 0x04: + Serial.println(F("SWING (vertical)")); + break; + case 0x05: + Serial.println(F("FAN SPEED")); + break; + case 0x06: + Serial.println(F("TIMER")); + break; + case 0x07: + Serial.println(F("AIR FLOW (horizontal)")); + break; + case 0x08: + Serial.println(F("HOLD")); + break; + case 0x09: + Serial.println(F("SLEEP")); + break; + case 0x0A: + Serial.println(F("TURBO")); + break; + case 0x0B: + Serial.println(F("LAMP")); + break; + default: + Serial.println(F("Unknown")); + } + +/******************************************************************************** + * Byte[07]: POWER, FAN, SLEEP, HORIZONTAL, VERTICAL + * SLEEP ON: Bxxxxxxx1 (LSB) + * SLEEP OFF: Bxxxxxxx0 + * POWER ON: Bxxxxxx1x + * POWER OFF: Bxxxxxx0x + * VERTICAL SWING: Bxxxx01xx + * VERTICAL WIND: Bxxxx00xx + * VERTICAL FIXED: Bxxxx10xx + * HORIZONTAL SWING: Bxxx0xxxx + * HORIZONTAL OFF: Bxxx1xxxx + * FAN AUTO: Bx00xxxxx + * FAN3: Bx01xxxxx + * FAN2: Bx10xxxxx + * FAN1: Bx11xxxxx + *******************************************************************************/ + + // SLEEP ON/OFF + Serial.print(F("bytes[7] bit0 Sleep: ")); + switch (bytes[7]& B00000001) { + case B00000000: + Serial.println(F("Off")); + break; + case B00000001: + Serial.println(F("On")); + break; + default: + Serial.println(F("Unknown")); + } + + // POWER ON/OFF + Serial.print(F("bytes[7] bit1 Power: ")); + switch (bytes[7]& B00000010) { + case B00000000: + Serial.println(F("Off")); + break; + case B00000010: + Serial.println(F("On")); + break; + default: + Serial.println(F("Unknown")); + } + + // Vertical air swing + Serial.print(F("bytes[7] bit2-3 Vertical: ")); + switch (bytes[7] & B00001100) { + case B00000100: + Serial.println(F("Swing")); + break; + case 0x00: + Serial.println(F("Wind")); + break; + case B00001000: + Serial.println(F("Fixed")); + break; + default: + Serial.println(F("Unknown")); + } + + // Air flow (Horizontal) ON/OFF + Serial.print(F("bytes[7] bit4 Air flow: ")); + switch (bytes[7]& B00010000) { + case B00000000: + Serial.println(F("Swing")); + break; + case B00010000: + Serial.println(F("Fixed")); + break; + default: + Serial.println(F("Unknown")); + } + + // Fan Speed + Serial.print(F("bytes[7] bit5-6 Fan Speed: ")); + switch (bytes[7] & B01100000) { + case B00000000: + Serial.println(F("Auto")); + break; + case B00100000: + Serial.println(F("3")); + break; + case B01000000: + Serial.println(F("2")); + break; + case B01100000: + Serial.println(F("1")); + break; + default: + Serial.println(F("Unknown")); + } + +/******************************************************************************** + * Byte[09]: Mode, Temperature + * MODE AUTO: B000xxxxx + * MODE COOL: B001xxxxx + * MODE VENT: B011xxxxx + * MODE DRY: B010xxxxx + * MODE HEAT: B100xxxxx + * Temperature is determined by bit0-4: + * 0x00 = 16C + * 0x10 = 32C + *******************************************************************************/ + + // MODE + Serial.print(F("bytes[9] bit5-7 Mode: ")); + switch (bytes[9] & B11100000) { + case 0x00: + Serial.println(F("Auto")); + break; + case B00100000: + Serial.println(F("Cool")); + break; + case B01100000: + Serial.println(F("Ventilate")); + break; + case B01000000: + Serial.println(F("Dry")); + break; + case B10000000: + Serial.println(F("Heat")); + break; + default: + Serial.println("Unknown"); + } + + //Temperature + Serial.print("Bytes[9] Temperature: "); + Serial.print((bytes[9] & B00011111)+16); + Serial.println("°C"); + +// Checksum + bool checksum = true; + + for (int i = 0; i < 12; i+=2) { + checksum &= (bytes[i] + bytes[i+1] == 255); + } + + Serial.println(checksum?"Checksum: MATCHES":"Checksum: DOES NOT MATCH"); + + return true; + } + + return false; +} + diff --git a/rawirdecode.ino b/rawirdecode.ino index 48396e4..3192a05 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -16,6 +16,7 @@ bool decodeHitachi(byte *bytes, int byteCount); bool decodeSamsung(byte *bytes, int byteCount); bool decodeBallu(byte *bytes, int byteCount); bool decodeAUX(byte *bytes, int byteCount); +bool decodeZHLT01remote(byte *bytes, int byteCount); /* Raw IR decoder sketch! @@ -28,12 +29,17 @@ bool decodeAUX(byte *bytes, int byteCount); Code is public domain, check out www.ladyada.net and adafruit.com for more tutorials! + * VS1838 + * |---|- VCC (4) + * | O |- GND (3) + * |---|- OUT (2) */ + // We need to use the 'raw' pin reading methods // because timing is very important here and the digitalRead() // procedure is slower! -//uint8_t IRpin = 2; +// uint8_t IRpin = 11; // Digital pin #2 is the same as Pin D2 see // http://arduino.cc/en/Hacking/PinMapping168 for the 'raw' pin mapping @@ -45,6 +51,9 @@ bool decodeAUX(byte *bytes, int byteCount); #define IRpin 2 #endif +#define VCCPin 4 +#define GNDPin 3 + // the maximum pulse we'll listen for - 65 milliseconds is a long time #define MAXPULSE 65000 @@ -86,7 +95,12 @@ byte bytes[128]; void setup(void) { - Serial.begin(9600); + pinMode (VCCPin, OUTPUT); + pinMode (GNDPin, OUTPUT); + digitalWrite (VCCPin, HIGH); + digitalWrite (GNDPin, LOW); + + Serial.begin(115200); delay(1000); Serial.println(F("Select model to decode (this affects the IR signal timings detection):")); Serial.println(F("* '1' for Panasonic DKE>, Mitsubishi Electric, Fujitsu etc. codes")); @@ -276,6 +290,8 @@ void printPulses(void) { // Print the symbols (0, 1, H, h, W) Serial.println(F("Symbols:")); +// Serial.println("--1-------2-------3-------4-------5-------6-------7-------8-------9-------0-------1-------2-------"); +// Serial.println("--123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678"); Serial.println(symbols+1); // Print the decoded bytes @@ -349,8 +365,12 @@ void decodeProtocols() decodeHitachi(bytes, byteCount) || decodeSamsung(bytes, byteCount) || decodeBallu(bytes, byteCount) || - decodeAUX(bytes, byteCount))) + decodeAUX(bytes, byteCount) || + decodeZHLT01remote(bytes, byteCount) + )) { Serial.println(F("Unknown protocol")); + Serial.print("Bytecount: "); + Serial.println(byteCount); } } From 0b61f888331f98d6558fa3967b2e0d66e97467fc Mon Sep 17 00:00:00 2001 From: Toni Date: Tue, 3 Jul 2018 17:20:32 +0300 Subject: [PATCH 63/94] Gree YAC decoder --- Gree_YAC.cpp | 249 ++++++++++++++++++++++++++++++++++++++++++++++++ rawirdecode.ino | 2 + 2 files changed, 251 insertions(+) create mode 100644 Gree_YAC.cpp diff --git a/Gree_YAC.cpp b/Gree_YAC.cpp new file mode 100644 index 0000000..50efdf8 --- /dev/null +++ b/Gree_YAC.cpp @@ -0,0 +1,249 @@ +#include + +bool decodeGree_YAC(byte *bytes, int pulseCount) +{ + // If this looks like a Gree code... + if ( pulseCount == 142 ) { + Serial.println(F("Looks like a Gree YAC protocol")); + + // Check if the checksum matches + uint8_t checksum0 = ( + (bytes[0] & 0x0F) + + (bytes[1] & 0x0F) + + (bytes[2] & 0x0F) + + (bytes[3] & 0x0F) + + ((bytes[4] & 0xF0) >> 4) + + ((bytes[5] & 0xF0) >> 4) + + ((bytes[6] & 0xF0) >> 4) + + 0x0A) & 0xF; + + uint8_t checksum1 = ( + (bytes[8] & 0x0F) + + (bytes[9] & 0x0F) + + (bytes[10] & 0x0F) + + (bytes[11] & 0x0F) + + ((bytes[12] & 0xF0) >> 4) + + ((bytes[13] & 0xF0) >> 4) + + ((bytes[14] & 0xF0) >> 4) + + 0x0A) & 0xF; + + + //byte 7[7:4] contais checksum for bytes 0-6 + //byte 15[7:4] contais checksum for bytes 8-14 + if ((checksum0 == (bytes[7]>>4)) && (checksum1 == (bytes[15]>>4))) { + Serial.println(F("Checksum matches")); + } else { + Serial.println(F("Checksum does not match")); + } + + // Power mode + // in bytes[0] and bytes[8] + if (bytes[0] == bytes[8]){ + switch (bytes[0] & 0x08) { + case 0x00: + Serial.println(F("POWER OFF")); + break; + case 0x08: + Serial.println(F("POWER ON")); + break; + } + } + + // Eco mode (8C) + // bit2 of byte 7 (bits 7:4 are checksum) + switch (bytes[7] & 0x04) { + case 0x00: + Serial.println(F("ECO OFF")); + break; + case 0x04: + Serial.println(F("ECO ON")); + break; + } + + // Turbo mode + // byte[2] bit 4 + switch (bytes[2] & 0x10) { + case 0x00: + Serial.println(F("TURBO OFF")); + break; + case 0x10: + Serial.println(F("TURBO ON")); + break; + } + + // Operating mode + // in bytes[0] and bytes[8] + if (bytes[0] == bytes[8]){ + switch (bytes[0] & 0x07) { + case 0x00: + Serial.println(F("MODE AUTO")); + break; + case 0x04: + Serial.println(F("MODE HEAT")); + break; + case 0x01: + Serial.println(F("MODE COOL")); + break; + case 0x02: + Serial.println(F("MODE DRY")); + break; + case 0x03: + Serial.println(F("MODE FAN")); + break; + } + } + + // Temperature + // temp in two identical bytes 1 and 9, bits [3:0] + // bits [7:4] are used with timers but can be left as 0 + if (bytes[1]==bytes[9]){ + Serial.print(F("Temperature: ")); + Serial.println((bytes[1] & 0x0F) + 16); + } + + // Fan speed + if (bytes[0] == bytes[8]){ //same fan speed in two bytes + // note: fan speed 0-5 also in byte 14 bits [6:4] as 0-5 + // speed 0-3 in bytes[0] and bytes[8] bits [5:4] 0-3, speed 4-5 set as 3. + switch (bytes[0] & 0x30) { + case 0x00: + Serial.println(F("FAN: AUTO")); + break; + case 0x10: + Serial.println(F("FAN: 1")); + break; + case 0x20: + Serial.println(F("FAN: 2")); + break; + case 0x30: + switch (bytes[14] & 0x70) { + case 0x30: + Serial.println(F("FAN: 3")); + break; + case 0x40: + Serial.println(F("FAN: 4")); + break; + case 0x50: + Serial.println(F("FAN: 5")); + break; + } + break; + } + } + + // Sleep mode + // Note that YAC1FB has also Sleep 3-mode which sends 24 bytes and is not decoded here + switch (bytes[0] & 0x80) { + case 0x80: + Serial.println(F("SLEEP: ON 1")); + break; + case 0x00: + if ((bytes[12] & 0x01) == 1){ + Serial.println(F("SLEEP: ON 2")); + } else { + Serial.println(F("SLEEP: OFF")); + } + break; + } + + // Air direction swing + // in bytes[0] and bytes[8] + if (bytes[0] == bytes[8]){ + switch (bytes[0] & 0x40) { + case 0x40: + Serial.println(F("SWING: ON")); + break; + case 0x00: + Serial.println(F("SWING: OFF")); + break; + } + } + + // Vertical air direction + switch (bytes[4] & 0x0F){ + case 0x00: + Serial.println(F("VERT. VANE STOP")); + break; + case 0x01: + Serial.println(F("VERT. VANE SWEEP UP-DOWN (1-5)")); + break; + case 0x02: + Serial.println(F("VERT. VANE UP (1)")); + break; + case 0x03: + Serial.println(F("VERT. VANE HIGH (2)")); + break; + case 0x04: + Serial.println(F("VERT. VANE CENTER (3)")); + break; + case 0x05: + Serial.println(F("VERT. VANE LOW (4)")); + break; + case 0x06: + Serial.println(F("VERT. VANE DOWN (5) ")); + break; + case 0x07: + Serial.println(F("VERT. VANE SWEEP 3-5")); + break; + case 0x09: + Serial.println(F("VERT. VANE SWEEP 2-4")); + break; + case 0x0B: + Serial.println(F("VERT. VANE SWEEP 1-3")); + break; + } + + // Horizontal air direction + switch (bytes[4] & 0xF0){ + case 0x00: + Serial.println(F("HORIZ. VANE STOP")); + break; + case 0x10: + Serial.println(F("HORIZ. VANE SWEEP LEFT-RIGHT (1-5)")); + break; + case 0x20: + Serial.println(F("HORIZ. VANE FAR LEFT (1)")); + break; + case 0x30: + Serial.println(F("HORIZ. VANE LEFT (2)")); + break; + case 0x40: + Serial.println(F("HORIZ. VANE CENTER (3)")); + break; + case 0x50: + Serial.println(F("HORIZ. VANE RIGHT (4)")); + break; + case 0x60: + Serial.println(F("HORIZ. VANE FAR RIGHT (5)")); + break; + case 0xC0: + Serial.println(F("HORIZ. VANE LEFT (1) AND RIGHT (5)")); + break; + case 0xD0: + Serial.println(F("HORIZ. VANE SWEEP TO CENTER")); + break; + } + + // Display temperature + // bytes[5] bit [0:1] + switch (bytes[5] & 0x3) { + case 0x00: + Serial.println(F("DISPLAY: SET TEMP")); + break; + case 0x01: + Serial.println(F("DISPLAY: SET TEMP")); + break; + case 0x02: + Serial.println(F("DISPLAY: INDOOR TEMP")); + break; + case 0x03: + Serial.println(F("DISPLAY: OUTDOOR TEMP")); + break; + + } + return true; + } + + return false; +} + diff --git a/rawirdecode.ino b/rawirdecode.ino index 48396e4..576ea21 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -9,6 +9,7 @@ bool decodeCarrier(byte *bytes, int byteCount); bool decodePanasonicCKP(byte *bytes, int byteCount); bool decodeHyundai(byte *bytes, int pulseCount); bool decodeGree(byte *bytes, int pulseCount); +bool decodeGree_YAC(byte *bytes, int pulseCount); bool decodeFuego(byte *bytes, int byteCount); bool decodeToshiba(byte *bytes, int byteCount); bool decodeNibe(byte *bytes, char* symbols, int bitCount); @@ -343,6 +344,7 @@ void decodeProtocols() decodePanasonicCKP(bytes, byteCount) || decodeHyundai(bytes, currentpulse) || decodeGree(bytes, currentpulse) || + decodeGree_YAC(bytes, currentpulse) || decodeFuego(bytes, byteCount) || decodeToshiba(bytes, byteCount) || decodeNibe(bytes, symbols, currentpulse) || From d1fd07b57c2a59841cc65676814351922629b6d2 Mon Sep 17 00:00:00 2001 From: mmanza Date: Sat, 21 Jul 2018 16:40:09 -0300 Subject: [PATCH 64/94] add decode to ballu / bgh protocol --- Ballu.cpp | 81 ++++++++++++++++++++++++++++++++++++++++++++++--- rawirdecode.ino | 40 ++++++++++++++++++++---- 2 files changed, 110 insertions(+), 11 deletions(-) diff --git a/Ballu.cpp b/Ballu.cpp index 2aa9d33..b8c56a2 100644 --- a/Ballu.cpp +++ b/Ballu.cpp @@ -2,14 +2,22 @@ bool decodeBallu(byte *bytes, int byteCount) { - // If this looks like a Ballu code... + // If this looks like a Ballu, BGH code... + // Remote control "J1-05(E)" if ( byteCount == 21 && bytes[0] == 0x83 && bytes[1] == 0x06 ) { - Serial.println(F("Looks like a Ballu protocol")); + Serial.println(F("Looks like a Ballu, BGH protocol")); // Power mode + //its send same signal for on/off its depends unit status + if((bytes[2] >> 2) == 0x01){ + Serial.println(F("On/Off action ")); + } + // TBD + //bytes 6 and 7 are remote control time (hour and minutes) // Operating mode + //Serial.print(F("Hex Mode ")); Serial.print (bytes[3],BIN); Serial.print (" "); Serial.println (bytes[3] & 0x07); switch (bytes[3] & 0x07) { case 0x00: Serial.println(F("MODE HEAT")); @@ -25,13 +33,20 @@ bool decodeBallu(byte *bytes, int byteCount) break; } + // swing + if((bytes[2] >> 4) == 0x08 && (bytes[8] >> 4) == 0x04 ){ + Serial.println(F("Vertical Swing")); + + } + + // Temperature Serial.print(F("Temperature: ")); Serial.println((bytes[3] >> 4) + 16); // Fan speed switch (bytes[02] & 0x03) { - case 0xA0: + case 0x00: Serial.println(F("FAN: AUTO")); break; case 0x01: @@ -45,8 +60,64 @@ bool decodeBallu(byte *bytes, int byteCount) break; } - // Check if the checksum matches - // TBD + // pressed buton byte 15 + // Serial.print(F("Button Pressed: ")); Serial.print (bytes[15],BIN); Serial.print (" | "); Serial.println (bytes[15], HEX); + switch (bytes[15]) { + case 0x01: + Serial.println(F("Button: on/off")); + break; + case 0x02: + Serial.println(F("Button: Temp")); + break; + case 0x04: + Serial.println(F("Button: Super")); + break; + case 0x06: + Serial.println(F("Button: Modo")); + break; + case 0x07: + Serial.println(F("Button: Vertical Swing")); + break; + case 0x08: + Serial.println(F("Button: Horizontal Swing")); + break; + case 0x11: + Serial.println(F("Button: Fan")); + break; + case 0x17: + Serial.println(F("Button: Smart")); + break; + case 0x0D: + Serial.println(F("Button: iFeel")); + break; + } + + // Check if the checksum matches + // it has 2 checksums one at byte 13 and other at byte 20 + // first is XOR of bytes 2 to 12 + // second is XOR of bytes 14 to 19 + byte checksum = 0; + for (int i=2; i<13; i++) { + // Serial.print(bytes[i], HEX); Serial.print(" | "); Serial.println(bytes[i], BIN); + checksum ^= bytes[i]; + // Serial.print(checksum, HEX); Serial.print(" | "); Serial.println(checksum, BIN); + } + if (checksum == bytes[13]) { + Serial.println(F("Firs Checksum matches")); + } else { + Serial.println(F("Fist Checksum does not match")); + } + checksum = 0; + for (int i=14; i<20; i++) { + // Serial.print(bytes[i], HEX); Serial.print(" | "); Serial.println(bytes[i], BIN); + checksum ^= bytes[i]; + // Serial.print(checksum, HEX); Serial.print(" | "); Serial.println(checksum, BIN); + } + if (checksum == bytes[20]) { + Serial.println(F("Second Checksum matches")); + } else { + Serial.println(F("Second Checksum does not match")); + } return true; } diff --git a/rawirdecode.ino b/rawirdecode.ino index ad06840..0d488af 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -279,8 +279,8 @@ void printPulses(void) { // Print the symbols (0, 1, H, h, W) Serial.println(F("Symbols:")); -// Serial.println("--1-------2-------3-------4-------5-------6-------7-------8-------9-------0-------1-------2-------"); -// Serial.println("--123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678"); + //Serial.println("--1-------2-------3-------4-------5-------6-------7-------8-------9-------0-------1-------2-------3-------4-------5-------"); + //Serial.println("--123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678"); Serial.println(symbols+1); // Print the decoded bytes @@ -288,17 +288,34 @@ void printPulses(void) { // Decode the string of bits to a byte array for (uint16_t i = 0; i < currentpulse; i++) { + if (symbols[i] == '0' || symbols[i] == '1') { + + if (bitCount == 0) { + Serial.print(byteCount); Serial.print(": "); + } + currentByte >>= 1; bitCount++; if (symbols[i] == '1') { currentByte |= 0x80; } + + Serial.print(symbols[i]); + if (bitCount == 4) { + Serial.print("|"); + } if (bitCount == 8) { bytes[byteCount++] = currentByte; bitCount = 0; + Serial.print(" | "); + printByte(currentByte); + Serial.print(" | "); + + Serial.print(currentByte ,BIN); + Serial.println(" "); } } else { // Ignore bits which do not form octets bitCount = 0; @@ -308,10 +325,12 @@ void printPulses(void) { // Print the byte array for (int i = 0; i < byteCount; i++) { - if (bytes[i] < 0x10) { - Serial.print(F("0")); - } - Serial.print(bytes[i],HEX); + // if (bytes[i] < 0x10) { + // Serial.print(F("0")); + // } + // Serial.print(bytes[i],HEX); + + printByte(bytes[i]); if ( i < byteCount - 1 ) { Serial.print(F(",")); } @@ -334,6 +353,15 @@ void printPulses(void) { Serial.println(space_one_avg); } +void printByte(byte bytetoprint){ + if (bytetoprint < 0x10) { + Serial.print(F("0")); + } + Serial.print(bytetoprint ,HEX); + +} + + void decodeProtocols() { Serial.println(F("Decoding known protocols...")); From 04ac6b54c061968c256c48fca25cffb7a98a9d3d Mon Sep 17 00:00:00 2001 From: Benjamin Townsend Date: Sun, 16 Sep 2018 12:13:10 -0700 Subject: [PATCH 65/94] Add binary zero padding and formatting --- rawirdecode.ino | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/rawirdecode.ino b/rawirdecode.ino index 0d488af..7028981 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -292,6 +292,8 @@ void printPulses(void) { if (symbols[i] == '0' || symbols[i] == '1') { if (bitCount == 0) { + if (byteCount < 10) + Serial.print("0"); Serial.print(byteCount); Serial.print(": "); } @@ -310,12 +312,14 @@ void printPulses(void) { if (bitCount == 8) { bytes[byteCount++] = currentByte; bitCount = 0; - Serial.print(" | "); - printByte(currentByte); - Serial.print(" | "); - - Serial.print(currentByte ,BIN); - Serial.println(" "); + Serial.print(" | "); + printByte(currentByte); + Serial.print(" | "); + + for (int mask = 0x80; mask > 0; mask >>= 1) { + Serial.print((currentByte & mask) ? "1" : "0"); + } + Serial.println(" "); } } else { // Ignore bits which do not form octets bitCount = 0; @@ -353,12 +357,11 @@ void printPulses(void) { Serial.println(space_one_avg); } -void printByte(byte bytetoprint){ - if (bytetoprint < 0x10) { - Serial.print(F("0")); - } - Serial.print(bytetoprint ,HEX); - +void printByte(byte bytetoprint) { + if (bytetoprint < 0x10) { + Serial.print(F("0")); + } + Serial.print(bytetoprint, HEX); } From 4a7ed9a1263a76d0b229d4ebac6398fc4be2d0de Mon Sep 17 00:00:00 2001 From: Oleg Date: Sun, 4 Nov 2018 11:36:17 +0500 Subject: [PATCH 66/94] Airwell RC-3 remote support. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PoC. Works with https://www.aliexpress.com/item/A-C-controller-Air-Conditioner-air-conditioning-remote-control-FOR-YORK-Electra-Airwell-Emailair-RC-4/32634241533.html Reads fine in receive mode 1 or 2 Only supports 2 operation modes — ♺ and ❄ Reports: - Mode: 1 - ♺, 2 - ❄ - Fan speed: 1, 2, 3, Auto - Set temperature - If power button is pressed, reports it --- Airwell.cpp | 107 ++++++++++++++++++++++++++++++++++++++++++++++++ rawirdecode.ino | 2 + 2 files changed, 109 insertions(+) create mode 100644 Airwell.cpp diff --git a/Airwell.cpp b/Airwell.cpp new file mode 100644 index 0000000..44480f6 --- /dev/null +++ b/Airwell.cpp @@ -0,0 +1,107 @@ +#include + +// PoC Airwell RC-3 remote support. +// Works with https://www.aliexpress.com/item/A-C-controller-Air-Conditioner-air-conditioning-remote-control-FOR-YORK-Electra-Airwell-Emailair-RC-4/32634241533.html +// Reads fine in receive mode 1 or 2 +// Only supports 2 operation modes — ♺ and ❄ +// Reports: +// - Mode: 1 - ♺, 2 - ❄ +// - Fan speed: 1, 2, 3, Auto +// - Set temperature +// - If power button is pressed, reports it + +bool decodeAirwell(char* symbols, int bitCount) +{ + // If wrong package length + if (bitCount != 110) { return false; } + // If incorrect header + if (symbols[1] != 'H' || symbols[2] != 'h') { return false; } + // If incorrect first payload footer + if (symbols[35] != 'H' || symbols[36] != 'h') { return false; } + + Serial.println(F("Looks like an Airwell RC-3 remote")); + + if (symbols[3] == 'H' && symbols[4] == 'h' && (symbols[5] == '0' || symbols[5] == '1')) { + Serial.print(F("POWER")); + return true; + } + + int acMode; + if (symbols[3] == '0' && symbols[4] == '1' && symbols[5] == '0') { acMode = 1; } + else if (symbols[3] == '0' && symbols[4] == '0' && symbols[5] == '1') { acMode = 2; } + else { + Serial.println(F("Mode: UNKNOWN")); + return false; + } + + Serial.print(F("Mode: ")); + Serial.println(acMode); + + int acSpeed; + if (symbols[6] == 'H' && symbols[7] == 'h' && symbols[8] == '0' && symbols[9] == '0') { acSpeed = 1; } + else if (symbols[6] == 'H' && symbols[7] == 'h' && symbols[8] == 'H' && symbols[9] == 'h') { acSpeed = 2; } + else if (symbols[6] == '0' && symbols[7] == 'H' && symbols[8] == 'h' && symbols[9] == '0') { acSpeed = 3; } + else if (symbols[6] == '0' && symbols[7] == '0' && symbols[8] == 'H' && symbols[9] == 'h') { acSpeed = 0; } + else { + Serial.println(F("Speed: UNKNOWN")); + return false; + } + + Serial.print(F("Speed: ")); + Serial.println((acSpeed == 0) ? "AUTO" : String(acSpeed)); + + int acTemp; + if (symbols[13] == '0' && symbols[14] == '0' && symbols[15] == '0' && symbols[16] == '1' && symbols[17] == 'H' && symbols[18] == 'h') { acTemp = 16; } + else if (symbols[13] == '0' && symbols[14] == '0' && symbols[15] == '1' && symbols[16] == 'H' && symbols[17] == 'h' && symbols[18] == '0') { acTemp = 17; } + else if (symbols[13] == '0' && symbols[14] == '0' && symbols[15] == '1' && symbols[16] == '0' && symbols[17] == 'H' && symbols[18] == 'h') { acTemp = 18; } + else if (symbols[13] == '0' && symbols[14] == '1' && symbols[15] == 'H' && symbols[16] == 'h' && symbols[17] == '0' && symbols[18] == '0') { acTemp = 19; } + else if (symbols[13] == '0' && symbols[14] == '1' && symbols[15] == 'H' && symbols[16] == 'h' && symbols[17] == 'H' && symbols[18] == 'h') { acTemp = 20; } + else if (symbols[13] == '0' && symbols[14] == '1' && symbols[15] == '0' && symbols[16] == 'H' && symbols[17] == 'h' && symbols[18] == '0') { acTemp = 21; } + else if (symbols[13] == '0' && symbols[14] == '1' && symbols[15] == '0' && symbols[16] == '0' && symbols[17] == 'H' && symbols[18] == 'h') { acTemp = 22; } + else if (symbols[13] == '1' && symbols[14] == 'H' && symbols[15] == 'h' && symbols[16] == '0' && symbols[17] == '0' && symbols[18] == '0') { acTemp = 23; } + else if (symbols[13] == '1' && symbols[14] == 'H' && symbols[15] == 'h' && symbols[16] == '1' && symbols[17] == 'H' && symbols[18] == 'h') { acTemp = 24; } + else if (symbols[13] == '1' && symbols[14] == 'H' && symbols[15] == 'h' && symbols[16] == 'H' && symbols[17] == 'h' && symbols[18] == '0') { acTemp = 25; } + else if (symbols[13] == '1' && symbols[14] == 'H' && symbols[15] == 'h' && symbols[16] == '0' && symbols[17] == 'H' && symbols[18] == 'h') { acTemp = 26; } + else if (symbols[13] == '1' && symbols[14] == '0' && symbols[15] == 'H' && symbols[16] == 'h' && symbols[17] == '0' && symbols[18] == '0') { acTemp = 27; } + else if (symbols[13] == '1' && symbols[14] == '0' && symbols[15] == 'H' && symbols[16] == 'h' && symbols[17] == 'H' && symbols[18] == 'h') { acTemp = 28; } + else if (symbols[13] == '1' && symbols[14] == '0' && symbols[15] == '0' && symbols[16] == 'H' && symbols[17] == 'h' && symbols[18] == '0') { acTemp = 29; } + else if (symbols[13] == '1' && symbols[14] == '0' && symbols[15] == '0' && symbols[16] == '0' && symbols[17] == 'H' && symbols[18] == 'h') { acTemp = 30; } + else { + Serial.println(F("Temp: UNKNOWN")); + return false; + } + + Serial.print(F("Temp: ")); + Serial.println(acTemp); + + return true; +} + + +/* Example payloads. They are repeated thrice in 110 symbol packet, only the first repeat is shown: + + M FAN TEMP + / \/ \ / \ +16/1: Hh010Hh000000001Hh0000000000000001Hh +17/1: Hh010Hh00000001Hh00000000000000001Hh +18/1: Hh010Hh000000010Hh0000000000000001Hh +19/1: Hh010Hh0000001Hh000000000000000001Hh +20/1: Hh010Hh0000001HhHh0000000000000001Hh +20/2: Hh010HhHh00001HhHh0000000000000001Hh +20/3: Hh0100Hh000001HhHh0000000000000001Hh +20/A: Hh01000Hh00001HhHh0000000000000001Hh +21/1: Hh010Hh00000010Hh00000000000000001Hh +22/1: Hh010Hh000000100Hh0000000000000001Hh +23/1: Hh010Hh000001Hh0000000000000000001Hh +24/1: Hh010Hh000001Hh1Hh0000000000000001Hh +25/1: Hh010Hh000001HhHh00000000000000001Hh +26/1: Hh010Hh000001Hh0Hh0000000000000001Hh +27/1: Hh010Hh0000010Hh000000000000000001Hh +28/1: Hh010Hh0000010HhHh0000000000000001Hh +29/1: Hh010Hh00000100Hh00000000000000001Hh +30/1: Hh010Hh000001000Hh0000000000000001Hh +30/2: Hh010HhHh0001000Hh0000000000000001Hh +30/3: Hh0100Hh00001000Hh0000000000000001Hh +30/A: Hh01000Hh0001000Hh0000000000000001Hh + +*/ diff --git a/rawirdecode.ino b/rawirdecode.ino index 7028981..9045098 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -14,6 +14,7 @@ bool decodeGree_YAC(byte *bytes, int pulseCount); bool decodeFuego(byte *bytes, int byteCount); bool decodeToshiba(byte *bytes, int byteCount); bool decodeNibe(byte *bytes, char* symbols, int bitCount); +bool decodeAirwell(char* symbols, int bitCount); bool decodeHitachi(byte *bytes, int byteCount); bool decodeSamsung(byte *bytes, int byteCount); bool decodeBallu(byte *bytes, int byteCount); @@ -384,6 +385,7 @@ void decodeProtocols() decodeFuego(bytes, byteCount) || decodeToshiba(bytes, byteCount) || decodeNibe(bytes, symbols, currentpulse) || + decodeAirwell(symbols, currentpulse) || decodeHitachi(bytes, byteCount) || decodeSamsung(bytes, byteCount) || decodeBallu(bytes, byteCount) || From 7e1a96e9a65eba27cf500a7479777e29c7e2f303 Mon Sep 17 00:00:00 2001 From: nelgi <46314439+nelgi@users.noreply.github.com> Date: Sun, 17 Nov 2019 22:51:38 +0200 Subject: [PATCH 67/94] Update Carrier.cpp Fix checksum calculation and add Carrier (Toshiba DaiseikAi) protocol #3 --- Carrier.cpp | 148 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 114 insertions(+), 34 deletions(-) diff --git a/Carrier.cpp b/Carrier.cpp index 0f705d7..627fa9f 100644 --- a/Carrier.cpp +++ b/Carrier.cpp @@ -29,17 +29,12 @@ bool decodeCarrier1(byte *bytes, int byteCount) if ( byteCount == 18 && bytes[0] == 0x4F && bytes[1] == 0xB0 && (memcmp(bytes, bytes+9, 9) == 0)) { Serial.println(F("Looks like a Carrier protocol #1")); - // Check if the checksum matches - byte checksum = 0; + // Check if the checksum matches + byte checksum = 0x00; - checksum = bitReverse(bytes[0]) + - bitReverse(bytes[1]) + - bitReverse(bytes[2]) + - bitReverse(bytes[3]) + - bitReverse(bytes[4]) + - bitReverse(bytes[5]) + - bitReverse(bytes[6]) + - bitReverse(bytes[7]); + for (byte i = 0; i < 8; i++) { + checksum ^= bitReverse(bytes[i]); + } switch (bytes[6] & 0x0F) { case 0x00: @@ -68,41 +63,18 @@ bool decodeCarrier1(byte *bytes, int byteCount) break; case 0x00: Serial.println(F("MODE: AUTO")); - checksum += 0x02; - switch (bytes[6] & 0x0F) { - case 0x02: // FAN1 - case 0x03: // FAN5 - case 0x06: // FAN2 - checksum += 0x80; - break; - } break; case 0x80: Serial.println(F("MODE: COOL")); break; case 0x40: Serial.println(F("MODE: DRY")); - checksum += 0x02; break; case 0xC0: Serial.println(F("MODE: HEAT")); - switch (bytes[6] & 0x0F) { - case 0x05: // FAN4 - case 0x06: // FAN2 - checksum += 0xC0; - break; - } break; case 0x20: Serial.println(F("MODE: FAN")); - checksum += 0x02; - switch (bytes[6] & 0x0F) { - case 0x02: // FAN1 - case 0x03: // FAN5 - case 0x06: // FAN2 - checksum += 0x80; - break; - } break; } @@ -231,7 +203,115 @@ bool decodeCarrier2(byte *bytes, int byteCount) return false; } +bool decodeCarrier3(byte *bytes, int byteCount) +{ + // If this looks like a Carrier code... + if ( byteCount == 20 && bytes[0] == 0x4F && bytes[1] == 0xB0 && (memcmp(bytes, bytes+10, 10) == 0)) { + Serial.println(F("Looks like a Carrier protocol #3")); + + // Check if the checksum matches + byte checksum = 0x00; + + for (byte i = 0; i < 9; i++) { + checksum ^= bitReverse(bytes[i]); + } + + switch (bytes[6] & 0x0F) { + case 0x00: + Serial.println(F("FAN: AUTO")); + break; + case 0x02: + Serial.println(F("FAN: 1")); + break; + case 0x06: + Serial.println(F("FAN: 2")); + break; + case 0x01: + Serial.println(F("FAN: 3")); + break; + case 0x05: + Serial.println(F("FAN: 4")); + break; + case 0x03: + Serial.println(F("FAN: 5")); + break; + } + + switch (bytes[6] & 0xF0) { + case 0xE0: + Serial.println(F("MODE: OFF")); + break; + case 0x00: + Serial.println(F("MODE: AUTO")); + break; + case 0x80: + Serial.println(F("MODE: COOL")); + break; + case 0x40: + Serial.println(F("MODE: DRY")); + break; + case 0xC0: + Serial.println(F("MODE: HEAT")); + break; + case 0x20: + Serial.println(F("MODE: FAN")); + break; + } + + if (bytes[2] == 0x20 && bytes[3] == 0xDF) + { + if (bytes[8] == 0x20) + { + Serial.println(F("ADVANCED MODE: Frost guard +8c")); + } + else if (bytes[8] == 0xC0) + { + Serial.println(F("ADVANCED MODE: ECO")); + } + else + { + Serial.println(F("ADVANCED MODE: Hi POWER")); + } + } + + checksum = bitReverse(checksum); + + const byte temperatures[] = { 17, 25, 21, 29, 19, 27, 23, 00, 18, 26, 22, 30, 20, 28, 24 }; + + + Serial.print(F("Temperature: ")); + Serial.println(temperatures[bytes[5]]); + + char bin1[9]; + char bin2[9]; + char bin3[9]; + + snprintf(bin1, sizeof(bin1), BYTETOBINARYPATTERN, BYTETOBINARY(checksum)); + snprintf(bin2, sizeof(bin2), BYTETOBINARYPATTERN, BYTETOBINARY(bytes[9])); + snprintf(bin3, sizeof(bin3), BYTETOBINARYPATTERN, BYTETOBINARY(bytes[6])); + + + Serial.print(F("ModeFan ")); + Serial.println(bin3); + + + Serial.print(F("Checksum ")); + Serial.print(bin1); + + if (checksum == bytes[9]) { + Serial.println(F(" matches")); + } else { + Serial.println(F(" does not match real")); + Serial.print(F("checksum ")); + Serial.println(bin2); + } + return true; + } + + return false; +} + bool decodeCarrier(byte *bytes, int byteCount) { - return decodeCarrier1(bytes, byteCount) || decodeCarrier2(bytes, byteCount); + return decodeCarrier1(bytes, byteCount) || decodeCarrier2(bytes, byteCount) || decodeCarrier3(bytes, byteCount); } From 2bb52488f43b093941509d3fa614adc54db8284d Mon Sep 17 00:00:00 2001 From: nelgi <46314439+nelgi@users.noreply.github.com> Date: Tue, 19 Nov 2019 22:57:26 +0200 Subject: [PATCH 68/94] Fix the "advanced" modes --- Carrier.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Carrier.cpp b/Carrier.cpp index 627fa9f..70a2e70 100644 --- a/Carrier.cpp +++ b/Carrier.cpp @@ -262,15 +262,15 @@ bool decodeCarrier3(byte *bytes, int byteCount) { if (bytes[8] == 0x20) { - Serial.println(F("ADVANCED MODE: Frost guard +8c")); + Serial.println(F("ADVANCED: Frost guard (+8°C) Mode")); } else if (bytes[8] == 0xC0) { - Serial.println(F("ADVANCED MODE: ECO")); + Serial.println(F("ADVANCED: Eco/Sleep Mode")); } - else + else if (bytes[8] == 0x80) { - Serial.println(F("ADVANCED MODE: Hi POWER")); + Serial.println(F("ADVANCED: Hi POWER Mode")); } } From a3a41d4cb658f5a62545c2cd0c7433d7a8820a10 Mon Sep 17 00:00:00 2001 From: Patrick Hamers Date: Sun, 29 Dec 2019 21:06:45 +0100 Subject: [PATCH 69/94] Added support for Mitsubishi Heavy FDTCxxVF --- MitsubishiHeavy.cpp | 78 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/MitsubishiHeavy.cpp b/MitsubishiHeavy.cpp index c491f2b..15b8683 100644 --- a/MitsubishiHeavy.cpp +++ b/MitsubishiHeavy.cpp @@ -136,8 +136,8 @@ bool decodeMitsubishiHeavy(byte *bytes, int byteCount) return true; } else if ( byteCount == 19 && bytes[0] == 0x52 && bytes[1] == 0xAE && bytes[2] == 0xC3 && bytes[3] == 0x1A && bytes[4] == 0xE5) { - Serial.println(F("Looks like a Mitsubishi Heavy ZM-S protocol")); - Serial.println(F("Model SRKxxZM-S Remote Control RLA502A700B")); + Serial.println(F("Looks like a Mitsubishi Heavy ZM-S protocol (Model SRKxxZM-S Remote Control RLA502A700B) or")); + Serial.println(F("looks like a Mitsubishi Heavy ZS-S protocol (Model SRKxxZS-S Remote Control RLA502A700L)")); // Power mode switch (bytes[5] & 0x08) { @@ -301,6 +301,80 @@ bool decodeMitsubishiHeavy(byte *bytes, int byteCount) Serial.println(F("OFF")); break; } + return true; + } else if ( byteCount == 8 && bytes[0] == 0x0A) { + Serial.println(F("Looks like a Mitsubishi Heavy FDTC protocol (Model FDTCxxVF Remote Control PJA502A704AA)")); + + // Power mode + switch (bytes[2] & 0x80) { + case 0x00: + Serial.println(F("POWER OFF")); + break; + case 0x80: + Serial.println(F("POWER ON")); + break; + } + + // Operating mode + switch (bytes[2] & 0x70) { + case 0x00: + Serial.println(F("MODE AUTO")); + break; + case 0x40: + Serial.println(F("MODE HEAT")); + break; + case 0x20: + Serial.println(F("MODE COOL")); + break; + case 0x10: + Serial.println(F("MODE DRY")); + break; + case 0x30: + Serial.println(F("MODE FAN")); + break; + } + + // Temperature + Serial.print(F("Temperature: ")); + Serial.println((bytes[2] & 0x0F) + 16); + + // Fan speed + switch (bytes[1] & 0x30) { + case 0x00: + Serial.println(F("FAN 1")); + break; + case 0x10: + Serial.println(F("FAN 2")); + break; + case 0x20: + Serial.println(F("FAN 3")); + break; + } + + // Vertical air direction + Serial.print(F("Vertical air direction: ")); + if ((bytes[1] & 0b01000000) == 0x40 ) + { + Serial.println(F("SWING")); + } + else + { + switch ((bytes[3] & 0x30)) { + case 0x00: + Serial.println(F("UP")); + break; + case 0x10: + Serial.println(F("MIDDLE UP")); + break; + case 0x20: + Serial.println(F("MIDDLE DOWN")); + break; + case 0x30: + Serial.println(F("DOWN")); + break; + } + } + return true; } return false; From 353eb9fcce5f56dd25dfb2ec08c4211d0e72916d Mon Sep 17 00:00:00 2001 From: Panayotis Kouvarakis Date: Tue, 16 Jun 2020 19:29:22 +0300 Subject: [PATCH 70/94] Add support for Mitsubishi FD-50VF and RC KM09D/0096695 additional options --- MitsubishiElectric.cpp | 408 +++++++++++++++++++++++------------------ 1 file changed, 232 insertions(+), 176 deletions(-) diff --git a/MitsubishiElectric.cpp b/MitsubishiElectric.cpp index 414b7b2..81f82c9 100644 --- a/MitsubishiElectric.cpp +++ b/MitsubishiElectric.cpp @@ -1,176 +1,232 @@ -#include - -bool decodeMitsubishiElectric(byte *bytes, int byteCount) -{ - // If this looks like a Mitsubishi FD-25 or FE code... - if ( byteCount == 36 && bytes[0] == 0x23 && - ( (memcmp(bytes, bytes+18, 17) == 0) || - ((memcmp(bytes, bytes+18, 14) == 0) && bytes[32] == 0x24) ) ){ - Serial.println(F("Looks like a Mitsubishi FD / FE / MSY series protocol")); - - // Check if the checksum matches - byte checksum = 0; - - for (int i=0; i<17; i++) { - checksum += bytes[i]; - } - - if (checksum == bytes[17]) { - Serial.println(F("Checksum matches")); - } else { - Serial.println(F("Checksum does not match")); - } - - // Power mode - switch (bytes[5]) { - case 0x00: - Serial.println(F("POWER OFF")); - break; - case 0x20: - Serial.println(F("POWER ON")); - break; - default: - Serial.println(F("POWER unknown")); - break; - } - - // Operating mode - switch (bytes[6] & 0x38) { // 0b00111000 - case 0x38: - Serial.println(F("MODE FAN")); - break; - case 0x20: - Serial.println(F("MODE AUTO")); - break; - case 0x08: - if (bytes[15] == 0x20) { - Serial.println(F("MODE MAINTENANCE HEAT (FE only)")); - } else { - Serial.println(F("MODE HEAT")); - } - break; - case 0x18: - Serial.println(F("MODE COOL")); - break; - case 0x10: - Serial.println(F("MODE DRY")); - break; - default: - Serial.println(F("MODE unknown")); - break; - } - - // I-See - switch (bytes[6] & 0x40) { // 0b01000000 - case 0x40: - Serial.println(F("I-See: ON")); - break; - case 0x00: - Serial.println(F("I-See: OFF")); - break; - } - - // Plasma - switch (bytes[15] & 0x40) { // 0b01000000 - case 0x40: - Serial.println(F("Plasma: ON")); - break; - case 0x00: - Serial.println(F("Plasma: OFF")); - break; - } - - // Temperature - Serial.print(F("Temperature: ")); - if (bytes[7] == 0x00) { - Serial.println(F("10")); - } else { - Serial.println(bytes[7] + 16); - } - - // Fan speed - switch (bytes[9] & 0x07) { // 0b00000111 - case 0x00: - Serial.println(F("FAN AUTO")); - break; - case 0x01: - Serial.println(F("FAN 1")); - break; - case 0x02: - Serial.println(F("FAN 2")); - break; - case 0x03: - Serial.println(F("FAN 3")); - break; - case 0x04: - Serial.println(F("FAN 4")); - break; - default: - Serial.println(F("FAN unknown")); - break; - } - - // Vertical air direction - switch (bytes[9] & 0xF8) { // 0b11111000 - case 0x40: // 0b01000 - Serial.println(F("VANE: AUTO1?")); - break; - case 0x48: // 0b01001 - Serial.println(F("VANE: UP")); - break; - case 0x50: // 0b01010 - Serial.println(F("VANE: UP-1")); - break; - case 0x58: // 0b01011 - Serial.println(F("VANE: UP-2")); - break; - case 0x60: // 0b01100 - Serial.println(F("VANE: UP-3")); - break; - case 0x68: // 0b01101 - Serial.println(F("VANE: DOWN")); - break; - case 0x78: // 0b01111 - Serial.println(F("VANE: SWING")); - break; - case 0x80: // 0b10000 - Serial.println(F("VANE: AUTO2?")); - break; - case 0xB8: // 0b10111 - Serial.println(F("VANE: AUTO3?")); - break; - default: - Serial.println(F("VANE: unknown")); - break; - } - - // Horizontal air direction - switch (bytes[8] & 0xF0) { // 0b11110000 - case 0x10: - Serial.println(F("WIDE VANE: LEFT")); - break; - case 0x20: - Serial.println(F("WIDE VANE: MIDDLE LEFT")); - break; - case 0x30: - Serial.println(F("WIDE VANE: MIDDLE")); - break; - case 0x40: - Serial.println(F("WIDE VANE: MIDDLE RIGHT")); - break; - case 0x50: - Serial.println(F("WIDE VANE: RIGHT")); - break; - case 0xC0: - Serial.println(F("WIDE VANE: SWING")); - break; - default: - Serial.println(F("WIDE VANE: unknown")); - break; - } - - return true; - } - - return false; -} +#include + +bool decodeMitsubishiElectric(byte *bytes, int byteCount) +{ + // If this looks like a Mitsubishi FD-25 or FE code... + if ( byteCount == 36 && bytes[0] == 0x23 && + ( (memcmp(bytes, bytes+18, 17) == 0) || + ((memcmp(bytes, bytes+18, 14) == 0) && bytes[32] == 0x24) ) ){ + Serial.println(F("Looks like a Mitsubishi FD / FE / MSY series protocol")); + + // Check if the checksum matches + byte checksum = 0; + + for (int i=0; i<17; i++) { + checksum += bytes[i]; + } + + if (checksum == bytes[17]) { + Serial.println(F("Checksum matches")); + } else { + Serial.println(F("Checksum does not match")); + } + + // Power mode + switch (bytes[5]) { + case 0x00: + Serial.println(F("POWER OFF")); + break; + case 0x20: + Serial.println(F("POWER ON")); + break; + default: + Serial.println(F("POWER unknown")); + break; + } + + // Operating mode + Serial.print(F("MODE ")); + switch (bytes[6] & 0x38) { // 0b00111000 + case 0x38: + Serial.println(F("FAN")); + break; + case 0x20: + Serial.println(F("AUTO")); + break; + case 0x08: + if (bytes[15] == 0x20) { + Serial.println(F("MAINTENANCE HEAT (FE only)")); + } else { + Serial.println(F("HEAT")); + } + break; + case 0x18: + Serial.println(F("COOL")); + break; + case 0x10: + Serial.println(F("DRY")); + break; + default: + Serial.println(F("unknown")); + break; + } + + // I-See + Serial.print(F("I-See: ")); + switch (bytes[6] & 0x40) { // 0b01000000 + case 0x40: + Serial.println(F("ON")); + break; + case 0x00: + Serial.println(F("OFF")); + break; + } + + // Clean + Serial.print(F("Clean: ")); + switch (bytes[14] & 0x04) { // 0b00000100 + case 0x04: + Serial.println(F("ON")); + break; + case 0x00: + Serial.println(F("OFF")); + break; + } + + + // Plasma + Serial.print(F("Plasma: ")); + switch (bytes[15] & 0x40) { // 0b01000000 + case 0x40: + Serial.println(F("ON")); + break; + case 0x00: + Serial.println(F("OFF")); + break; + } + + // Temperature + Serial.print(F("Temperature: ")); + if (bytes[7] == 0x00) { + Serial.println(F("10")); + } else { + Serial.println(bytes[7] + 16); + } + + // Fan speed + Serial.print(F("FAN ")); + switch (bytes[9] & 0x07) { // 0b00000111 + case 0x00: + Serial.println(F("AUTO")); + break; + case 0x01: + Serial.println(F("1")); + break; + case 0x02: + Serial.println(F("2")); + break; + case 0x03: + Serial.println(F("3")); + break; + case 0x04: + Serial.println(F("4")); + break; + default: + Serial.println(F("unknown")); + break; + } + + // Vertical air direction + Serial.print(F("VANE: ")); + switch (bytes[9] & 0xF8) { // 0b11111000 + case 0x40: // 0b01000 + Serial.println(F("AUTO1?")); + break; + case 0x48: // 0b01001 + Serial.println(F("UP")); + break; + case 0x50: // 0b01010 + Serial.println(F("UP-1")); + break; + case 0x58: // 0b01011 + Serial.println(F("UP-2")); + break; + case 0x60: // 0b01100 + Serial.println(F("UP-3")); + break; + case 0x68: // 0b01101 + Serial.println(F("DOWN")); + break; + case 0x78: // 0b01111 + Serial.println(F("SWING")); + break; + case 0x80: // 0b10000 + Serial.println(F("AUTO2?")); + break; + case 0xB8: // 0b10111 + Serial.println(F("AUTO3?")); + break; + default: + Serial.println(F("unknown")); + break; + } + + // Horizontal air direction + Serial.print(F("WIDE VANE: ")); + switch (bytes[8] & 0xF0) { // 0b11110000 + case 0x00: + Serial.println(F("AREA")); + break; + case 0x10: + Serial.println(F("LEFT")); + break; + case 0x20: + Serial.println(F("MIDDLE LEFT")); + break; + case 0x30: + Serial.println(F("MIDDLE")); + break; + case 0x40: + Serial.println(F("MIDDLE RIGHT")); + break; + case 0x50: + Serial.println(F("RIGHT")); + break; + case 0xC0: + Serial.println(F("SWING")); + break; + default: + Serial.println(F("unknown")); + break; + } + + // Horizontal air direction, area mode + Serial.print(F("AREA MODE: ")); + switch (bytes[13] & 0xC0) { // 0b11000000 + case 0x00: + Serial.println(F("OFF")); + break; + case 0x40: + Serial.println(F("LEFT")); + break; + case 0x80: + Serial.println(F("AUTO")); + break; + case 0xC0: + Serial.println(F("RIGHT")); + break; + } + + // Installation position + Serial.print(F("INSTALL POSITION: ")); + switch (bytes[14] & 0x18) { // 0b00011000 + case 0x08: + Serial.println(F("LEFT")); + break; + case 0x10: + Serial.println(F("CENTER")); + break; + case 0x18: + Serial.println(F("RIGHT")); + break; + default: + Serial.println(F("unknown")); + break; + } + + + return true; + } + + return false; +} From b811bdbf5113fefbd8d2a295f02a4ce9f772b76f Mon Sep 17 00:00:00 2001 From: rob-deutsch Date: Tue, 27 Oct 2020 18:30:27 +1100 Subject: [PATCH 71/94] Works on ESP32 --- rawirdecode.ino | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/rawirdecode.ino b/rawirdecode.ino index 9045098..51e85bb 100644 --- a/rawirdecode.ino +++ b/rawirdecode.ino @@ -44,6 +44,8 @@ bool decodeZHLT01remote(byte *bytes, int byteCount); #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) #define IRpin_PIN PINE #define IRpin 4 +#elif defined(ESP32) +#define IRpin 5 #else #define IRpin_PIN PIND #define IRpin 2 @@ -87,9 +89,10 @@ uint8_t modelChoice = 0; byte byteCount = 0; byte bytes[128]; - void setup(void) { + pinMode(IRpin, INPUT); + Serial.begin(9600); delay(1000); Serial.println(F("Select model to decode (this affects the IR signal timings detection):")); @@ -205,7 +208,7 @@ void receivePulses(void) { while (currentpulse < sizeof(symbols)) { highpulse = 0; - while (IRpin_PIN & (1 << IRpin)) { + while (gpio_get_level(gpio_num_t(IRpin))) { // pin is still HIGH // count off another few microseconds @@ -245,7 +248,7 @@ void receivePulses(void) { // same as above lowpulse = 0; - while (! (IRpin_PIN & _BV(IRpin))) { + while (! gpio_get_level(gpio_num_t(IRpin))) { // pin is still LOW lowpulse++; delayMicroseconds(RESOLUTION); From 747e7e0fe2e631a8c1d3ac1ac6cc16d2799f133f Mon Sep 17 00:00:00 2001 From: Toni Date: Fri, 30 Oct 2020 10:36:00 +0200 Subject: [PATCH 72/94] PlatformIO project structure --- .gitignore | 2 ++ platformio.ini | 15 ++++++++++++ AUXAC.cpp => rawirdecode/AUXAC.cpp | 0 Airwell.cpp => rawirdecode/Airwell.cpp | 0 Ballu.cpp => rawirdecode/Ballu.cpp | 0 Carrier.cpp => rawirdecode/Carrier.cpp | 0 Daikin.cpp => rawirdecode/Daikin.cpp | 0 Fuego.cpp => rawirdecode/Fuego.cpp | 0 Fujitsu.cpp => rawirdecode/Fujitsu.cpp | 0 Gree.cpp => rawirdecode/Gree.cpp | 0 Gree_YAC.cpp => rawirdecode/Gree_YAC.cpp | 0 Hitachi.cpp => rawirdecode/Hitachi.cpp | 0 Hyundai.cpp => rawirdecode/Hyundai.cpp | 0 .../MitsubishiElectric.cpp | 0 .../MitsubishiHeavy.cpp | 0 Nibe.cpp => rawirdecode/Nibe.cpp | 0 .../PanasonicCPK.cpp | 0 .../PanasonicCS.cpp | 0 Samsung.cpp => rawirdecode/Samsung.cpp | 0 Sharp.cpp => rawirdecode/Sharp.cpp | 0 Toshiba.cpp => rawirdecode/Toshiba.cpp | 0 .../ZHLT01Remote.cpp | 0 .../rawirdecode.ino | 24 ++++++++++++------- 23 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 .gitignore create mode 100644 platformio.ini rename AUXAC.cpp => rawirdecode/AUXAC.cpp (100%) rename Airwell.cpp => rawirdecode/Airwell.cpp (100%) rename Ballu.cpp => rawirdecode/Ballu.cpp (100%) rename Carrier.cpp => rawirdecode/Carrier.cpp (100%) rename Daikin.cpp => rawirdecode/Daikin.cpp (100%) rename Fuego.cpp => rawirdecode/Fuego.cpp (100%) rename Fujitsu.cpp => rawirdecode/Fujitsu.cpp (100%) rename Gree.cpp => rawirdecode/Gree.cpp (100%) rename Gree_YAC.cpp => rawirdecode/Gree_YAC.cpp (100%) rename Hitachi.cpp => rawirdecode/Hitachi.cpp (100%) rename Hyundai.cpp => rawirdecode/Hyundai.cpp (100%) rename MitsubishiElectric.cpp => rawirdecode/MitsubishiElectric.cpp (100%) rename MitsubishiHeavy.cpp => rawirdecode/MitsubishiHeavy.cpp (100%) rename Nibe.cpp => rawirdecode/Nibe.cpp (100%) rename PanasonicCPK.cpp => rawirdecode/PanasonicCPK.cpp (100%) rename PanasonicCS.cpp => rawirdecode/PanasonicCS.cpp (100%) rename Samsung.cpp => rawirdecode/Samsung.cpp (100%) rename Sharp.cpp => rawirdecode/Sharp.cpp (100%) rename Toshiba.cpp => rawirdecode/Toshiba.cpp (100%) rename ZHLT01Remote.cpp => rawirdecode/ZHLT01Remote.cpp (100%) rename rawirdecode.ino => rawirdecode/rawirdecode.ino (97%) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b9f3806 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.pio +.vscode diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..727a775 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,15 @@ +[platformio] +src_dir = rawirdecode + +; Tested on M5STACK ATOM LITE +[env:esp32] +platform = espressif32 +framework = arduino +board = esp32dev +upload_speed = 115200 + +; Tested on Arduino Mega +[env:atmega2560] +platform = atmelavr +framework = arduino +board = megaatmega2560 \ No newline at end of file diff --git a/AUXAC.cpp b/rawirdecode/AUXAC.cpp similarity index 100% rename from AUXAC.cpp rename to rawirdecode/AUXAC.cpp diff --git a/Airwell.cpp b/rawirdecode/Airwell.cpp similarity index 100% rename from Airwell.cpp rename to rawirdecode/Airwell.cpp diff --git a/Ballu.cpp b/rawirdecode/Ballu.cpp similarity index 100% rename from Ballu.cpp rename to rawirdecode/Ballu.cpp diff --git a/Carrier.cpp b/rawirdecode/Carrier.cpp similarity index 100% rename from Carrier.cpp rename to rawirdecode/Carrier.cpp diff --git a/Daikin.cpp b/rawirdecode/Daikin.cpp similarity index 100% rename from Daikin.cpp rename to rawirdecode/Daikin.cpp diff --git a/Fuego.cpp b/rawirdecode/Fuego.cpp similarity index 100% rename from Fuego.cpp rename to rawirdecode/Fuego.cpp diff --git a/Fujitsu.cpp b/rawirdecode/Fujitsu.cpp similarity index 100% rename from Fujitsu.cpp rename to rawirdecode/Fujitsu.cpp diff --git a/Gree.cpp b/rawirdecode/Gree.cpp similarity index 100% rename from Gree.cpp rename to rawirdecode/Gree.cpp diff --git a/Gree_YAC.cpp b/rawirdecode/Gree_YAC.cpp similarity index 100% rename from Gree_YAC.cpp rename to rawirdecode/Gree_YAC.cpp diff --git a/Hitachi.cpp b/rawirdecode/Hitachi.cpp similarity index 100% rename from Hitachi.cpp rename to rawirdecode/Hitachi.cpp diff --git a/Hyundai.cpp b/rawirdecode/Hyundai.cpp similarity index 100% rename from Hyundai.cpp rename to rawirdecode/Hyundai.cpp diff --git a/MitsubishiElectric.cpp b/rawirdecode/MitsubishiElectric.cpp similarity index 100% rename from MitsubishiElectric.cpp rename to rawirdecode/MitsubishiElectric.cpp diff --git a/MitsubishiHeavy.cpp b/rawirdecode/MitsubishiHeavy.cpp similarity index 100% rename from MitsubishiHeavy.cpp rename to rawirdecode/MitsubishiHeavy.cpp diff --git a/Nibe.cpp b/rawirdecode/Nibe.cpp similarity index 100% rename from Nibe.cpp rename to rawirdecode/Nibe.cpp diff --git a/PanasonicCPK.cpp b/rawirdecode/PanasonicCPK.cpp similarity index 100% rename from PanasonicCPK.cpp rename to rawirdecode/PanasonicCPK.cpp diff --git a/PanasonicCS.cpp b/rawirdecode/PanasonicCS.cpp similarity index 100% rename from PanasonicCS.cpp rename to rawirdecode/PanasonicCS.cpp diff --git a/Samsung.cpp b/rawirdecode/Samsung.cpp similarity index 100% rename from Samsung.cpp rename to rawirdecode/Samsung.cpp diff --git a/Sharp.cpp b/rawirdecode/Sharp.cpp similarity index 100% rename from Sharp.cpp rename to rawirdecode/Sharp.cpp diff --git a/Toshiba.cpp b/rawirdecode/Toshiba.cpp similarity index 100% rename from Toshiba.cpp rename to rawirdecode/Toshiba.cpp diff --git a/ZHLT01Remote.cpp b/rawirdecode/ZHLT01Remote.cpp similarity index 100% rename from ZHLT01Remote.cpp rename to rawirdecode/ZHLT01Remote.cpp diff --git a/rawirdecode.ino b/rawirdecode/rawirdecode.ino similarity index 97% rename from rawirdecode.ino rename to rawirdecode/rawirdecode.ino index 51e85bb..35c30e4 100644 --- a/rawirdecode.ino +++ b/rawirdecode/rawirdecode.ino @@ -45,7 +45,7 @@ bool decodeZHLT01remote(byte *bytes, int byteCount); #define IRpin_PIN PINE #define IRpin 4 #elif defined(ESP32) -#define IRpin 5 +#define IRpin 25 // G25 on M5STACK ATOM LITE #else #define IRpin_PIN PIND #define IRpin 2 @@ -130,7 +130,7 @@ void setup(void) { case '9': modelChoice = 9; break; - } + } } } @@ -208,7 +208,7 @@ void receivePulses(void) { while (currentpulse < sizeof(symbols)) { highpulse = 0; - while (gpio_get_level(gpio_num_t(IRpin))) { + while (getPinState()) { // pin is still HIGH // count off another few microseconds @@ -248,7 +248,7 @@ void receivePulses(void) { // same as above lowpulse = 0; - while (! gpio_get_level(gpio_num_t(IRpin))) { + while (! getPinState()) { // pin is still LOW lowpulse++; delayMicroseconds(RESOLUTION); @@ -273,6 +273,14 @@ void receivePulses(void) { } } +bool getPinState() { +#if defined(ESP32) + return gpio_get_level(gpio_num_t(IRpin)); +#else + return (IRpin_PIN & _BV(IRpin)); +#endif +} + void printPulses(void) { int bitCount = 0; @@ -292,22 +300,22 @@ void printPulses(void) { // Decode the string of bits to a byte array for (uint16_t i = 0; i < currentpulse; i++) { - + if (symbols[i] == '0' || symbols[i] == '1') { - + if (bitCount == 0) { if (byteCount < 10) Serial.print("0"); Serial.print(byteCount); Serial.print(": "); } - + currentByte >>= 1; bitCount++; if (symbols[i] == '1') { currentByte |= 0x80; } - + Serial.print(symbols[i]); if (bitCount == 4) { Serial.print("|"); From 1e07704b9b008eb6355195e010e4848b1df21632 Mon Sep 17 00:00:00 2001 From: Toni Date: Fri, 30 Oct 2020 11:17:33 +0200 Subject: [PATCH 73/94] Update readme --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 093a07d..4e27d28 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,16 @@ Required hardware: - Breadboard, wiring - IR remote control from the aircon/heatpump you plan to decode +## Build instructions + +### Arduino IDE +* Open the sketch from subdirectory 'rawirdecode' in Arduino IDE and build + +### PlatformIO +* platformio.ini contains build definitions for Arduino Mega and ESP32 (tested on M5STACK ATOM LITE) +* On Mega, connect the receiver data pin to GPIO 2 +* On ESP32, connect the receiver data pin to GPIO 25 + ## Instructions * Connect an IR receiver into the Arduino @@ -21,7 +31,7 @@ Required hardware: * Point your IR remote to the IR receiver and send the code * If the symbols are known, then the decoder shows its meaning on the serial monitor * If the symbols are unknown, then you can help by writing a decoder for the unknown remote - + -> Mode '9' can be used to decode known signals, in that case you can send the symbols from the terminal, like entering this: Hh001101011010111100000111001001010100000000000111000000001111111011010100000001000111001011 From 01ec07438b4abe256667214cc924dcf4c431ca68 Mon Sep 17 00:00:00 2001 From: Clifford Roche Date: Wed, 11 Nov 2020 23:05:06 -0500 Subject: [PATCH 74/94] Adding I-Feel decoding for Gree --- rawirdecode/Gree_YAC.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/rawirdecode/Gree_YAC.cpp b/rawirdecode/Gree_YAC.cpp index 50efdf8..1667cfe 100644 --- a/rawirdecode/Gree_YAC.cpp +++ b/rawirdecode/Gree_YAC.cpp @@ -3,7 +3,14 @@ bool decodeGree_YAC(byte *bytes, int pulseCount) { // If this looks like a Gree code... - if ( pulseCount == 142 ) { + if ( pulseCount == 19 ) { + Serial.println(F("Looks like a Gree YAC protocol for I-Feel")); + Serial.print(F("I-Feel Temperature: ")); + Serial.println(bytes[0]); + return true; + } + + if ( pulseCount == 142 || pulseCount == 161 ) { Serial.println(F("Looks like a Gree YAC protocol")); // Check if the checksum matches @@ -241,6 +248,11 @@ bool decodeGree_YAC(byte *bytes, int pulseCount) break; } + + if (pulseCount == 161) { + Serial.print(F("I-Feel Temperature: ")); + Serial.println(bytes[16]); + } return true; } From 0bb5bd5dd1c3c210cf7a7407b8ff1dbccb2bb569 Mon Sep 17 00:00:00 2001 From: ericvb Date: Wed, 30 Dec 2020 10:28:52 +0100 Subject: [PATCH 75/94] Adding comment about the brand defines --- README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/README.md b/README.md index 4e27d28..8678dbf 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,29 @@ Required hardware: - IR remote control from the aircon/heatpump you plan to decode ## Build instructions +Defines for the different brands were introduced to limit the memory footage on a Arduino UNO. +Uncomment, in the beginning of the sketch, the define for your remote brand. + +* //#define MITSUBISHI_ELECTRIC +* //#define FUJITSU +* //#define MITSUBISHI_HEAVY +* //#define DAIKIN +* //#define SHARP_ +* //#define CARRIER +* //#define PANASONIC_CKP +* //#define PANASONIC_CS +* //#define HYUNDAI +* //#define GREE +* //#define GREE_YAC +* //#define FUEGO +* //#define TOSHIBA +* //#define NIBE +* //#define AIRWELL +* //#define HITACHI +* //#define SAMSUNG +* //#define BALLU +* //#define AUX +* //#define ZHLT01_REMOTE ### Arduino IDE * Open the sketch from subdirectory 'rawirdecode' in Arduino IDE and build From a50fcfc06656d4df47c411467fa89bfcce3a8d55 Mon Sep 17 00:00:00 2001 From: ericvb Date: Wed, 30 Dec 2020 10:57:38 +0100 Subject: [PATCH 76/94] Adding comment on protection when no brand define is uncommented --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8678dbf..2d0ca52 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Required hardware: ## Build instructions Defines for the different brands were introduced to limit the memory footage on a Arduino UNO. Uncomment, in the beginning of the sketch, the define for your remote brand. +You will get a compiler error if you forget to uncomment! * //#define MITSUBISHI_ELECTRIC * //#define FUJITSU From 96d987aa9f73031607950fcc8ba8e1647685c302 Mon Sep 17 00:00:00 2001 From: ericvb Date: Wed, 30 Dec 2020 10:59:31 +0100 Subject: [PATCH 77/94] Adding defines to lower the memory footage for an arduino UNO + adding protection if no brand define uncommented --- rawirdecode/rawirdecode.ino | 193 ++++++++++++++++++++++++++++-------- 1 file changed, 151 insertions(+), 42 deletions(-) diff --git a/rawirdecode/rawirdecode.ino b/rawirdecode/rawirdecode.ino index 35c30e4..e6f9c04 100644 --- a/rawirdecode/rawirdecode.ino +++ b/rawirdecode/rawirdecode.ino @@ -1,25 +1,90 @@ #include -bool decodeMitsubishiElectric(byte *bytes, int byteCount); -bool decodeFujitsu(byte *bytes, int byteCount); -bool decodeMitsubishiHeavy(byte *bytes, int byteCount); -bool decodeDaikin(byte *bytes, int byteCount); -bool decodeSharp(byte *bytes, int byteCount); -bool decodeCarrier(byte *bytes, int byteCount); -bool decodePanasonicCKP(byte *bytes, int byteCount); -bool decodePanasonicCS(byte *bytes, int byteCount); -bool decodeHyundai(byte *bytes, int pulseCount); -bool decodeGree(byte *bytes, int pulseCount); -bool decodeGree_YAC(byte *bytes, int pulseCount); -bool decodeFuego(byte *bytes, int byteCount); -bool decodeToshiba(byte *bytes, int byteCount); -bool decodeNibe(byte *bytes, char* symbols, int bitCount); -bool decodeAirwell(char* symbols, int bitCount); -bool decodeHitachi(byte *bytes, int byteCount); -bool decodeSamsung(byte *bytes, int byteCount); -bool decodeBallu(byte *bytes, int byteCount); -bool decodeAUX(byte *bytes, int byteCount); -bool decodeZHLT01remote(byte *bytes, int byteCount); +// uncomment the define corresponding with your remote +//#define MITSUBISHI_ELECTRIC +//#define FUJITSU +//#define MITSUBISHI_HEAVY +//#define DAIKIN +//#define SHARP_ +//#define CARRIER +//#define PANASONIC_CKP +//#define PANASONIC_CS +//#define HYUNDAI +//#define GREE +//#define GREE_YAC +//#define FUEGO +//#define TOSHIBA +//#define NIBE +//#define AIRWELL +//#define HITACHI +//#define SAMSUNG +//#define BALLU +//#define AUX +//#define ZHLT01_REMOTE + +#if !defined(MITSUBISHI_ELECTRIC)&&!defined(FUJITSU)&&!defined(MITSUBISHI_HEAVY)&&!defined(DAIKIN)&&!defined(SHARP_)&&!defined(CARRIER)&&!defined(PANASONIC_CKP)&&!defined(PANASONIC_CS)&&!defined(HYUNDAI)&&!defined(GREE)&&!defined(GREE_YAC)&&!defined(FUEGO)&&!defined(TOSHIBA)&&!defined(NIBE)&&!defined(AIRWELL)&&!defined(HITACHI)&&!defined(SAMSUNG)&&!defined(BALLU)&&!defined(AUX)&&!defined(ZHLT01_REMOTE) +#error You must uncomment at least one brand define!! +#endif + + +#if defined(MITSUBISHI_ELECTRIC) + bool decodeMitsubishiElectric(byte *bytes, int byteCount); +#endif +#if defined(FUJITSU) + bool decodeFujitsu(byte *bytes, int byteCount); +#endif +#if defined(MITSUBISHI_HEAVY) + bool decodeMitsubishiHeavy(byte *bytes, int byteCount); +#endif +#if defined(DAIKIN) + bool decodeDaikin(byte *bytes, int byteCount); +#endif +#if defined(SHARP_) + bool decodeSharp(byte *bytes, int byteCount); +#endif +#if defined(CARRIER) + bool decodeCarrier(byte *bytes, int byteCount); +#endif +#if defined(PANASONIC_CKP) + bool decodePanasonicCKP(byte *bytes, int byteCount); +#endif +#if defined(PANASONIC_CS) + bool decodePanasonicCS(byte *bytes, int byteCount); +#endif +#if defined(HYUNDAI) + bool decodeHyundai(byte *bytes, int pulseCount); +#endif +#if defined(GREE) or defined(GREE_YAC) + bool decodeGree(byte *bytes, int pulseCount); + bool decodeGree_YAC(byte *bytes, int pulseCount); +#endif +#if defined(FUEGO) + bool decodeFuego(byte *bytes, int byteCount); +#endif +#if defined(TOSHIBA) + bool decodeToshiba(byte *bytes, int byteCount); +#endif +#if defined(NIBE) + bool decodeNibe(byte *bytes, char* symbols, int bitCount); +#endif +#if defined(AIRWELL) + bool decodeAirwell(char* symbols, int bitCount); +#endif +#if defined(HITACHI) + bool decodeHitachi(byte *bytes, int byteCount); +#endif +#if defined(SAMSUNG) + bool decodeSamsung(byte *bytes, int byteCount); +#endif +#if defined(BALLU) + bool decodeBallu(byte *bytes, int byteCount); +#endif +#if defined(AUX) + bool decodeAUX(byte *bytes, int byteCount); +#endif +#if defined(ZHLT01_REMOTE) + bool decodeZHLT01remote(byte *bytes, int byteCount); +#endif /* Raw IR decoder sketch! @@ -173,6 +238,8 @@ void loop(void) { currentpulse=0; byteCount=0; + + Serial.println("################# Start"); if (modelChoice != 9) { receivePulses(); } else { @@ -182,6 +249,8 @@ void loop(void) { printPulses(); decodeProtocols(); + Serial.println("################# End "); + Serial.println(); } void receivePulses(void) { @@ -381,31 +450,71 @@ void decodeProtocols() { Serial.println(F("Decoding known protocols...")); - if ( ! (decodeMitsubishiElectric(bytes, byteCount) || - decodeFujitsu(bytes, byteCount) || - decodeMitsubishiHeavy(bytes, byteCount) || - decodeSharp(bytes, byteCount) || - decodeDaikin(bytes, byteCount) || - decodeCarrier(bytes, byteCount) || - decodeCarrier(bytes, byteCount) || - decodePanasonicCKP(bytes, byteCount) || - decodePanasonicCS(bytes, byteCount) || - decodeHyundai(bytes, currentpulse) || - decodeGree(bytes, currentpulse) || - decodeGree_YAC(bytes, currentpulse) || - decodeFuego(bytes, byteCount) || - decodeToshiba(bytes, byteCount) || - decodeNibe(bytes, symbols, currentpulse) || - decodeAirwell(symbols, currentpulse) || - decodeHitachi(bytes, byteCount) || - decodeSamsung(bytes, byteCount) || - decodeBallu(bytes, byteCount) || - decodeAUX(bytes, byteCount) || - decodeZHLT01remote(bytes, byteCount) - )) + bool knownProtocol; + +#if defined(MITSUBISHI_ELECTRIC) + knownProtocol = decodeMitsubishiElectric(bytes, byteCount); +#endif +#if defined(FUJITSU) + knownProtocol = decodeFujitsu(bytes, byteCount); +#endif +#if defined(MITSUBISHI_HEAVY) + knownProtocol = decodeMitsubishiHeavy(bytes, byteCount); +#endif +#if defined(SHARP_) + knownProtocol = decodeSharp(bytes, byteCount); +#endif +#if defined(DAIKIN) + knownProtocol = decodeDaikin(bytes, byteCount); +#endif +#if defined(CARRIER) + knownProtocol = decodeCarrier(bytes, byteCount); +#endif +#if defined(PANASONIC_CKP) + knownProtocol = decodePanasonicCKP(bytes, byteCount); +#endif +#if defined(PANASONIC_CS) + knownProtocol = decodePanasonicCS(bytes, byteCount); +#endif +#if defined(HYUNDAI) + knownProtocol = decodeHyundai(bytes, currentpulse); +#endif +#if defined(GREE) or defined(GREE_YAC) + knownProtocol = decodeGree(bytes, currentpulse) || decodeGree_YAC(bytes, currentpulse); +#endif +#if defined(FUEGO) + knownProtocol = decodeFuego(bytes, byteCount); +#endif +#if defined(TOSHIBA) + knownProtocol = decodeToshiba(bytes, byteCount); +#endif +#if defined(NIBE) + knownProtocol = decodeNibe(bytes, symbols, currentpulse); +#endif +#if defined(AIRWELL) + knownProtocol = decodeAirwell(symbols, currentpulse); +#endif +#if defined(HITACHI) + knownProtocol = decodeHitachi(bytes, byteCount); +#endif +#if defined(SAMSUNG) + knownProtocol = decodeSamsung(bytes, byteCount); +#endif +#if defined(BALLU) + knownProtocol = decodeBallu(bytes, byteCount); +#endif +#if defined(AUX) + knownProtocol = decodeAUX(bytes, byteCount); +#endif +#if defined(ZHLT01_REMOTE) + knownProtocol = decodeZHLT01remote(bytes, byteCount); +#endif + + if (!knownProtocol) { Serial.println(F("Unknown protocol")); Serial.print("Bytecount: "); Serial.println(byteCount); } + } From 117108899ad33d190b06fad353492751e74814e7 Mon Sep 17 00:00:00 2001 From: ericvb Date: Wed, 30 Dec 2020 16:46:04 +0100 Subject: [PATCH 78/94] Adding support for the remote ARH-1362 --- rawirdecode/Samsung.cpp | 98 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 6 deletions(-) diff --git a/rawirdecode/Samsung.cpp b/rawirdecode/Samsung.cpp index 93940ec..3ee511c 100644 --- a/rawirdecode/Samsung.cpp +++ b/rawirdecode/Samsung.cpp @@ -1,14 +1,15 @@ #include -// Samsung with remote ARH-465 +// Samsung with remote ARH-465 or remote ARH-1362 bool decodeSamsung(byte *bytes, int byteCount) { // If this looks like a Samsung code... if (bytes[0] == 0x02 && ((byteCount == 21 && bytes[1] == 0xB2) || (byteCount == 14 && bytes[1] == 0x92)) - && bytes[2] == 0x0F) { - Serial.println(F("Looks like a Samsung protocol")); + && bytes[2] == 0x0F) + { + Serial.println(F("Looks like a short 14 bytes Samsung protocol")); // Power mode if (byteCount == 21) @@ -47,13 +48,13 @@ bool decodeSamsung(byte *bytes, int byteCount) Serial.println(F("FAN: AUTO")); break; case 0x05: - Serial.println(F("FAN: 1")); + Serial.println(F("FAN: 1 (low)")); break; case 0x09: - Serial.println(F("FAN: 2")); + Serial.println(F("FAN: 2 (medium)")); break; case 0x0B: - Serial.println(F("FAN: 3")); + Serial.println(F("FAN: 3 (high)")); break; case 0x0F: Serial.println(F("FAN: 4")); @@ -115,5 +116,90 @@ bool decodeSamsung(byte *bytes, int byteCount) return true; } + + if (bytes[0] == 0x02 + && ((byteCount == 21 && bytes[1] == 0xB2) || (byteCount == 21 && bytes[1] == 0x92)) + && bytes[2] == 0x0F) + { + Serial.println(F("Looks like a 21 bytes long Samsung protocol specific ARH-1362 remote")); + + // Power mode + if (byteCount == 21 && bytes[1] == 0xB2) + { + Serial.println(F("POWER OFF")); + return true; + } + Serial.println(F("POWER ON")); + + // Operating mode | fan speed auto + switch (bytes[19] & 0xF0) { + case 0x00: + Serial.println(F("MODE AUTO")); + break; + case 0x10: + Serial.println(F("MODE COOL")); + break; + case 0x20: + Serial.println(F("MODE DRY")); + break; + case 0x40: + Serial.println(F("MODE HEAT")); + break; + } + + // Temperature + Serial.print(F("Temperature: ")); + Serial.println((bytes[18] >> 4) + 16); + + // Fan speed + switch (bytes[19] & 0x0F) { + case 0x0D: + case 0x01: + Serial.println(F("FAN: AUTO")); + break; + case 0x05: + Serial.println(F("FAN: 1 (low)")); + break; + case 0x09: + Serial.println(F("FAN: 2 (medium)")); + break; + case 0x0B: + Serial.println(F("FAN: 3 (high)")); + break; + } + + // calculate the checksum + uint8_t checksum = 0; + byte originalChecksum = bytes[15]; + + // Calculate the byte 15 checksum + // Count the number of ONE bits on message uint8_ts 15-20 + for (uint8_t j=16; j<20; j++) { + uint8_t Samsungbyte = bytes[j]; + for (uint8_t i=0; i<8; i++) { + if ( (Samsungbyte & 0x01) == 0x01 ) { + checksum++; + } + Samsungbyte >>= 1; + } + } + + // Transform the number of ONE bits to the actual checksum + checksum = 28 - checksum; + checksum <<= 4; + checksum += 0x02; + + Serial.print(F("Checksum '0x")); + Serial.print(checksum, HEX); + + if ( originalChecksum == checksum ) { + Serial.println(F("' matches")); + } else { + Serial.print(F("' does not match 0x")); + Serial.println(originalChecksum, HEX); + } + return true; + } + return false; } From eabb0f08df3ff6627e7432fac3f824514b670182 Mon Sep 17 00:00:00 2001 From: ericvb Date: Wed, 30 Dec 2020 16:53:57 +0100 Subject: [PATCH 79/94] Avoid false positives in receiving --- rawirdecode/rawirdecode.ino | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/rawirdecode/rawirdecode.ino b/rawirdecode/rawirdecode.ino index e6f9c04..474b0fc 100644 --- a/rawirdecode/rawirdecode.ino +++ b/rawirdecode/rawirdecode.ino @@ -239,7 +239,6 @@ void loop(void) { currentpulse=0; byteCount=0; - Serial.println("################# Start"); if (modelChoice != 9) { receivePulses(); } else { @@ -247,10 +246,15 @@ void loop(void) { currentpulse++; } - printPulses(); - decodeProtocols(); - Serial.println("################# End "); - Serial.println(); + // avoid false receiving positives + if (currentpulse > 1) + { + Serial.println("################# Start"); + printPulses(); + decodeProtocols(); + Serial.println("################# End "); + Serial.println(); + } } void receivePulses(void) { From 63794b35da2c43b0de79f126b660515b47502e78 Mon Sep 17 00:00:00 2001 From: ericvb Date: Sun, 3 Jan 2021 21:41:54 +0100 Subject: [PATCH 80/94] adding checksum hack in case of a power off --- rawirdecode/Samsung.cpp | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/rawirdecode/Samsung.cpp b/rawirdecode/Samsung.cpp index 3ee511c..ceee795 100644 --- a/rawirdecode/Samsung.cpp +++ b/rawirdecode/Samsung.cpp @@ -3,20 +3,14 @@ // Samsung with remote ARH-465 or remote ARH-1362 bool decodeSamsung(byte *bytes, int byteCount) -{ +{ // If this looks like a Samsung code... if (bytes[0] == 0x02 - && ((byteCount == 21 && bytes[1] == 0xB2) || (byteCount == 14 && bytes[1] == 0x92)) + && (byteCount == 14 && bytes[1] == 0x92) && bytes[2] == 0x0F) { Serial.println(F("Looks like a short 14 bytes Samsung protocol")); - - // Power mode - if (byteCount == 21) - { - Serial.println(F("POWER OFF")); - return true; - } + Serial.println(F("POWER ON")); // Operating mode @@ -121,15 +115,15 @@ bool decodeSamsung(byte *bytes, int byteCount) && ((byteCount == 21 && bytes[1] == 0xB2) || (byteCount == 21 && bytes[1] == 0x92)) && bytes[2] == 0x0F) { - Serial.println(F("Looks like a 21 bytes long Samsung protocol specific ARH-1362 remote")); + Serial.println(F("Looks like a 21 bytes long Samsung protocol")); // Power mode if (byteCount == 21 && bytes[1] == 0xB2) { Serial.println(F("POWER OFF")); - return true; } - Serial.println(F("POWER ON")); + else + Serial.println(F("POWER ON")); // Operating mode | fan speed auto switch (bytes[19] & 0xF0) { @@ -187,7 +181,7 @@ bool decodeSamsung(byte *bytes, int byteCount) // Transform the number of ONE bits to the actual checksum checksum = 28 - checksum; checksum <<= 4; - checksum += 0x02; + checksum |= (byteCount == 21 && bytes[1] == 0xB2) ? 0x22 : 0x02; Serial.print(F("Checksum '0x")); Serial.print(checksum, HEX); From 41aa3e0cf8334809abca9756eec3ae3eaa5cfb80 Mon Sep 17 00:00:00 2001 From: ericvb Date: Sun, 3 Jan 2021 22:05:36 +0100 Subject: [PATCH 81/94] Adding hack in case of power off and temp = 20 degrees celcius --- rawirdecode/Samsung.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rawirdecode/Samsung.cpp b/rawirdecode/Samsung.cpp index ceee795..f7f3b34 100644 --- a/rawirdecode/Samsung.cpp +++ b/rawirdecode/Samsung.cpp @@ -181,7 +181,11 @@ bool decodeSamsung(byte *bytes, int byteCount) // Transform the number of ONE bits to the actual checksum checksum = 28 - checksum; checksum <<= 4; - checksum |= (byteCount == 21 && bytes[1] == 0xB2) ? 0x22 : 0x02; + checksum |= (byteCount == 21 && bytes[1] == 0xB2) ? 0x22 : 0x02; + + // incredible hack if power off and temp = 20 + if (byteCount == 21 && bytes[1] == 0xB2 && bytes[18] == 0x40) + checksum = 0x02; Serial.print(F("Checksum '0x")); Serial.print(checksum, HEX); From 61492085481c8b6e7b8fd6c48b87a0614d814a2d Mon Sep 17 00:00:00 2001 From: ericvb Date: Sun, 3 Jan 2021 22:17:52 +0100 Subject: [PATCH 82/94] hack power off and temp = 20 only for heat, cool or dry modes --- rawirdecode/Samsung.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rawirdecode/Samsung.cpp b/rawirdecode/Samsung.cpp index f7f3b34..890d737 100644 --- a/rawirdecode/Samsung.cpp +++ b/rawirdecode/Samsung.cpp @@ -183,8 +183,8 @@ bool decodeSamsung(byte *bytes, int byteCount) checksum <<= 4; checksum |= (byteCount == 21 && bytes[1] == 0xB2) ? 0x22 : 0x02; - // incredible hack if power off and temp = 20 - if (byteCount == 21 && bytes[1] == 0xB2 && bytes[18] == 0x40) + // incredible hack if power off and temp = 20 and mode heat or cool or dry + if (byteCount == 21 && bytes[1] == 0xB2 && bytes[18] == 0x40 && ((bytes[19] & 0xF0) == 0x40 || (bytes[19] & 0xF0) == 0x20 || (bytes[19] & 0xF0) == 0x10)) checksum = 0x02; Serial.print(F("Checksum '0x")); From 281184c60eeba96279b62c2f5fdeb4353e85722b Mon Sep 17 00:00:00 2001 From: Clifford Roche Date: Mon, 8 Feb 2021 20:05:22 -0500 Subject: [PATCH 83/94] Tighten up timings --- rawirdecode/rawirdecode.ino | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/rawirdecode/rawirdecode.ino b/rawirdecode/rawirdecode.ino index 35c30e4..ef0a8dc 100644 --- a/rawirdecode/rawirdecode.ino +++ b/rawirdecode/rawirdecode.ino @@ -57,7 +57,7 @@ bool decodeZHLT01remote(byte *bytes, int byteCount); // what our timing resolution should be, larger is better // as its more 'precise' - but too large and you wont get // accurate timing -uint16_t RESOLUTION=20; +uint16_t RESOLUTION=5; // The thresholds for different symbols uint16_t MARK_THRESHOLD_BIT_HEADER = 0; // Value between BIT MARK and HEADER MARK @@ -205,9 +205,13 @@ void receivePulses(void) { space_pause_avg = 0; space_pause_cnt = 0; + unsigned long start = micros(); + unsigned long last = start; + while (currentpulse < sizeof(symbols)) { highpulse = 0; + while (getPinState()) { // pin is still HIGH @@ -223,7 +227,10 @@ void receivePulses(void) { } } - highpulse = highpulse * RESOLUTION; + // Instructions below here also take time, and need to be counted before the next high pulse. + unsigned long ts = micros(); + highpulse = ts - last; + last = ts; if (currentpulse > 0) { @@ -259,7 +266,9 @@ void receivePulses(void) { // this is a MARK - lowpulse = lowpulse * RESOLUTION; + ts = micros(); + lowpulse = ts - last; + last = ts; if ( lowpulse > MARK_THRESHOLD_BIT_HEADER ) { symbols[currentpulse] = 'H'; From b8f7b62ea99867550f056490925a4f3fcfa9bad0 Mon Sep 17 00:00:00 2001 From: Clifford Roche Date: Mon, 8 Feb 2021 20:16:56 -0500 Subject: [PATCH 84/94] More consistent header space timing measure --- rawirdecode/rawirdecode.ino | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rawirdecode/rawirdecode.ino b/rawirdecode/rawirdecode.ino index ef0a8dc..42228eb 100644 --- a/rawirdecode/rawirdecode.ino +++ b/rawirdecode/rawirdecode.ino @@ -242,7 +242,8 @@ void receivePulses(void) { } else if ( (currentpulse > 0 && symbols[currentpulse-1] == 'H') || highpulse > SPACE_THRESHOLD_ONE_HEADER ) { symbols[currentpulse] = 'h'; // Cumulative moving average, see http://en.wikipedia.org/wiki/Moving_average#Cumulative_moving_average - space_header_avg = (highpulse + space_header_cnt * space_header_avg) / ++space_header_cnt; + if (highpulse > SPACE_THRESHOLD_ONE_HEADER) + space_header_avg = (highpulse + space_header_cnt * space_header_avg) / ++space_header_cnt; } else if ( highpulse > SPACE_THRESHOLD_ZERO_ONE ) { symbols[currentpulse] = '1'; space_one_avg = (highpulse + space_one_cnt * space_one_avg) / ++space_one_cnt; From 88eb30450480ced3c1498ee7a64c933c36183d06 Mon Sep 17 00:00:00 2001 From: Clifford Roche Date: Tue, 9 Feb 2021 17:19:16 -0500 Subject: [PATCH 85/94] State of Gree I-Feel --- rawirdecode/Gree_YAC.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/rawirdecode/Gree_YAC.cpp b/rawirdecode/Gree_YAC.cpp index 1667cfe..aa2970e 100644 --- a/rawirdecode/Gree_YAC.cpp +++ b/rawirdecode/Gree_YAC.cpp @@ -249,6 +249,15 @@ bool decodeGree_YAC(byte *bytes, int pulseCount) } + switch (bytes[5] & 0x4) { + case 0x00: + Serial.println(F("I-FEEL: OFF")); + break; + case 0x04: + Serial.println(F("I-FEEL: ON")); + break; + } + if (pulseCount == 161) { Serial.print(F("I-Feel Temperature: ")); Serial.println(bytes[16]); @@ -258,4 +267,3 @@ bool decodeGree_YAC(byte *bytes, int pulseCount) return false; } - From fff2ab601ac103fbc4469d82325b159d48cb49df Mon Sep 17 00:00:00 2001 From: Clifford Roche Date: Tue, 9 Feb 2021 17:21:53 -0500 Subject: [PATCH 86/94] Consistent strings --- rawirdecode/Gree_YAC.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rawirdecode/Gree_YAC.cpp b/rawirdecode/Gree_YAC.cpp index aa2970e..fc72313 100644 --- a/rawirdecode/Gree_YAC.cpp +++ b/rawirdecode/Gree_YAC.cpp @@ -259,7 +259,7 @@ bool decodeGree_YAC(byte *bytes, int pulseCount) } if (pulseCount == 161) { - Serial.print(F("I-Feel Temperature: ")); + Serial.print(F("I-FEEL TEMPERATURE: ")); Serial.println(bytes[16]); } return true; From 99d0d2dbae6347f0622d7e185369ca41cc54e172 Mon Sep 17 00:00:00 2001 From: Toni Date: Wed, 10 Feb 2021 18:44:16 +0200 Subject: [PATCH 87/94] Reduce memory footprint only on low-memory devices --- rawirdecode/rawirdecode.ino | 70 +++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/rawirdecode/rawirdecode.ino b/rawirdecode/rawirdecode.ino index 46dc78e..1b38734 100644 --- a/rawirdecode/rawirdecode.ino +++ b/rawirdecode/rawirdecode.ino @@ -1,31 +1,55 @@ #include -// uncomment the define corresponding with your remote -//#define MITSUBISHI_ELECTRIC -//#define FUJITSU -//#define MITSUBISHI_HEAVY -//#define DAIKIN -//#define SHARP_ -//#define CARRIER -//#define PANASONIC_CKP -//#define PANASONIC_CS -//#define HYUNDAI -//#define GREE -//#define GREE_YAC -//#define FUEGO -//#define TOSHIBA -//#define NIBE -//#define AIRWELL -//#define HITACHI -//#define SAMSUNG -//#define BALLU -//#define AUX -//#define ZHLT01_REMOTE +#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(ESP32) + #define MITSUBISHI_ELECTRIC + #define FUJITSU + #define MITSUBISHI_HEAVY + #define DAIKIN + #define SHARP_ + #define CARRIER + #define PANASONIC_CKP + #define PANASONIC_CS + #define HYUNDAI + #define GREE + #define GREE_YAC + #define FUEGO + #define TOSHIBA + #define NIBE + #define AIRWELL + #define HITACHI + #define SAMSUNG + #define BALLU + #define AUX + #define ZHLT01_REMOTE +#else + // low-memory device, + // uncomment the define corresponding with your remote + //#define MITSUBISHI_ELECTRIC + //#define FUJITSU + //#define MITSUBISHI_HEAVY + //#define DAIKIN + //#define SHARP_ + //#define CARRIER + //#define PANASONIC_CKP + //#define PANASONIC_CS + //#define HYUNDAI + //#define GREE + //#define GREE_YAC + //#define FUEGO + //#define TOSHIBA + //#define NIBE + //#define AIRWELL + //#define HITACHI + //#define SAMSUNG + //#define BALLU + //#define AUX + //#define ZHLT01_REMOTE +#endif #if !defined(MITSUBISHI_ELECTRIC)&&!defined(FUJITSU)&&!defined(MITSUBISHI_HEAVY)&&!defined(DAIKIN)&&!defined(SHARP_)&&!defined(CARRIER)&&!defined(PANASONIC_CKP)&&!defined(PANASONIC_CS)&&!defined(HYUNDAI)&&!defined(GREE)&&!defined(GREE_YAC)&&!defined(FUEGO)&&!defined(TOSHIBA)&&!defined(NIBE)&&!defined(AIRWELL)&&!defined(HITACHI)&&!defined(SAMSUNG)&&!defined(BALLU)&&!defined(AUX)&&!defined(ZHLT01_REMOTE) -#error You must uncomment at least one brand define!! + #error You must uncomment at least one brand define!! #endif - + #if defined(MITSUBISHI_ELECTRIC) bool decodeMitsubishiElectric(byte *bytes, int byteCount); From 2d6ad6d9304b6f79aeb9ee6f41855375f2d1a436 Mon Sep 17 00:00:00 2001 From: Toni Date: Wed, 10 Feb 2021 18:44:36 +0200 Subject: [PATCH 88/94] Fix compiler warnings --- rawirdecode/Gree.cpp | 6 +++--- rawirdecode/rawirdecode.ino | 38 +++++++++++++++++++++---------------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/rawirdecode/Gree.cpp b/rawirdecode/Gree.cpp index 425816f..fde8cb8 100644 --- a/rawirdecode/Gree.cpp +++ b/rawirdecode/Gree.cpp @@ -12,9 +12,9 @@ bool decodeGree(byte *bytes, int pulseCount) (bytes[1] & 0x0F) + (bytes[2] & 0x0F) + (bytes[3] & 0x0F) + - (bytes[5] & 0xF0) >> 4 + - (bytes[6] & 0xF0) >> 4 + - (bytes[7] & 0xF0) >> 4 + + ((bytes[5] & 0xF0) >> 4) + + ((bytes[6] & 0xF0) >> 4) + + ((bytes[7] & 0xF0) >> 4) + 0x0A) & 0xF0; if (checksum == bytes[8]) { diff --git a/rawirdecode/rawirdecode.ino b/rawirdecode/rawirdecode.ino index 1b38734..90e5518 100644 --- a/rawirdecode/rawirdecode.ino +++ b/rawirdecode/rawirdecode.ino @@ -131,13 +131,13 @@ // http://arduino.cc/en/Hacking/PinMapping168 for the 'raw' pin mapping #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) -#define IRpin_PIN PINE -#define IRpin 4 + #define IRpin_PIN PINE + #define IRpin 4 #elif defined(ESP32) -#define IRpin 25 // G25 on M5STACK ATOM LITE + #define IRpin 25 // G25 on M5STACK ATOM LITE #else -#define IRpin_PIN PIND -#define IRpin 2 + #define IRpin_PIN PIND + #define IRpin 2 #endif // the maximum pulse we'll listen for - 65 milliseconds is a long time @@ -276,9 +276,9 @@ void loop(void) { Serial.println("################# Start"); printPulses(); decodeProtocols(); - Serial.println("################# End "); - Serial.println(); - } + Serial.println("################# End "); + Serial.println(); + } } void receivePulses(void) { @@ -308,7 +308,7 @@ void receivePulses(void) { while (currentpulse < sizeof(symbols)) { highpulse = 0; - + while (getPinState()) { // pin is still HIGH @@ -335,18 +335,22 @@ void receivePulses(void) { if ( highpulse > SPACE_THRESHOLD_HEADER_PAUSE ) { symbols[currentpulse] = 'W'; // Cumulative moving average, see http://en.wikipedia.org/wiki/Moving_average#Cumulative_moving_average - space_pause_avg = (highpulse + space_pause_cnt * space_pause_avg) / ++space_pause_cnt; + space_pause_avg = (highpulse + space_pause_cnt * space_pause_avg) / (space_pause_cnt + 1); + space_pause_cnt++; } else if ( (currentpulse > 0 && symbols[currentpulse-1] == 'H') || highpulse > SPACE_THRESHOLD_ONE_HEADER ) { symbols[currentpulse] = 'h'; // Cumulative moving average, see http://en.wikipedia.org/wiki/Moving_average#Cumulative_moving_average if (highpulse > SPACE_THRESHOLD_ONE_HEADER) - space_header_avg = (highpulse + space_header_cnt * space_header_avg) / ++space_header_cnt; + space_header_avg = (highpulse + space_header_cnt * space_header_avg) / (space_header_cnt + 1); + space_header_cnt++; } else if ( highpulse > SPACE_THRESHOLD_ZERO_ONE ) { symbols[currentpulse] = '1'; - space_one_avg = (highpulse + space_one_cnt * space_one_avg) / ++space_one_cnt; + space_one_avg = (highpulse + space_one_cnt * space_one_avg) / (space_one_cnt + 1); + space_one_cnt++; } else { symbols[currentpulse] = '0'; - space_zero_avg = (highpulse + space_zero_cnt * space_zero_avg) / ++space_zero_cnt; + space_zero_avg = (highpulse + space_zero_cnt * space_zero_avg) / (space_zero_cnt + 1); + space_zero_cnt++; } } currentpulse++; @@ -371,9 +375,11 @@ void receivePulses(void) { if ( lowpulse > MARK_THRESHOLD_BIT_HEADER ) { symbols[currentpulse] = 'H'; currentpulse++; - mark_header_avg = (lowpulse + mark_header_cnt * mark_header_avg) / ++mark_header_cnt; + mark_header_avg = (lowpulse + mark_header_cnt * mark_header_avg) / (mark_header_cnt + 1); + mark_header_cnt++; } else { - mark_bit_avg = (lowpulse + mark_bit_cnt * mark_bit_avg) / ++mark_bit_cnt; + mark_bit_avg = (lowpulse + mark_bit_cnt * mark_bit_avg) / (mark_bit_cnt + 1); + mark_bit_cnt++; } // we read one high-low pulse successfully, continue! @@ -489,7 +495,7 @@ void decodeProtocols() Serial.println(F("Decoding known protocols...")); bool knownProtocol; - + #if defined(MITSUBISHI_ELECTRIC) knownProtocol = decodeMitsubishiElectric(bytes, byteCount); #endif From a80d451b57026e5efbbed5a090e8c4c942c84dea Mon Sep 17 00:00:00 2001 From: EmmanuelMess Date: Mon, 20 Feb 2023 09:58:59 -0300 Subject: [PATCH 89/94] Add Philco IR recognition --- README.md | 1 + rawirdecode/Philco.cpp | 331 ++++++++++++++++++++++++++++++++++++ rawirdecode/rawirdecode.ino | 9 +- 3 files changed, 340 insertions(+), 1 deletion(-) create mode 100644 rawirdecode/Philco.cpp diff --git a/README.md b/README.md index 2d0ca52..ef08d0f 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ You will get a compiler error if you forget to uncomment! * //#define BALLU * //#define AUX * //#define ZHLT01_REMOTE +* //#define PHILCO ### Arduino IDE * Open the sketch from subdirectory 'rawirdecode' in Arduino IDE and build diff --git a/rawirdecode/Philco.cpp b/rawirdecode/Philco.cpp new file mode 100644 index 0000000..3110fd7 --- /dev/null +++ b/rawirdecode/Philco.cpp @@ -0,0 +1,331 @@ +#include + +// Philco with remote KT-L12010C + +bool decodePhilco(byte *bytes, int byteCount) +{ + if (byteCount == 21 && bytes[0] == 0x83 && bytes[1] == 0x06) { + Serial.println(F("Looks like a Philco protocol")); + + if (bytes[2] & 0x04 && bytes[15] == 0x01) { + Serial.println(F("POWER ON")); + } + + { + Serial.print(F("Current time: ")); + Serial.print(bytes[6] & 0x5F); + Serial.print(F(":")); + Serial.println(bytes[7]); + } + + // Operating mode + if((bytes[4] & 0xF) == 0x02) { + switch (bytes[13]) { + case 0x07: + Serial.println(F("MODE COOL")); + break; + case 0xF1: + Serial.println(F("MODE DRY")); + break; + case 0xF7: + Serial.println(F("MODE FAN")); + break; + case 0xD2: + Serial.println(F("MODE HEAT")); + break; + } + } else if((bytes[3] & 0xF) == 0x1) { + Serial.println(F("MODE SMART")); + + if((bytes[2] & 0xF0) == 0x00) { + Serial.println(F("SMART: --")); + } else if((bytes[2] & 0xF0) == 0x10 && bytes[16] == 0x00) { + Serial.println(F("SMART: 1")); + } else if((bytes[2] & 0xF0) == 0x50 && bytes[16] == 0x00) { + Serial.println(F("SMART: -1")); + } else if((bytes[2] & 0xF0) == 0x20) { + switch (bytes[16]) { + case 0x00: + Serial.println(F("SMART: 2")); + break; + case 0x04: + Serial.println(F("SMART: 3")); + break; + case 0x08: + Serial.println(F("SMART: 4")); + break; + case 0x0C: + Serial.println(F("SMART: 5")); + break; + case 0x10: + Serial.println(F("SMART: 6")); + break; + case 0x14: + Serial.println(F("SMART: 7")); + break; + } + } else if((bytes[2] & 0xF0) == 0x60) { + switch (bytes[16]) { + case 0x00: + Serial.println(F("SMART: -2")); + break; + case 0x04: + Serial.println(F("SMART: -3")); + break; + case 0x08: + Serial.println(F("SMART: -4")); + break; + case 0x0C: + Serial.println(F("SMART: -5")); + break; + case 0x10: + Serial.println(F("SMART: -6")); + break; + case 0x14: + Serial.println(F("SMART: -7")); + break; + } + } + } + + if (bytes[3] == 0x73) { + Serial.println(F("MODE DRY")); + + if((bytes[2] & 0xF0) == 0x00) { + Serial.println(F("DRY: --")); + } else if((bytes[2] & 0xF0) == 0x10 && bytes[16] == 0x00) { + Serial.println(F("DRY: 1")); + } else if((bytes[2] & 0xF0) == 0x50 && bytes[16] == 0x00) { + Serial.println(F("DRY: -1")); + } else if((bytes[2] & 0xF0) == 0x20) { + switch (bytes[16]) { + case 0x00: + Serial.println(F("DRY: 2")); + break; + case 0x04: + Serial.println(F("DRY: 3")); + break; + case 0x08: + Serial.println(F("DRY: 4")); + break; + case 0x0C: + Serial.println(F("DRY: 5")); + break; + case 0x10: + Serial.println(F("DRY: 6")); + break; + case 0x14: + Serial.println(F("DRY: 7")); + break; + } + } else if((bytes[2] & 0xF0) == 0x60) { + switch (bytes[16]) { + case 0x00: + Serial.println(F("DRY: -2")); + break; + case 0x04: + Serial.println(F("DRY: -3")); + break; + case 0x08: + Serial.println(F("DRY: -4")); + break; + case 0x0C: + Serial.println(F("DRY: -5")); + break; + case 0x10: + Serial.println(F("DRY: -6")); + break; + case 0x14: + Serial.println(F("DRY: -7")); + break; + } + } + } + + // Temperature target + if ((bytes[3] & 0xF) == 0x2 || (bytes[3] & 0xF) == 0x0) { + Serial.print(F("Selected temperature: ")); + Serial.print((bytes[3] >> 4) + 16); + Serial.println(F("°")); + } + + // Temperature real + if(bytes[11] == 0x80) { + Serial.print(F("Current temperature: ")); + Serial.print((bytes[12] & 0x0F) + 16); + Serial.println(F("°")); + } + + // Fan speed + switch (bytes[2] & 0x03) { + case 0x00: + Serial.println(F("FAN: AUTO")); + break; + case 0x03: + Serial.println(F("FAN: 1 (low)")); + break; + case 0x02: + Serial.println(F("FAN: 2 (medium)")); + break; + case 0x01: + Serial.println(F("FAN: 3 (high)")); + break; + } + + if (bytes[2] == 0x03 && bytes[14] == 0x04 && bytes[15] == 0x0B) { + Serial.println(F("QUIET: ON")); + } else if (bytes[14] == 0x00 && bytes[15] == 0x0B) { + Serial.println(F("QUIET: OFF)")); + } + + // Air direction + if(bytes[8] == 0x80 && bytes[15] == 0x08) { + Serial.println(F("FIN: left to right")); + } + + if(bytes[8] == 0x40 && bytes[15] == 0x07) { + Serial.println(F("FIN: up and down")); + } + + if((bytes[2] & 0x0F) == 0x0B) { + // Sleep mode + switch (bytes[14]) { + case 0x00: + Serial.println(F("SLEEP: 1")); + break; + case 0x40: + Serial.println(F("SLEEP: 2")); + break; + case 0x80: + Serial.println(F("SLEEP: 3")); + break; + case 0xC0: + Serial.println(F("SLEEP: 4")); + break; + } + } else { + Serial.println(F("SLEEP: OFF")); + } + + // Turbo mode + Serial.print(F("TURBO: ")); + switch (bytes[5]) { + case 0x90: + case 0x10: + Serial.println(F("ON")); + break; + case 0x00: + Serial.println(F("OFF")); + break; + } + + Serial.print(F("ECONOMY: ")); + switch (bytes[14]) { + case 0x20: + Serial.println(F("ON")); + break; + case 0x00: + Serial.println(F("OFF")); + break; + } + + // Ifeel mode + Serial.print(F("IFEEL: ")); + switch (bytes[11]) { + case 0x80: + Serial.println(F("ON")); + break; + default: + Serial.println(F("OFF")); + break; + } + + //Dimmer + if ((bytes[6] & 0x20)) { + Serial.println(F("DIMMER: ON")); + } else { + Serial.println(F("DIMMER: OFF")); + } + + // Timer + byte hoursOff = bytes[8]; + byte minutesOff = bytes[9] & 0x7F; + byte hoursOn = bytes[10]; + byte minutesOn = bytes[11] & 0x7F; + + if (bytes[9] & 0x80 && bytes[15] == 0x05) { + { + Serial.print(F("Activate TIMER ON at ")); + Serial.print(hoursOn); + Serial.print(F(":")); + Serial.println(minutesOn); + } + + { + Serial.print(F("TIMER OFF at ")); + Serial.print(hoursOff); + Serial.print(F(":")); + Serial.println(minutesOff); + } + } else if (bytes[15] == 0x1D) { + { + Serial.print(F("Activate TIMER OFF at ")); + Serial.print(hoursOff); + Serial.print(F(":")); + Serial.println(minutesOff); + } + + { + Serial.print(F("TIMER ON at ")); + Serial.print(hoursOn); + Serial.print(F(":")); + Serial.println(minutesOn); + } + } else if (bytes[10] == 0x00 && bytes[11] == 0x80 && bytes[15] == 0x05) { + Serial.println(F("Deactivate TIMER ON")); + } else if (bytes[8] == 0x00 && bytes[9] == 0x80 && bytes[15] == 0x1D) { + Serial.println(F("Deactivate TIMER OFF")); + } + + { + byte originalChecksum = bytes[13]; + byte calcuatedChecksum = 0x00; + + for (int i = 2; i < 13; i++) { + calcuatedChecksum ^= bytes[i]; + } + + Serial.print(F("First checksum '0x")); + Serial.print(calcuatedChecksum, HEX); + + if ( originalChecksum == calcuatedChecksum ) { + Serial.println(F("' matches")); + } else { + Serial.print(F("' does not match 0x")); + Serial.println(originalChecksum, HEX); + } + } + + { + byte originalChecksum = bytes[20]; + byte calcuatedChecksum = 0x00; + + for (int i = 14; i < 20; i++) { + calcuatedChecksum ^= bytes[i]; + } + + Serial.print(F("Second checksum '0x")); + Serial.print(calcuatedChecksum, HEX); + + if ( originalChecksum == calcuatedChecksum ) { + Serial.println(F("' matches")); + } else { + Serial.print(F("' does not match 0x")); + Serial.println(originalChecksum, HEX); + } + } + return true; + } + + return false; +} diff --git a/rawirdecode/rawirdecode.ino b/rawirdecode/rawirdecode.ino index 90e5518..aa74b8b 100644 --- a/rawirdecode/rawirdecode.ino +++ b/rawirdecode/rawirdecode.ino @@ -44,9 +44,10 @@ //#define BALLU //#define AUX //#define ZHLT01_REMOTE + //#define PHILCO #endif -#if !defined(MITSUBISHI_ELECTRIC)&&!defined(FUJITSU)&&!defined(MITSUBISHI_HEAVY)&&!defined(DAIKIN)&&!defined(SHARP_)&&!defined(CARRIER)&&!defined(PANASONIC_CKP)&&!defined(PANASONIC_CS)&&!defined(HYUNDAI)&&!defined(GREE)&&!defined(GREE_YAC)&&!defined(FUEGO)&&!defined(TOSHIBA)&&!defined(NIBE)&&!defined(AIRWELL)&&!defined(HITACHI)&&!defined(SAMSUNG)&&!defined(BALLU)&&!defined(AUX)&&!defined(ZHLT01_REMOTE) +#if !defined(MITSUBISHI_ELECTRIC)&&!defined(FUJITSU)&&!defined(MITSUBISHI_HEAVY)&&!defined(DAIKIN)&&!defined(SHARP_)&&!defined(CARRIER)&&!defined(PANASONIC_CKP)&&!defined(PANASONIC_CS)&&!defined(HYUNDAI)&&!defined(GREE)&&!defined(GREE_YAC)&&!defined(FUEGO)&&!defined(TOSHIBA)&&!defined(NIBE)&&!defined(AIRWELL)&&!defined(HITACHI)&&!defined(SAMSUNG)&&!defined(BALLU)&&!defined(AUX)&&!defined(ZHLT01_REMOTE)&&!defined(PHILCO) #error You must uncomment at least one brand define!! #endif @@ -109,6 +110,9 @@ #if defined(ZHLT01_REMOTE) bool decodeZHLT01remote(byte *bytes, int byteCount); #endif +#if defined(PHILCO) + bool decodePhilco(byte *bytes, int byteCount); +#endif /* Raw IR decoder sketch! @@ -553,6 +557,9 @@ void decodeProtocols() #if defined(ZHLT01_REMOTE) knownProtocol = decodeZHLT01remote(bytes, byteCount); #endif +#if defined(PHILCO) + knownProtocol = decodePhilco(bytes, byteCount); +#endif if (!knownProtocol) { From d2e4057611269698be929510ca85bec601d9e32a Mon Sep 17 00:00:00 2001 From: abmantis Date: Mon, 3 Jul 2023 00:14:07 +0100 Subject: [PATCH 90/94] Add support for ZH/JG-01 The remove is not working, so I only have the Xiaomi IR App to test. Despite working, the app is missing the Timer options so I have no way to extract those out. --- rawirdecode/ZHJG01Remote.cpp | 208 +++++++++++++++++++++++++++++++++++ rawirdecode/rawirdecode.ino | 18 ++- 2 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 rawirdecode/ZHJG01Remote.cpp diff --git a/rawirdecode/ZHJG01Remote.cpp b/rawirdecode/ZHJG01Remote.cpp new file mode 100644 index 0000000..6cea97a --- /dev/null +++ b/rawirdecode/ZHJG01Remote.cpp @@ -0,0 +1,208 @@ + +/******************************************************************************** + * Airconditional remote control decoder for: + * + * ZH/JG-01 Remote control https://www.google.com/search?q=zh/JG-01 + * + * The ZH/JG-01 remote control is used for many locally branded Split airconditioners, + * so it is better to name this protocol by the name of the protocol rather then the + * name of the Airconditioner. For this project I used a TACHIAIR airconditioner. + * + * For airco-brands: + * Tachiair + * Chigo + * Etc. + * + *********************************************************************************** + * SUMMARY FUNCTIONAL DESCRIPTION + ********************************************************************************** + * The remote sends a 6 Byte message which contains all possible settings every + * time. + * + * Every EVEN Byte (00,02,04,06,08 and 10) hold command data + * Every UNeven Byte (01,03,05,07 and 09) hold a checksum of the corresponding + * command by inverting the bits, for example: + * + * The identifier byte[0] = 0xD5 = B1101 0101 + * The checksum byte[1] = 0x2A = B0010 1010 + * + * So, you can check the message by: + * - inverting the bits of the checksum byte with the corresponding command, they + * should be the same, or + * - Summing up the checksum byte and the corresponding command, + * they should always add up to 0xFF = B11111111 = 255 + * + * TIMINGS + * (Only ZH/JG-01 (option 6) work) + * Space: Not used + * Header Mark: 6550 us + * Header Space: 0 us + * Bit Mark: 560 us + * Zero Space: 1530 us + * One Space: 3750 us + * + * ****************************************************************************** + * Written by: Abílio Costa + * Date: 2023-07-03 + * Version: 1.0 + *******************************************************************************/ + +#include + +byte reverseByte(byte input) { + byte output = 0; + for (int i = 0; i < 8; ++i) { + output <<= 1; + output |= (input & 1); + input >>= 1; + } + return output; +} + +bool decodeZHJG01remote(byte *bytes, int byteCount) +{ + +/******************************************************************************** + * Is this a ZH/JG-01 Remote? + * Message should by 6 bytes, 52 symbols, with valid checksum. + * + *******************************************************************************/ + + if (byteCount != 6) { + return false; + } + + bool checksum = true; + for (int i = 0; i < 6; i+=2) { + checksum &= (bytes[i] + bytes[i+1] == 255); + } + + if (!checksum) { + return false; + } + + Serial.println(F("Looks like a ZH/JG-01 remote control protocol")); + +/******************************************************************************** + * Byte[0]: Turbo, Eco, Fan, Vertical Swing + * TURBO ON: B0x1xxxxx + * ECO ON: B0x0xxxxx + * TURBO/ECO OFF: B1xxxxxxx + * FAN1: Bx00xxxxx + * FAN2: Bx01xxxxx + * FAN3: Bx10xxxxx + * FAN AUTO: Bx11xxxxx + * VERTICAL FIXED: Bxxx01xxx + * VERTICAL SWING: Bxxx10xxx + * VERTICAL WIND: Bxxx11xxx + *******************************************************************************/ + // TURBO ON/OFF + Serial.print(F("bytes[0] bit0+2 Turbo: ")); + switch (bytes[0]& B10100000) { + case B00100000: + Serial.println(F("On")); + break; + default: + Serial.println(F("Off")); + } + + // FAN ON/OFF + Serial.print(F("bytes[0] bit0+2 Eco: ")); + switch (bytes[0]& B10100000) { + case B00000000: + Serial.println(F("On")); + break; + default: + Serial.println(F("Off")); + } + + Serial.print(F("bytes[0] bit1-2 Fan Speed: ")); + switch (bytes[0] & B01100000) { + case B00000000: + Serial.println(F("1")); + break; + case B00100000: + Serial.println(F("2")); + break; + case B01000000: + Serial.println(F("3")); + break; + case B01100000: + Serial.println(F("Auto")); + break; + default: + Serial.println(F("Unknown")); + } + + // Vertical air swing + Serial.print(F("bytes[0] bit3-4 Vertical: ")); + switch (bytes[0] & B00011000) { + case B00010000: + Serial.println(F("Swing")); + break; + case B00011000: + Serial.println(F("Wind")); + break; + case B00001000: + Serial.println(F("Fixed")); + break; + default: + Serial.println(F("Unknown")); + } + +/******************************************************************************** + * Byte[2]: Temp, Power, Mode + * TEMP: Bttttxxxx + * POWER ON: Bxxxx0xxx + * POWER OFF: Bxxxx1xxx + * MODE HEAT: Bxxxxx011 + * MODE VENT: Bxxxxx100 + * MODE DRY: Bxxxxx101 + * MODE COOL: Bxxxxx110 + * MODE AUTO: Bxxxxx111 + *******************************************************************************/ + + // TEMPERATURE + byte tempByte = 0; + Serial.print("bytes[2] Temperature: "); + Serial.print(((~bytes[2] >> 4) & 0b1111) + 17); + Serial.println("°C"); + + // POWER ON/OFF + Serial.print(F("bytes[2] bit4 Power: ")); + switch (bytes[2] & B00001000) { + case B00001000: + Serial.println(F("Off")); + break; + case B00000000: + Serial.println(F("On")); + break; + default: + Serial.println(F("Unknown")); + } + + // MODE + Serial.print(F("bytes[2] bit5-7 Mode: ")); + switch (bytes[2] & B00000111) { + case B00000111: + Serial.println(F("Auto")); + break; + case B00000110: + Serial.println(F("Cool")); + break; + case B00000100: + Serial.println(F("Ventilate")); + break; + case B00000101: + Serial.println(F("Dry")); + break; + case B00000011: + Serial.println(F("Heat")); + break; + default: + Serial.println("Unknown"); + } + + return true; +} + diff --git a/rawirdecode/rawirdecode.ino b/rawirdecode/rawirdecode.ino index aa74b8b..fef2801 100644 --- a/rawirdecode/rawirdecode.ino +++ b/rawirdecode/rawirdecode.ino @@ -21,6 +21,7 @@ #define BALLU #define AUX #define ZHLT01_REMOTE + #define ZHJG01_REMOTE #else // low-memory device, // uncomment the define corresponding with your remote @@ -47,7 +48,7 @@ //#define PHILCO #endif -#if !defined(MITSUBISHI_ELECTRIC)&&!defined(FUJITSU)&&!defined(MITSUBISHI_HEAVY)&&!defined(DAIKIN)&&!defined(SHARP_)&&!defined(CARRIER)&&!defined(PANASONIC_CKP)&&!defined(PANASONIC_CS)&&!defined(HYUNDAI)&&!defined(GREE)&&!defined(GREE_YAC)&&!defined(FUEGO)&&!defined(TOSHIBA)&&!defined(NIBE)&&!defined(AIRWELL)&&!defined(HITACHI)&&!defined(SAMSUNG)&&!defined(BALLU)&&!defined(AUX)&&!defined(ZHLT01_REMOTE)&&!defined(PHILCO) +#if !defined(MITSUBISHI_ELECTRIC)&&!defined(FUJITSU)&&!defined(MITSUBISHI_HEAVY)&&!defined(DAIKIN)&&!defined(SHARP_)&&!defined(CARRIER)&&!defined(PANASONIC_CKP)&&!defined(PANASONIC_CS)&&!defined(HYUNDAI)&&!defined(GREE)&&!defined(GREE_YAC)&&!defined(FUEGO)&&!defined(TOSHIBA)&&!defined(NIBE)&&!defined(AIRWELL)&&!defined(HITACHI)&&!defined(SAMSUNG)&&!defined(BALLU)&&!defined(AUX)&&!defined(ZHLT01_REMOTE)&&!defined(ZHJG01_REMOTE)&&!defined(PHILCO) #error You must uncomment at least one brand define!! #endif @@ -110,6 +111,9 @@ #if defined(ZHLT01_REMOTE) bool decodeZHLT01remote(byte *bytes, int byteCount); #endif +#if defined(ZHJG01_REMOTE) + bool decodeZHJG01remote(byte *bytes, int byteCount); +#endif #if defined(PHILCO) bool decodePhilco(byte *bytes, int byteCount); #endif @@ -194,6 +198,7 @@ void setup(void) { Serial.println(F("* '3' for Mitsubishi Heavy etc. codes")); Serial.println(F("* '4' for Hyundai etc. codes")); Serial.println(F("* '5' for Samsung etc. codes")); + Serial.println(F("* '6' for ZH/JG-01 codes")); Serial.println(F("* '9' for entering the bit sequence on the serial monitor (instead of the IR receiver)")); Serial.println(); Serial.print(F("Enter choice: ")); @@ -220,6 +225,9 @@ void setup(void) { case '5': modelChoice = 5; break; + case '6': + modelChoice = 6; + break; case '9': modelChoice = 9; break; @@ -256,6 +264,11 @@ void setup(void) { SPACE_THRESHOLD_ZERO_ONE = 800; SPACE_THRESHOLD_ONE_HEADER = 2400; SPACE_THRESHOLD_HEADER_PAUSE = 10000; + } else if (modelChoice == 6) { + MARK_THRESHOLD_BIT_HEADER = 6500; + SPACE_THRESHOLD_ZERO_ONE = 2100; + SPACE_THRESHOLD_ONE_HEADER = 7750; + SPACE_THRESHOLD_HEADER_PAUSE = 9000; } } @@ -557,6 +570,9 @@ void decodeProtocols() #if defined(ZHLT01_REMOTE) knownProtocol = decodeZHLT01remote(bytes, byteCount); #endif +#if defined(ZHJG01_REMOTE) + knownProtocol = decodeZHJG01remote(bytes, byteCount); +#endif #if defined(PHILCO) knownProtocol = decodePhilco(bytes, byteCount); #endif From 1dfd1236213d8859993d228862e7fe762561fca0 Mon Sep 17 00:00:00 2001 From: abmantis Date: Tue, 4 Jul 2023 22:26:11 +0100 Subject: [PATCH 91/94] Use the non-inverted bytes The odd bytes make more sense to use, since 0 is OFF and temperature does not need to be fliped --- rawirdecode/ZHJG01Remote.cpp | 106 +++++++++++++++++------------------ 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/rawirdecode/ZHJG01Remote.cpp b/rawirdecode/ZHJG01Remote.cpp index 6cea97a..6f48f06 100644 --- a/rawirdecode/ZHJG01Remote.cpp +++ b/rawirdecode/ZHJG01Remote.cpp @@ -19,12 +19,12 @@ * The remote sends a 6 Byte message which contains all possible settings every * time. * - * Every EVEN Byte (00,02,04,06,08 and 10) hold command data - * Every UNeven Byte (01,03,05,07 and 09) hold a checksum of the corresponding + * Every UNeven Byte (01,03,05,07 and 09) hold command data + * Every EVEN Byte (00,02,04,06,08 and 10) hold a checksum of the corresponding * command by inverting the bits, for example: * - * The identifier byte[0] = 0xD5 = B1101 0101 - * The checksum byte[1] = 0x2A = B0010 1010 + * The checksum byte[0] = 0x2A = B0010 1010 + * The identifier byte[1] = 0xD5 = B1101 0101 * * So, you can check the message by: * - inverting the bits of the checksum byte with the corresponding command, they @@ -84,22 +84,22 @@ bool decodeZHJG01remote(byte *bytes, int byteCount) Serial.println(F("Looks like a ZH/JG-01 remote control protocol")); /******************************************************************************** - * Byte[0]: Turbo, Eco, Fan, Vertical Swing - * TURBO ON: B0x1xxxxx - * ECO ON: B0x0xxxxx - * TURBO/ECO OFF: B1xxxxxxx - * FAN1: Bx00xxxxx - * FAN2: Bx01xxxxx - * FAN3: Bx10xxxxx - * FAN AUTO: Bx11xxxxx - * VERTICAL FIXED: Bxxx01xxx - * VERTICAL SWING: Bxxx10xxx - * VERTICAL WIND: Bxxx11xxx + * Byte[1]: Turbo, Eco, Fan, Vertical Swing + * TURBO ON: B1x0xxxxx + * ECO ON: B1x1xxxxx + * TURBO/ECO OFF: B0xxxxxxx + * FAN1: Bx11xxxxx + * FAN2: Bx10xxxxx + * FAN3: Bx01xxxxx + * FAN AUTO: Bx00xxxxx + * VERTICAL FIXED: Bxxx10xxx + * VERTICAL SWING: Bxxx01xxx + * VERTICAL WIND: Bxxx00xxx *******************************************************************************/ // TURBO ON/OFF - Serial.print(F("bytes[0] bit0+2 Turbo: ")); - switch (bytes[0]& B10100000) { - case B00100000: + Serial.print(F("bytes[1] bit0+2 Turbo: ")); + switch (bytes[1]& B10100000) { + case B10000000: Serial.println(F("On")); break; default: @@ -107,27 +107,27 @@ bool decodeZHJG01remote(byte *bytes, int byteCount) } // FAN ON/OFF - Serial.print(F("bytes[0] bit0+2 Eco: ")); - switch (bytes[0]& B10100000) { - case B00000000: + Serial.print(F("bytes[1] bit0+2 Eco: ")); + switch (bytes[1]& B10100000) { + case B10100000: Serial.println(F("On")); break; default: Serial.println(F("Off")); } - Serial.print(F("bytes[0] bit1-2 Fan Speed: ")); - switch (bytes[0] & B01100000) { - case B00000000: + Serial.print(F("bytes[1] bit1-2 Fan Speed: ")); + switch (bytes[1] & B01100000) { + case B01100000: Serial.println(F("1")); break; - case B00100000: + case B01000000: Serial.println(F("2")); break; - case B01000000: + case B00100000: Serial.println(F("3")); break; - case B01100000: + case B00000000: Serial.println(F("Auto")); break; default: @@ -135,15 +135,15 @@ bool decodeZHJG01remote(byte *bytes, int byteCount) } // Vertical air swing - Serial.print(F("bytes[0] bit3-4 Vertical: ")); - switch (bytes[0] & B00011000) { - case B00010000: + Serial.print(F("bytes[1] bit3-4 Vertical: ")); + switch (bytes[1] & B00011000) { + case B00001000: Serial.println(F("Swing")); break; - case B00011000: + case B00000000: Serial.println(F("Wind")); break; - case B00001000: + case B00010000: Serial.println(F("Fixed")); break; default: @@ -151,30 +151,30 @@ bool decodeZHJG01remote(byte *bytes, int byteCount) } /******************************************************************************** - * Byte[2]: Temp, Power, Mode + * Byte[3]: Temp, Power, Mode * TEMP: Bttttxxxx - * POWER ON: Bxxxx0xxx - * POWER OFF: Bxxxx1xxx - * MODE HEAT: Bxxxxx011 - * MODE VENT: Bxxxxx100 - * MODE DRY: Bxxxxx101 - * MODE COOL: Bxxxxx110 - * MODE AUTO: Bxxxxx111 + * POWER ON: Bxxxx1xxx + * POWER OFF: Bxxxx0xxx + * MODE HEAT: Bxxxxx100 + * MODE VENT: Bxxxxx011 + * MODE DRY: Bxxxxx010 + * MODE COOL: Bxxxxx001 + * MODE AUTO: Bxxxxx000 *******************************************************************************/ // TEMPERATURE byte tempByte = 0; - Serial.print("bytes[2] Temperature: "); - Serial.print(((~bytes[2] >> 4) & 0b1111) + 17); + Serial.print("bytes[3] Temperature: "); + Serial.print(((bytes[3] >> 4) & 0b1111) + 17); Serial.println("°C"); // POWER ON/OFF - Serial.print(F("bytes[2] bit4 Power: ")); - switch (bytes[2] & B00001000) { - case B00001000: + Serial.print(F("bytes[3] bit4 Power: ")); + switch (bytes[3] & B00001000) { + case B00000000: Serial.println(F("Off")); break; - case B00000000: + case B00001000: Serial.println(F("On")); break; default: @@ -182,21 +182,21 @@ bool decodeZHJG01remote(byte *bytes, int byteCount) } // MODE - Serial.print(F("bytes[2] bit5-7 Mode: ")); - switch (bytes[2] & B00000111) { - case B00000111: + Serial.print(F("bytes[3] bit5-7 Mode: ")); + switch (bytes[3] & B00000111) { + case B00000000: Serial.println(F("Auto")); break; - case B00000110: + case B00000001: Serial.println(F("Cool")); break; - case B00000100: + case B00000011: Serial.println(F("Ventilate")); break; - case B00000101: + case B00000010: Serial.println(F("Dry")); break; - case B00000011: + case B00000100: Serial.println(F("Heat")); break; default: From e483267b0c6358613ff893d86d1fbdd9b0aa97ea Mon Sep 17 00:00:00 2001 From: she11sh0cked <22623152+she11sh0cked@users.noreply.github.com> Date: Sat, 10 Aug 2024 01:52:09 +0200 Subject: [PATCH 92/94] feat(nodemcu): Add support for ESP8266 (nodemcu) --- README.md | 3 ++- platformio.ini | 7 ++++++- rawirdecode/rawirdecode.ino | 28 ++++++++++++++++++++++++++-- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ef08d0f..7ccc4e4 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,10 @@ You will get a compiler error if you forget to uncomment! * Open the sketch from subdirectory 'rawirdecode' in Arduino IDE and build ### PlatformIO -* platformio.ini contains build definitions for Arduino Mega and ESP32 (tested on M5STACK ATOM LITE) +* platformio.ini contains build definitions for Arduino Mega, ESP32 (both tested on M5STACK ATOM LITE) and ESP8266 (tested on nodemcu) * On Mega, connect the receiver data pin to GPIO 2 * On ESP32, connect the receiver data pin to GPIO 25 +* On ESP8266, connect the receiver data pin to GPIO 5 ## Instructions diff --git a/platformio.ini b/platformio.ini index 727a775..4406c02 100644 --- a/platformio.ini +++ b/platformio.ini @@ -12,4 +12,9 @@ upload_speed = 115200 [env:atmega2560] platform = atmelavr framework = arduino -board = megaatmega2560 \ No newline at end of file +board = megaatmega2560 + +[env:esp8266] +platform = espressif8266 +framework = arduino +board = nodemcuv2 \ No newline at end of file diff --git a/rawirdecode/rawirdecode.ino b/rawirdecode/rawirdecode.ino index fef2801..3363504 100644 --- a/rawirdecode/rawirdecode.ino +++ b/rawirdecode/rawirdecode.ino @@ -1,6 +1,6 @@ #include -#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(ESP32) +#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(ESP32) || defined(ESP8266) #define MITSUBISHI_ELECTRIC #define FUJITSU #define MITSUBISHI_HEAVY @@ -143,6 +143,8 @@ #define IRpin 4 #elif defined(ESP32) #define IRpin 25 // G25 on M5STACK ATOM LITE +#elif defined(ESP8266) + #define IRpin 5 #else #define IRpin_PIN PIND #define IRpin 2 @@ -204,6 +206,10 @@ void setup(void) { Serial.print(F("Enter choice: ")); while (modelChoice == 0) { + #if defined(ESP8266) + yield(); + #endif + int selection = Serial.read(); if ( selection != -1 ) { @@ -283,7 +289,11 @@ void loop(void) { if (modelChoice != 9) { receivePulses(); } else { - while ((currentpulse = Serial.readBytesUntil('\n', symbols+1, sizeof(symbols)-1)) == 0) {} + while ((currentpulse = Serial.readBytesUntil('\n', symbols+1, sizeof(symbols)-1)) == 0) { + #if defined(ESP8266) + yield(); + #endif + } currentpulse++; } @@ -324,9 +334,17 @@ void receivePulses(void) { while (currentpulse < sizeof(symbols)) { + #if defined(ESP8266) + yield(); + #endif + highpulse = 0; while (getPinState()) { + #if defined(ESP8266) + yield(); + #endif + // pin is still HIGH // count off another few microseconds @@ -375,6 +393,10 @@ void receivePulses(void) { // same as above lowpulse = 0; while (! getPinState()) { + #if defined(ESP8266) + yield(); + #endif + // pin is still LOW lowpulse++; delayMicroseconds(RESOLUTION); @@ -406,6 +428,8 @@ void receivePulses(void) { bool getPinState() { #if defined(ESP32) return gpio_get_level(gpio_num_t(IRpin)); +#elif defined(ESP8266) + return digitalRead(IRpin); #else return (IRpin_PIN & _BV(IRpin)); #endif From e263fd32baa2077949a705de09158fe6f33d293c Mon Sep 17 00:00:00 2001 From: she11sh0cked <22623152+she11sh0cked@users.noreply.github.com> Date: Sat, 10 Aug 2024 02:29:25 +0200 Subject: [PATCH 93/94] feat(KY-26 Remote): Add support for KY-26 remote control Tested with a ZAICON air conditioner remote control. --- rawirdecode/KY26Remote.cpp | 158 ++++++++++++++++++++++++++++++++++++ rawirdecode/rawirdecode.ino | 10 ++- 2 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 rawirdecode/KY26Remote.cpp diff --git a/rawirdecode/KY26Remote.cpp b/rawirdecode/KY26Remote.cpp new file mode 100644 index 0000000..e9694c0 --- /dev/null +++ b/rawirdecode/KY26Remote.cpp @@ -0,0 +1,158 @@ +#include + +// KY-26 Remote protocol +// +// The KY-26 remote control is used in locally branded air conditioners. +// I used a ZAICON air conditioner, which also seems to be rebranded as +// SACOM, SENCYS, and possibly others. +// +// The remote sends a 4-byte message which contains all possible settings every +// time. +// +// Byte 0 contains the a power signal, operating mode, and fan speed. +// Byte 1 contains the timer setting. +// Byte 2 contains the temperature setting. +// Byte 3 contains a checksum. + +bool decodeKY26Remote(byte *bytes, int byteCount) { + // If this looks like a KY-26 code... + if (byteCount != 4 // + || (bytes[0] & 0xC4) != 0x00 // Bits 3, 7, and 8 must be 0 + || (bytes[1] & 0xC0) != 0x80 // Bits 7 must be 0, bit 8 must be 1 + || (bytes[2] & 0xE0) != 0x00 // Bits 6, 7, and 8 must be 0 + || (bytes[3] & 0x80) != 0x80 // Bit 8 must be 1 + ) { + return false; + } + + Serial.println(F("Looks like a KY-26 remote control protocol")); + + // Power mode + // It sends the same signal for on/off, it depends on the unit status. + if (bytes[0] & 0x08) { + Serial.println(F("On/Off action")); + } + + // Operating mode + Serial.print(F("MODE: ")); + switch (bytes[0] & 0x03) { + case 0x00: + Serial.println(F("AUTO")); + break; + case 0x01: + Serial.println(F("COOL")); + break; + case 0x03: + Serial.println(F("FAN")); + break; + case 0x02: + Serial.println(F("DRY")); + break; + } + + // Fan speed + switch (bytes[0] & 0x30) { + case 0x10: + Serial.println(F("FAN: 1")); + break; + case 0x20: + Serial.println(F("FAN: 2")); + break; + case 0x30: + Serial.println(F("FAN: 3")); + break; + } + + // Timer + // First 4 bits are hours, last 4 bits are half hours. + Serial.print(F("Timer: ")); + if (!(bytes[1] & 0x20)) { + Serial.println(F("OFF")); + } else { + Serial.print(bytes[1] & 0x0F); + if (bytes[1] & 0x10) { + Serial.print(F(".5")); + } + Serial.println(F("h")); + } + + // Temperature + Serial.print(F("Temperature: ")); + int temp = bytes[2] & 0x1F; + Serial.println(temp == 0 ? 15 : temp); // 0 is 15 degrees + + // CheckSum8 Modulo 256 + byte checksum = 0; + for (int i = 0; i < 3; i++) { + checksum += bytes[i]; + } + checksum = checksum % 256; + if (checksum != bytes[3]) { + Serial.print(F("Checksum error: ")); + Serial.print(checksum, HEX); + Serial.print(F(" != ")); + Serial.println(bytes[3], HEX); + } else { + Serial.println(F("Checksum matches")); + } + + return true; +} + +/* + x +Hh 00011000 00000001 00000001 11110101 h POWER + xx +Hh 00001000 00000001 11101000 11100101 h SPEED 1 +Hh 00000100 00000001 11101000 11101101 h SPEED 2 +Hh 00001100 00000001 11101000 11100011 h SPEED 3 + xx +Hh 00001000 00000001 11101000 11100101 h MODE 1 +Hh 10001000 00000001 11011000 00110101 h MODE 2 +Hh 11001000 00000001 11011000 01110101 h MODE 3 +Hh 01001000 00000001 10011000 11010101 h MODE 4 + xxxxxx +Hh 00001000 00000001 00000000 00001001 h TEMP 15 +Hh 00001000 00000001 00001000 00000101 h TEMP 16 +Hh 00001000 00000001 10001000 10000101 h TEMP 17 +Hh 00001000 00000001 01001000 01000101 h TEMP 18 +Hh 00001000 00000001 11001000 11000101 h TEMP 19 +Hh 00001000 00000001 00101000 00100101 h TEMP 20 +Hh 00001000 00000001 10101000 10100101 h TEMP 21 +Hh 00001000 00000001 01101000 01100101 h TEMP 22 +Hh 00001000 00000001 11101000 11100101 h TEMP 23 +Hh 00001000 00000001 00011000 00010101 h TEMP 24 +Hh 00001000 00000001 10011000 10010101 h TEMP 25 +Hh 00001000 00000001 01011000 01010101 h TEMP 26 +Hh 00001000 00000001 11011000 11010101 h TEMP 27 +Hh 00001000 00000001 00111000 00110101 h TEMP 28 +Hh 00001000 00000001 10111000 10110101 h TEMP 29 +Hh 00001000 00000001 01111000 01110101 h TEMP 30 +Hh 00001000 00000001 11111000 11110101 h TEMP 31 + xxxxxx +Hh 00001100 00000001 00000000 00001101 h Timer OFF +Hh 00001100 00001101 00000000 00000111 h Timer 0.5h +Hh 00001100 10000101 00000000 10001011 h Timer 1h +Hh 00001100 10001101 00000000 10000111 h Timer 1.5h +Hh 00001100 01000101 00000000 01001011 h Timer 2h +Hh 00001100 01001101 00000000 01000111 h Timer 2.5h +Hh 00001100 11000101 00000000 11001011 h Timer 3h +Hh 00001100 11001101 00000000 11000111 h Timer 3.5h +Hh 00001100 00100101 00000000 00101011 h Timer 4h +Hh 00001100 00101101 00000000 00100111 h Timer 4.5h +Hh 00001100 10100101 00000000 10101011 h Timer 5h +Hh 00001100 10101101 00000000 10100111 h Timer 5.5h +Hh 00001100 01100101 00000000 01101011 h Timer 6h +Hh 00001100 01101101 00000000 01100111 h Timer 6.5h +Hh 00001100 11100101 00000000 11101011 h Timer 7h +Hh 00001100 11101101 00000000 11100111 h Timer 7.5h +Hh 00001100 00010101 00000000 00011011 h Timer 8h +Hh 00001100 00011101 00000000 00010111 h Timer 8.5h +Hh 00001100 10010101 00000000 10011011 h Timer 9h +Hh 00001100 10011101 00000000 10010111 h Timer 9.5h +Hh 00001100 01010101 00000000 01011011 h Timer 10h +Hh 00001100 01011101 00000000 01010111 h Timer 10.5h +Hh 00001100 11010101 00000000 11011011 h Timer 11h +Hh 00001100 11011101 00000000 11010111 h Timer 11.5h +Hh 00001100 00110101 00000000 00111011 h Timer 12h +*/ \ No newline at end of file diff --git a/rawirdecode/rawirdecode.ino b/rawirdecode/rawirdecode.ino index fef2801..94229ad 100644 --- a/rawirdecode/rawirdecode.ino +++ b/rawirdecode/rawirdecode.ino @@ -22,6 +22,7 @@ #define AUX #define ZHLT01_REMOTE #define ZHJG01_REMOTE + #define KY26_REMOTE #else // low-memory device, // uncomment the define corresponding with your remote @@ -46,9 +47,10 @@ //#define AUX //#define ZHLT01_REMOTE //#define PHILCO + //#define KY26_REMOTE #endif -#if !defined(MITSUBISHI_ELECTRIC)&&!defined(FUJITSU)&&!defined(MITSUBISHI_HEAVY)&&!defined(DAIKIN)&&!defined(SHARP_)&&!defined(CARRIER)&&!defined(PANASONIC_CKP)&&!defined(PANASONIC_CS)&&!defined(HYUNDAI)&&!defined(GREE)&&!defined(GREE_YAC)&&!defined(FUEGO)&&!defined(TOSHIBA)&&!defined(NIBE)&&!defined(AIRWELL)&&!defined(HITACHI)&&!defined(SAMSUNG)&&!defined(BALLU)&&!defined(AUX)&&!defined(ZHLT01_REMOTE)&&!defined(ZHJG01_REMOTE)&&!defined(PHILCO) +#if !defined(MITSUBISHI_ELECTRIC)&&!defined(FUJITSU)&&!defined(MITSUBISHI_HEAVY)&&!defined(DAIKIN)&&!defined(SHARP_)&&!defined(CARRIER)&&!defined(PANASONIC_CKP)&&!defined(PANASONIC_CS)&&!defined(HYUNDAI)&&!defined(GREE)&&!defined(GREE_YAC)&&!defined(FUEGO)&&!defined(TOSHIBA)&&!defined(NIBE)&&!defined(AIRWELL)&&!defined(HITACHI)&&!defined(SAMSUNG)&&!defined(BALLU)&&!defined(AUX)&&!defined(ZHLT01_REMOTE)&&!defined(ZHJG01_REMOTE)&&!defined(PHILCO)&&!defined(KY26_REMOTE) #error You must uncomment at least one brand define!! #endif @@ -117,6 +119,9 @@ #if defined(PHILCO) bool decodePhilco(byte *bytes, int byteCount); #endif +#if defined(KY26_REMOTE) + bool decodeKY26Remote(byte *bytes, int byteCount); +#endif /* Raw IR decoder sketch! @@ -576,6 +581,9 @@ void decodeProtocols() #if defined(PHILCO) knownProtocol = decodePhilco(bytes, byteCount); #endif +#if defined(KY26_REMOTE) + knownProtocol = decodeKY26Remote(bytes, byteCount); +#endif if (!knownProtocol) { From cba879ebee3435d9a8424d4a57ea4a03805fc6a5 Mon Sep 17 00:00:00 2001 From: Mike Yin Date: Thu, 3 Apr 2025 23:49:00 -0600 Subject: [PATCH 94/94] Addition of the Olimpia Splendid Maestro Reverse engineered - can evaluate some of the captures here: https://gist.github.com/yincrash/4e687cbfda9d91f157eefe3b21966113 Product page: https://olimpiasplendidusa.com/maestro-pro-inverter-12-hp Changed the default ESP8266 pin to 2 (D4 on nodemcu-styleboards) Pin 5 is used for the i2c bus and can be occupied on boards with screens builtin. Added an additional comment to describe that by default we're treating the bitstream as LSB to MSB byte ordering. --- rawirdecode/OlimpiaMaestro.cpp | 106 +++++++++++++++++++++++++++++++++ rawirdecode/rawirdecode.ino | 12 +++- 2 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 rawirdecode/OlimpiaMaestro.cpp diff --git a/rawirdecode/OlimpiaMaestro.cpp b/rawirdecode/OlimpiaMaestro.cpp new file mode 100644 index 0000000..1fc68d0 --- /dev/null +++ b/rawirdecode/OlimpiaMaestro.cpp @@ -0,0 +1,106 @@ +#include + +// from Carrier.cpp +#define BYTETOBINARYPATTERN "%d%d%d%d%d%d%d%d" +#define BYTETOBINARY(byte) \ + (byte & 0x80 ? 1 : 0), \ + (byte & 0x40 ? 1 : 0), \ + (byte & 0x20 ? 1 : 0), \ + (byte & 0x10 ? 1 : 0), \ + (byte & 0x08 ? 1 : 0), \ + (byte & 0x04 ? 1 : 0), \ + (byte & 0x02 ? 1 : 0), \ + (byte & 0x01 ? 1 : 0) + +bool decodeOlimpiaMaestro(byte *bytes, int byteCount) +{ + // If this looks like an Olimpia code... + if (byteCount == 11 && bytes[0] == 0x5A) { + Serial.println(F("Looks like an Olimpia-Splendid Maestro protocol")); + + // Determine if the unit is off or in a specific mode (bits 8–10) + switch ((bytes[1] & 0x07)) { + case 0b001: + Serial.println(F("POWER ON")); + Serial.println(F("MODE COOL")); + break; + case 0b010: + Serial.println(F("POWER ON")); + Serial.println(F("MODE HEAT")); + break; + case 0b011: + Serial.println(F("POWER ON")); + Serial.println(F("MODE DRY")); + break; + case 0b100: + Serial.println(F("POWER ON")); + Serial.println(F("MODE FAN")); + break; + case 0b101: + Serial.println(F("POWER ON")); + Serial.println(F("MODE AUTO")); + break; + case 0b000: + Serial.println(F("POWER OFF")); + break; + default: + Serial.println(F("MODE UNKNOWN")); + } + + // Temperature (bits 72–76) + Serial.print(F("Temperature: ")); + if (bytes[9] & 0x20) { + uint8_t encodedTemp = ((bytes[9] & 0x1F)); + Serial.print(((((float) encodedTemp) / 2) + 15) * (1.8) + 32); + Serial.println(F("°F")); + } else { + uint8_t encodedTemp = ((bytes[9] & 0x1F)); + Serial.print((((float) encodedTemp) / 2) + 15); + Serial.println(F("°C")); + } + + // Fan speed + switch ((bytes[1] & 0x18) >> 3) { + case 0b00: + Serial.println(F("FAN: LOW")); + break; + case 0b01: + Serial.println(F("FAN: MEDIUM")); + break; + case 0b10: + Serial.println(F("FAN: HIGH")); + break; + case 0b11: + Serial.println(F("FAN: AUTO")); + break; + } + + // Other flags + Serial.print(F("FLAGS: ")); + if (bytes[1] & 0x04) Serial.print(F("FLAP SWING ")); + if (bytes[7] & 0x04) Serial.print(F("ECONO ")); + if (bytes[1] & 0x02) Serial.print(F("LOW NOISE ")); + Serial.println(); + + // Check if the checksum matches + byte checksum = 0x00; + + for (int x = 0; x < 10; x++) { + checksum += bytes[x]; + } + + if ( bytes[10] == checksum ) { + Serial.println(F("Checksum matches")); + } else { + Serial.print(F("Checksum does not match.\nCalculated | Received\n ")); + Serial.printf(BYTETOBINARYPATTERN, BYTETOBINARY(checksum)); + Serial.print(F(" | ")); + Serial.printf(BYTETOBINARYPATTERN, BYTETOBINARY(bytes[10])); + Serial.println(); + } + + return true; + } + + return false; +} diff --git a/rawirdecode/rawirdecode.ino b/rawirdecode/rawirdecode.ino index 178e84f..61009e1 100644 --- a/rawirdecode/rawirdecode.ino +++ b/rawirdecode/rawirdecode.ino @@ -10,6 +10,7 @@ #define PANASONIC_CKP #define PANASONIC_CS #define HYUNDAI + #define OLIMPIA // try model choice 3 #define GREE #define GREE_YAC #define FUEGO @@ -35,6 +36,7 @@ //#define PANASONIC_CKP //#define PANASONIC_CS //#define HYUNDAI + //#define OLIMPIA //#define GREE //#define GREE_YAC //#define FUEGO @@ -82,6 +84,9 @@ #if defined(HYUNDAI) bool decodeHyundai(byte *bytes, int pulseCount); #endif +#if defined(OLIMPIA) + bool decodeOlimpiaMaestro(byte *bytes, int pulseCount); +#endif #if defined(GREE) or defined(GREE_YAC) bool decodeGree(byte *bytes, int pulseCount); bool decodeGree_YAC(byte *bytes, int pulseCount); @@ -149,7 +154,7 @@ #elif defined(ESP32) #define IRpin 25 // G25 on M5STACK ATOM LITE #elif defined(ESP8266) - #define IRpin 5 + #define IRpin 2 #else #define IRpin_PIN PIND #define IRpin 2 @@ -457,7 +462,7 @@ void printPulses(void) { // Print the decoded bytes Serial.println(F("Bytes:")); - // Decode the string of bits to a byte array + // Decode the string of bits to a byte array from LSB first to MSB last for each byte for (uint16_t i = 0; i < currentpulse; i++) { if (symbols[i] == '0' || symbols[i] == '1') { @@ -569,6 +574,9 @@ void decodeProtocols() #if defined(HYUNDAI) knownProtocol = decodeHyundai(bytes, currentpulse); #endif +#if defined(OLIMPIA) + knownProtocol = decodeOlimpiaMaestro(bytes, byteCount); +#endif #if defined(GREE) or defined(GREE_YAC) knownProtocol = decodeGree(bytes, currentpulse) || decodeGree_YAC(bytes, currentpulse); #endif