crankyoldgit / IRremoteESP8266

Infrared remote library for ESP8266/ESP32: send and receive infrared signals with multiple protocols. Based on: https://github.com/shirriff/Arduino-IRremote/
GNU Lesser General Public License v2.1
2.98k stars 832 forks source link

Could you add TCL112AC protocol? #619

Closed soosp closed 5 years ago

soosp commented 5 years ago

There is an protocol driver for original IRremote library: https://github.com/z3t0/Arduino-IRremote/pull/259/files Is this possible to implement it?

crankyoldgit commented 5 years ago

Yes, it can sort-of be added but the code you linked to doesn't/won't actually work. I could see some bugs with the linked code within a minute or two of looking at it. It also appears it was never merged into the IRremote library. Possibly for those reasons.

I can implement what I think it is trying to do. Do you have a Raw Data capture from IRrecvDumpV2 that you can share? That would help immensely.

soosp commented 5 years ago

I have a TCL manufactured AC unit, and assumed that the TCL112AC protocol works with it. I can dump the IR codes of my remote if it helps.

crankyoldgit commented 5 years ago

Yes, it will help, and ultimately, it's what you want to emulate I'm guessing.

soosp commented 5 years ago

Here are the captured data files. I tried to change only one thing in a file (e.g. change the temperature only). Most functions captured in cooling mode, and at 24 C, and with automatic fan settings.

OnOff.txt Temperatures, Cooling, Auto fan.txt Modes.txt Fan levels, Cooling, 24 C.txt VSwing, Cooling, 24 C, Auto fan.txt Display, Cooling, 24 C, Auto fan.txt Eco, Cooling, 24 C, Auto Fan.txt Health, Cooling, 24 C, Auto fan.txt HSwing, Cooling, 24 C, Auto fan.txt Turbo, Cooling, 24 C, Auto fan.txt

crankyoldgit commented 5 years ago

Great. Thanks for that data. It's very useful.

Here is a breakdown of the "on" message from the first file.

IRremoteESP8266/tools$ ./auto_analyse_raw_data.py --code -f /tmp/on.txt 
Found 227 timing entries.
Potential Mark Candidates:
[3030, 524]
Potential Space Candidates:
[1658, 1074, 336]

Guessing encoding type:
Looks like it uses space encoding. Yay!

Guessing key value:
kHdrMark   = 3030
kHdrSpace  = 1658
kBitMark   = 495
kOneSpace  = 1065
kZeroSpace = 324

Decoding protocol based on analysis so far:

kHdrMark+kHdrSpace+1100010011010011011001001000000000000000001001001100000011100000000000100000000000000000000000000000000111000000
  Bits: 112
  Hex:  0xC4D364800024C0E00200000001C0 (MSB first)
        0x038000000040070324000126CB23 (LSB first)
  Dec:  3992100527850402204402608498540992 (MSB first)
        70988433613961943391698424351523 (LSB first)
  Bin:  0b1100010011010011011001001000000000000000001001001100000011100000000000100000000000000000000000000000000111000000 (MSB first)
        0b0000001110000000000000000000000000000000010000000000011100000011001001000000000000000001001001101100101100100011 (LSB first)

Total Nr. of suspected bits: 112

Generating a VERY rough code outline:

// WARNING: This probably isn't directly usable. It's a guide only.
const uint16_t kHdrMark = 3030;
const uint16_t kBitMark = 495;
const uint16_t kHdrSpace = 1658;
const uint16_t kOneSpace = 1065;
const uint16_t kZeroSpace = 324;
const uint16_t kXyzBits = 112;
const uint16_t kXyzStateLength = 14;
// DANGER: More than 64 bits detected. A uint64_t for 'data' won't work!
// Function should be safe up to 64 bits.
void IRsend::sendXyz(const uint64_t data, const uint16_t nbits, const uint16_t repeat) {
  enableIROut(38);  // A guess. Most common frequency.
  for (uint16_t r = 0; r <= repeat; r++) {
    // Header
    mark(kHdrMark);
    space(kHdrSpace);
    // Data
    // e.g. data = 0xC4D364800024C0E00200000001C0, nbits = 112
    sendData(kBitMark, kOneSpace, kBitMark, kZeroSpace, data, nbits, true);
    // Footer
    mark(kBitMark);
    space(100000);  // A 100% made up guess of the gap between messages.
  }
}

// Alternative >64 bit Function
void IRsend::sendXyz(uint8_t data[], uint16_t nbytes, uint16_t repeat) {
  // nbytes should typically be kXyzStateLength
  // data should typically be:
  //   uint8_t data[kXyzStateLength] = {0xC4, 0xD3, 0x64, 0x80, 0x00, 0x24, 0xC0, 0xE0, 0x02, 0x00, 0x00, 0x00, 0x01, 0xC0};
  // data[] is assumed to be in MSB order for this code.
  for (uint16_t r = 0; r <= repeat; r++) {
    sendGeneric(kHdrMark, kHdrSpace,
                kBitMark, kOneSpace,
                kBitMark, kZeroSpace,
                kBitMark,
                100000, // 100% made-up guess at the message gap.
                data, nbytes,
                38000, // Complete guess of the modulation frequency.
                true, 0, 50);
  }
}

It looks like it should be easy enough to get the basic protocol supported. i.e. sending/capturing the 112 bit/14 byte message, but not decoding what they mean.

The values collected seem to match up with what was in the PR you linked to. I'll try to knock up some experimental code for you to try out soon.

crankyoldgit commented 5 years ago

@soosp Can you please test out the code in the tcl branch of the library? e.g. https://github.com/markszabo/IRremoteESP8266/tree/tcl

Let me know how it goes.

crankyoldgit commented 5 years ago

@soosp I've now added more decoding/support for the TCL protocol (power, mode, & checksum).

Per FAQ/similar issues, you'll have to do the analysis work to work out which bits in each state byte control which functions for the remaining functions. i.e. When you change something on the remote, you need to be able to account for every bit/byte that changes. When you've worked that out, let me know.

Otherwise I'll merge what we have and leave it at that. i.e. I won't be doing more message breakdown, it's up to you/someone else to work it out.

You should only need to report/record the state[] lines for each mode etc in your analysis if you use the code in the tcl branch. Let me know if you have any issues with that code.

soosp commented 5 years ago

Thanks for your efforts! Currently I'm very busy, but I'll try to test your code in the next days.

soosp commented 5 years ago

I made a quick test with code below, and it works well. I'll try to figure out remaining functions soon.

#ifndef UNIT_TEST
#include <Arduino.h>
#endif
#include <IRremoteESP8266.h>
#include <IRsend.h>
#include <ir_Tcl.h>

const uint16_t kIrLed = 4;  // ESP8266 GPIO pin to use. Recommended: 4 (D2).
IRTcl112Ac ac(kIrLed);  // Set the GPIO to be used for sending messages.

void setup() {
  ac.begin();
  Serial.begin(115200);
}

void loop() {
  Serial.println("Sending...");

  // Set up what we want to send. See ir_Tcl.cpp for all the options.
  ac.setPower(true);
  ac.setMode(kTcl112AcCool);
  ac.setTemp(24);

  // Now send the IR signal.
#if SEND_TCL112AC
  ac.send();
#else  // SEND_TCL112AC
  Serial.println("Can't send because SEND_TCL112AC has been disabled.");
#endif  // SEND_TCL112AC

  delay(5000);
}
crankyoldgit commented 5 years ago

Thanks for the confirmation!

soosp commented 5 years ago

You should only need to report/record the state[] lines for each mode etc in your analysis if you use the code in the tcl branch.

Do you mean values in these files? I copied only uint8_t state[14] lines from the output of IRrecvDumpV2.

Display.Cooling.24.C.Auto.fan.txt Eco.Cooling.24.C.Auto.Fan.txt Fan.levels.Cooling.24.C.txt Health.Cooling.24.C.Auto.fan.txt HSwing.Cooling.24.C.Auto.fan.txt Modes.txt OnOff.txt Temperatures.Cooling.Auto.fan.txt Turbo.Cooling.24.C.Auto.fan.txt VSwing.Cooling.24.C.Auto.fan.txt

soosp commented 5 years ago

Meanwhile the temperature values was tested succesfully with the code below.


#ifndef UNIT_TEST
#include <Arduino.h>
#endif
#include <IRremoteESP8266.h>
#include <IRsend.h>
#include <ir_Tcl.h>

const uint16_t kIrLed = D6;  // ESP8266 GPIO pin to use. Recommended: 4 (D2).
IRTcl112Ac ac(kIrLed);  // Set the GPIO to be used for sending messages.

void setup() {
  ac.begin();
  Serial.begin(115200);
}

void loop() {
  float temp;
  Serial.println("Sending...");

  // Walk over all supported temperatures
  for(temp = 16; temp <= 31; temp +=0.5) { 
    ac.setPower(true);
    ac.setMode(kTcl112AcCool);
    ac.setTemp(temp);
    Serial.print("Temperature: ");
    Serial.print(temp, 1);
    Serial.println(" C");

    // Now send the IR signal.
#if SEND_TCL112AC
    ac.send();
#else  // SEND_TCL112AC
    Serial.println("Can't send because SEND_TCL112AC has been disabled.");
#endif  // SEND_TCL112AC

    delay(3000);
  }

  // Tur off and wait for 15s
  ac.setPower(false);
  ac.send();
  delay(15000);
}
crankyoldgit commented 5 years ago

Do you mean values in these files? I copied only uint8_t state[14] lines from the output of IRrecvDumpV2.

Yes.

To work out what a function/button does, you basically need to explain every bit-flip that happens between messages. e.g. in Display.Cooling.24.C.Auto.fan.txt The byte in state[5] changes from 0x24 (on) to 0x64 (off). If you look at that in binary 0x24 = 0b00100100 and 0x64 = 0b01100100 Hence, that single bit is what controls what is Display On and Display Off.

The last byte of the state array (i.e. state[13]) is the checksum byte, that will change when some other part of the message changes, so you can ignore the last byte of the message when you are working out what changed etc.

Let us know what you work out from your data.

crankyoldgit commented 5 years ago

FYI, v2.5.6 has just been release which includes the changes/improvements mentioned.

soosp commented 5 years ago

Thanks for the apprise! Meanwhile I identified some bits in the protocol:

Display state:

state[5]
bits: 0b01000000
mask: 0b10111111 = 0xBF

on: 0b00000000 = 0x00
off: 0b01000000 = 0x40

Eco mode:

state[5]
bits: 0b10000000
mask: 0b01111111 = 0x7F

on: 0b10000000 = 0x80
off: 0b00000000 = 0x00

Fan mode:

state[8]
bits: 0b00000111
mask: 0b11111000 = 0xF8

auto: 0b00000000 = 0x00
min: 0b00000010 = 0x02
middle: 0b000000011 = 0x03
max: 0b000000101 = 0x05

Heath mode:

state[6]
bits: 0b00010000
mask: 0b11101111 = 0xEF

on: 0b00010000 = 0x10
off: 0b00000000 = 0x00

Horizontal swing:

state[12]
bits: 0b00001000
mask: 0b11110111 = 0xF7

on: 0b00001000 = 0x08
off: 0b00000000 = 0x00

Modes:

state[6]
bits: 0b00001111
mask: 0b11110000 = 0xF0

auto: 0b00001000 = 0x08
cooling: 0b00000011 = 0x03
drying: 0b00000010 = 0x02
ventillating: 0b00000111 = 0x07
heating: 0b00000001 = 0x01

Note: on ventillating mode the remote controller turns the fan to max state too: state[8]: 0x05 with mask 0xF8

On/off:

state[5]
bits: 0b00001000
mask: 0b11110111 = 0xF7

on: 0b00001000 = 0x04
off: 0b00000000 = 0x00

Turbo mode:

state[6]
bits: 0b01000000
mask: 0b10111111 = 0xBF

on: 0b01000000 = 0x40
off: 0b00000000 = 0x00

Note: It seems that the remote controller sets the temperature to the end value (state[7] and state[12]) and turns fan to max value and turns on the vertical swing (state[8] for both). It has been tested in cooling mode only, but needs testing in other modes.

Vertical swing:

state[8]
bits: 0b00111000
mask: 0b11000111 = 0xC7

on: 0b00111000 = 0x38
off: 0b00000000 = 0x00

Temperature: state[7] and state[12] as you investigated yet.

crankyoldgit commented 5 years ago

That's excellent work. Thanks. I'll try to add support for most of that in the next few days.

crankyoldgit commented 5 years ago

On/off:

state[5]
bits: 0b00001000
mask: 0b11110111 = 0xF7

on: 0b00001000 = 0x04
off: 0b00000000 = 0x00

Are you sure on that one? It differs from what I originally had. Just double checking before I change it.

soosp commented 5 years ago

Strange, because your code worked for me. It turned on/off my AC unit.

I tested the remote controller in some different modes, and seems that the only difference between the on/off command sequences the bit 3 of the state[5]:

ON
uint8_t state[14] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x24, 0x03, 0x07, 0x40, 0x00, 0x00, 0x00, 0x80, 0x03};
OFF
uint8_t state[14] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x03, 0x07, 0x40, 0x00, 0x00, 0x00, 0x80, 0xFF};

ON
uint8_t state[14] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x24, 0x02, 0x07, 0x40, 0x00, 0x00, 0x00, 0x80, 0x02};
OFF
uint8_t state[14] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x02, 0x07, 0x40, 0x00, 0x00, 0x00, 0x80, 0xFE};

ON
uint8_t state[14] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x24, 0x07, 0x07, 0x45, 0x00, 0x00, 0x00, 0x80, 0x0C};
OFF
uint8_t state[14] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x07, 0x07, 0x45, 0x00, 0x00, 0x00, 0x80, 0x08};
soosp commented 5 years ago

It seems that IRecvDumpV2 correctly identify the on/off state:

Timestamp : 000694.257
Encoding  : TCL112AC
Code      : 23CB260100240107400000000081 (112 bits)
Mesg Desc.: Power: On, Mode: 1 (HEAT), Temp: 24C
[...]
uint8_t state[14] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x24, 0x01, 0x07, 0x40, 0x00, 0x00, 0x00, 0x00, 0x81};

Timestamp : 000745.771
Encoding  : TCL112AC
Code      : 23CB26010020010740000000007D (112 bits)
Mesg Desc.: Power: Off, Mode: 1 (HEAT), Temp: 24C
[...]
uint8_t state[14] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x01, 0x07, 0x40, 0x00, 0x00, 0x00, 0x00, 0x7D};
crankyoldgit commented 5 years ago

On/off:

state[5]
bits: 0b00001000
mask: 0b11110111 = 0xF7

on: 0b00001000 = 0x04
off: 0b00000000 = 0x00

Okay, I think that it's just your hex/bit maths that is wrong then for this one case.

The diff in state[5] is as you & I indicated. ie. 0x04 ... which is 0b00000100, not your above 0b00001000 which is 0x08. Thus this threw out your bits/mask values.

If you want to try out the code in this branch: https://github.com/markszabo/IRremoteESP8266/tree/tcl_update it should support what you've listed/analysed thus far.

soosp commented 5 years ago

Oh, it's my mistake. Thank you for correction. I'll test your code soon.

crankyoldgit commented 5 years ago

@soops I've now merged those changes into the master branch. I think that covers everything you reverse-engineered. I'm going to close this issue for now. If there is something wrong with the new code, please update this issue and I'll re-open if it's within a week or so. Anything longer, please open a new issue.

Thanks for you effort.

crankyoldgit commented 5 years ago

FYI, this has been included in the newly released v2.6.0 of the library.