-
Notifications
You must be signed in to change notification settings - Fork 835
Adding support for a new AC protocol
Here is a rough outline of the steps & actions required to add a new Air Conditioner protocol to the library.
Take a look at the Supported Protocols document. See if we've already documented support for your Air Conditioner/Heat Pump.
Obtain a IR Demodulator Hardware component, and build this circuit, and then compile and run the IRrecvDumpV2 example code on your ESP device. Monitor the serial output from the ESP module. The output should tell you what it knows about your protocol if it is supported, or list it as UNKNOWN
if it doesn't know what it is.
We will need that information later. Do some research to see if you can find any/all of the following:
- The operation manual.
- The specification for the IR protocol.
- Links to any other implementations and projects that support your air conditioner.
- Photos of the remote control etc.
See & read the FAQ on this topic to see/understand what you are asking/attempting.
This document assumes you've carried out all the steps to add basic support for the protocol. You'll most likely need to do that before you proceed or read further.
If you've added basic support (at least decodeBlah()
) successfully, and it is successfully recognised by the IRrecvDumpV2 example code etc, you will want to document all the settings on the A/C remote for each message you capture.
e.g. "Power button was pressed. Remote went from 'off' to 'on' and Cool mode, with Fan at auto, no fan swing, no filters were on, led display on the aircon was on, timers were all off/disabled, the clock on the remote was at '12:00', the temperature was 25C ..." etc for each message.
Add that text (please, no screen shots) description of every setting on the remote to each uint8_t state[] = {...};
or uint64_t data = 0x...;
you capture. I also suggest you keep a copy of the uint16_t rawData[] = {...};
lines as well until we are sure everything the state[]
/data
lines are perfect. The rawData
lines can be used by the library to faithfully reproduce the real message you got. The state[]
/data
lines are higher level translation.
Unlike simple IR remotes (think, TVs & Stereos etc.) Air Conditioners tend to not have discrete IR commands. e.g. There is no repeatable/re-playable standard "Volume Up" code like a TV has. Almost all of the time, it sends every setting on the remote in one message. You will be spending some time later to try to work out what parts of the message mean what.
It may seem like too much work at first, but you will wish you had done it soon if you don't do it early.
Start organising the data in a spreadsheet. Typically breaking down & storing the description of what the message is about, or what the user action was (e.g. Set the temp to 22C), and the state[]
/data
code associated with it.
It's often helpful to break up each state[]
/data
code into each byte (That's two Hexadecimal characters per byte e.g. 0x4F
is one byte) per cell. You will want to label the columns of bytes starting with the first byte column as 0
, then next 1
etc) This will help when referencing the data later. e.g. In C++, the first byte of the state[]
array is state[0]
.
Most AirCon config settings tends to be sole confined to a single byte, or half of the byte (called a Nibble). Doing the above will help make any setting changes stand out. You will want this for later.
There is no correct or best way to construct your spreadsheet, but here are some examples. Set it up which ever way suits you initially to help yourself decode it. I will strongly suggest you use Google Sheets or some similar service that allows you to make a spreadsheet public and is always up to date. Excel or OpenOffice is fall back, but remember not everyone has this software. A web-based service allows pretty much anyone to access the document read-only and no-one has to keep downloading the latest version etc.
When it's ready, remember to share it read-only to the entire world. If you can, enable comments so people can ask questions and/or provide feedback. When the protocol is all finished, please don't delete the document. That way people can always reference back to it if needed.
Report all the information you've collected in a new issue. If I've got all that information, I can fairly quickly verify that the basic support for your protocol looks good, tweak it if needed, or ask you for more information.
Once we have a working decode
routine. You should no longer need to record the full output of IRrecvDumpV2
. You only need the uint64_t data = ...;
or the uint8_t state[] = {...};
lines to fully detail what the message is. i.e. The protocol number/type (e.g. 70
or DAIKIN152
) and the data/state lines should be all anyone needs to reproduce the message. We no longer need to care about the micro-details of timings if it is successfully detected. If it is still coming up as UNKNOWN
then, yes, collecting the raw data is necessary to help get better matching and timing information.
Most AirCon protocols have two distinct formats which significantly affect the hex code for the protocol. That is, if the code is transmitted in Least Significant Bit First (LSB or LSBF), or Most Significant Bit First (MSB or MSBF) bit order. To try work out which order your protocol uses, we use a fairly simple process.
- Set the remote to what ever modes produce the most about of zeros in the hex code as currently captured. e.g. Clock to "00:00", Auto modes, or Cool modes typically.
- Capture & Record the the ENTIRE temperature range starting from the lowest temperature the remote/aircon can do, and step up every smallest temperature increment you can until you reach the highest temp.
- Try to find which part of the hex code changes when you do this. Typically the last byte (the last two hexidecimal numbers) are often the checksum for the message. It is often safe to ignore those for the moment for now.
- Look to see how the numbers change with each temperature increment. If they are linear with the temperature (e.g. the increase by 1 for each degree etc) then we've probably got the bit ordering of the protocol correct. It's a 50/50 chance. If not, odds are we need to swap the bit ordering in the code.
- Once we've confirmed we have the bit ordering correct, only then should you proceed to trying to work out what bits and bytes control which aspects of the AirCon.
Lets say you capture the temperature range for a fictional simplified A/C by "Cranky Air". You use IRrecvDumpV2
and collect temperature settings for 16C to 20C.
Note: We are only doing 16 to 20 here for brevity, you should do the ENTIRE temperature range.
Settings Desc. | state[] |
---|---|
Auto Mode, 16C | 0x80, 0x00, 0x10, 0x23, 0x32 |
Auto Mode, 17C | 0x80, 0x00, 0x20, 0x23, 0x42 |
Auto Mode, 18C | 0x80, 0x00, 0x30, 0x23, 0x52 |
Auto Mode, 19C | 0x80, 0x00, 0x40, 0x23, 0x62 |
Auto Mode, 20C | 0x80, 0x00, 0x50, 0x23, 0x72 |
If you look at the above data, you start to notice that only two columns are changing. e.g.
Settings Desc. | state[] |
---|---|
Auto Mode, 16C | 0x80, 0x00, 0x10, 0x23, 0x32 |
Auto Mode, 17C | 0x80, 0x00, 0x20, 0x23, 0x42 |
Auto Mode, 18C | 0x80, 0x00, 0x30, 0x23, 0x52 |
Auto Mode, 19C | 0x80, 0x00, 0x40, 0x23, 0x62 |
Auto Mode, 20C | 0x80, 0x00, 0x50, 0x23, 0x72 |
In this case, it's kind of obvious that both columns are increasing by one (in a manner, technically by 16 but lets skip that technicality for now). This indicates we've probably got the bit order correct, and we've narrowed down the byte that controls the temperature to either state[2]
or state[4]
.
If the bit order was wrong (reversed) the data might look like this:
Settings Desc. | state[] with reversed bits |
---|---|
Auto Mode, 16C | 0x01, 0x00, 0x08, 0x34, 0x4C |
Auto Mode, 17C | 0x01, 0x00, 0x04, 0x34, 0x42 |
Auto Mode, 18C | 0x01, 0x00, 0x0C, 0x34, 0x4A |
Auto Mode, 19C | 0x01, 0x00, 0x02, 0x34, 0x46 |
Auto Mode, 20C | 0x01, 0x00, 0x0A, 0x34, 0x4E |
i.e. Nothing obvious in a linear sequence. Numbers seem to jump around etc.
If that is the data pattern you are seeing, then the bit order is probably wrong. Try reversing the binary values. e.g. 0b01010000
-> 0b00001010
So lets go back to the second table. We have two columns that are changing in lock step with each other. Either could be the byte responsible for controlling the temperature.
Often A/Cs have check value on their data. This often shows itself like this example. If a value changes, the checksum will change too. Often the check value is the last byte in the message sequence. So, it's most likely that temperature is controlled by state[2]
.
Let's try to confirm our new hypothesis, by changing some other part of the message/remote settings. Let's change operation modes, but keep the temperature setting constant.
Settings Desc. | state[] |
---|---|
Auto Mode, 16C | 0x80, 0x00, 0x10, 0x23, 0x32 |
Cool Mode, 16C | 0x81, 0x00, 0x10, 0x23, 0x33 |
Heat Mode, 16C | 0x82, 0x00, 0x10, 0x23, 0x34 |
Fan Mode, 16C | 0x84, 0x00, 0x10, 0x23, 0x36 |
Dry Mode, 16C | 0x88, 0x00, 0x10, 0x23, 0x3D |
We look at the data, and this time we notice again, that only two columns changed. state[0]
& state[4]
. e.g.
Settings Desc. | state[] |
---|---|
Auto Mode, 16C | 0x80, 0x00, 0x10, 0x23, 0x32 |
Cool Mode, 16C | 0x81, 0x00, 0x10, 0x23, 0x33 |
Heat Mode, 16C | 0x82, 0x00, 0x10, 0x23, 0x34 |
Fan Mode, 16C | 0x84, 0x00, 0x10, 0x23, 0x36 |
Dry Mode, 16C | 0x88, 0x00, 0x10, 0x23, 0x3D |
This new data gives us a new perspective on working out what bytes control what. As we noted earlier, state[4]
changed in the earlier temperature data capture too, and state[2]
does not change in these captures. As we didn't change the temperature, we can fairly confident that state[4]
is the check value, state[2]
is the byte that controls the temperature. Additionally we can make a good guess that state[0]
is most likely the byte that controls the operating mode of the A/C.
We can also make an even better guess that the lower half (nibble) of state[0]
is what controls the mode. i.e.
Auto = 0, Cool = 1, Heat = 2, Fan = 4, & Dry = 8.
As an exercise, look at those values in binary. You might be able to work out how/why the A/C is using those values to control the mode.
This is where things get hard, and you have to a lot of analysis work. I'll break things up in to sections to attack but you don't have to do them in any particular order.
The most important features you need to target are:
-
Power control
i.e. Turning the unit On and Off. See: Binary Settings.
-
Mode control
i.e. Cool, Heat, Fan Only, Auto, Dry etc. See: Value ranges
-
Temperature control
Which you should probably have worked out by now. See earlier. See: Value ranges
-
Fan Speed control
How fast the fan is to run. See: Value ranges
& potentially, the hardest to decode:
-
The message checksum(s)
Often Air Conditioner remotes are long and to make sure the message hasn't been corrupted they have some verification code built into the message. If a/c messages have a checksum(s), then working out how to calculate it will be required to be able to dynamically create a new valid A/C message. Thus, it's a requirement if you want the library to be able to create and send a new A/C message. It isn't required to just decode a message.
These are A/C settings that are typically controlled by a single "bit" in the hex code. e.g. An option that can only be "on" or "off". For instance, "Health", "Ion Filter", or "Turbo" settings are often a Binary Settings controlled by a single bit in the message. "Power" is often a binary setting too, but some A/Cs have special messages that either toggle the A/C unit from off to on and on to off with the exact same bit in a message, or even have an entirely different and unique message for controlling this.
The best way to detect these is to try to have the remote produce a code that has the least number of bits set to 1
(on). e.g. a 0
bit is typically off, or the default for a setting. The more 0
s in a code the better for making these settings stand out easier.
In your spreadsheet, you'll find that a certain byte/nibble will always alternate when you change the setting from on & off etc. That column may also change when other settings change too. Beware of that. This is because a binary setting can be just one bit out of the possible 8 in a byte (or 4 in a nibble). The other bits may be used for other settings.
It's often useful to use a program or web page that will help you convert from Hex to Binary. This will allow you to see which bit changes from 1
to 0
and back to 1
etc.
For example, let's say that state[5]
changes when we toggle the power setting. It changes from 0x8A
(on) to 0x0A
(off). If you convert those hex values to binary, you'd get 0b10001010
(on) and 0b00001010
(off). You can now more easily see it's the left most bit (highest bit, or Most Significant Bit) that changes. Thus we can assume that the power setting is controlled by the 0b10000000
bit of state[5]
.
These are A/C settings that are typically controlled by a group of "bits" in the hex code. e.g. A setting that has more than just "on" or "off". For instance, "Mode", "Temperature", or "Fan Speed" settings are often a Range Settings controlled by a cluster of bits in the message. They can vary from only two bits to multiple bytes in size. The size is at a minimum, determined by the amount of information needed to be stored. e.g. Often there are 5+ operation modes (Cool, Heat, Fan Only, Auto, Dry etc.) 5 in binary is 0b101
. Thus it need at least 3 bits to store the information. Often, due to who designed the protocol, they may use more bits than the absolute minimum for what ever reasons they saw fit at the time. Often if a range is only 3 bits long (using the current example) they will store it in it's own distinct nibble (the upper or lower 4 bits of a byte).
e.g. Let's say your spreadsheet shows that state[3]
changes when you change operation mode on the remote.
0x20
for Auto, 0x21
for Cool, 0x22
for Heat, etc up to 0x25
for Dry. Only the last half of the byte (or nibble) is changing. If we do the same trick of looking at it in binary notation per Binary Settings, you get 0b0010000
for Auto, 0b0010001
for Cool, 0b0010010
for Heat, etc up to 0b0010101
for Dry. As you can see, only the last three bits ever change when the mode changes. We could say that the a/c's mode is controlled by the last three (or lowest 3, or the 3 Least Significant) bits of state[3]
. Or we can say 0b00000111
in binary (0x07
in hex) are the controls (or masks) for the operating mode.
Temperatures often have a range of a minimum of 16C to a maximum of 31C. Sometimes the manufacture only stores the difference from the minimum temp in the message. i.e. 31 - 16 = 15. 15 in binary is 0b1111
, thus fitting in a nibble, where sometimes they store the whole number. i.e. 31 is 0b11111
, 5 bits. When it extends over a nibble, they tend to dedicate an entire byte to it.
A/C messages tend to be quite long. With that length comes the possibility of message corruption. People would be unhappy if a single bit got flipped in their commands, and instead of running in Cool mode, it accidentally ran in Hot. Or instead of 21 Celsius, it went with 31 Celsius. All and any of those are possible with a single incorrect bit. Hence manufacturers tend to include some way of telling if the message is likely to be correct before accepting the message. It's called an integrity check. This requirement often is the one of the biggest obstacles to overcome in order for the library to be able to produce a message that the A/C unit will accept. With out it passing the check, we can twiddle the temperature, mode and power settings all we like, and the device will just ignore it. The challenge of course, is to work out what form of integrity check is used and how to calculate it.
The most common form is a checksum byte. When you've been analysing the message hex-code structure, you've probably noticed for every change, two different bytes change. Even for Binary Settings which in theory, are often controlled by only a single bit the message. As you go through decoding the parts of the message, you'll often find one byte always, regardless if it's mode, temperature, fan speed etc. That is likely the checksum byte. It is often the very last byte of the message. Sometimes, if the message structure has multiple data sections to it, it may be at the end of each sections.
There are many ways this value can be calculated. In order of most likely to least likely:
The total of which is trimmed to fit inside a byte (or a nibble). If the whole last byte seems to change, it's often _the sum of all the previous bytes of the section or message, modulo 256, or written in most common computer languages % 256
.
Lets say there are ten bytes in a message: state[10] = {0x80, 0x00, 0x00, 0x12, 0x00, 0xBE, 0x00, 0x7C, 0x00, 0xCC};
Adding up the first nine bytes:
0x80 + 0x00 + 0x00 + 0x12 + 0x00 + 0xBE + 0x00 + 0x7C + 0x00 = 0x1CC (total) % 256 = 0xCC
. As the last byte is also 0xCC
, it's likely that's the algorithm used. Sometimes, it's not an entire byte dedicated to the checksum. It may be just a nibble. i.e. Only one of the Hex-characters in the last byte changes, not the entire byte. For that we would use modulo 16, or % 16
. Using the example just provided it would be the same total, just 0x1CC % 16
= 0xC
.
"A sum of the preceding ..." are fairly easy to identify. The checksum value tends to increase by the same amount as a single data byte does earlier in the message. e.g. If when you look at the temp values you collected earlier to help determine the bit ordering of the message, you look at the temp byte. If the data value went from 0x11
to 0x12` when you increased the temperature by 1 degree, the checksum should also increase by 1 too.
Just like above, instead of adding whole bytes together, each 4-bit nibble is added together and typically mod 16'ed. ( % 16
).
e.g.
0x8 + 0x0 + 0x0 + 0x0 + 0x0 + 0x0 + 0x1 + 0x2 + 0x0 + 0x0 + 0xB+ 0xE + 0x0 + 0x0 + 0x7 + 0xC + 0x0 + 0x0 = 0x37
0x37 % 16 = 0x7
, so we would expect a value of 0x7
somewhere in the checksum byte.
Note: With this approach, sometimes the other half of the checksum byte is also included in the total, so try that too.
Try the above two methods, but check if the result is always off by some value. Sometimes they include some hidden constant value to the calculation. e.g. If your result is always off by say 7. Then this might be the culprit.
4. Exclusive Or (XOR) byte or nibble.
Similar to the "sum of" possibilities above, instead of addition (+
), the data is xor'ed (^
) together into the last byte or nibble.
Lets say there are five bytes in a message: state[5] = {0x83, 0x7F, 0x12, 0x00, 0xEE};
The math for that is the first four byte 0x83 ^ 0x7F ^ 0x12 ^ 0x00 = 0xEE
. It matches the last byte, we probably have a winner.
The similar approach applies for nibbles as like the "sum of"s earlier.
e.g. 0x8 ^ 0x3 ^ 0x7 ^ 0xF ^ 0x1 ^ 0x2 ^ 0x0 ^ 0x0 ^ 0xE = 0xE
XOR check values are often noticeable by how widely the check value changes. It tends to jump all over the place, not in step with an increment to a data byte value. If the only change in the data is single bit (i.e. A Binary Settings), then the corresponding bit should also flip in the check value. e.g. if the top bit of a byte/nibble changes from 0 to 1, then the top bit of the check value should change form a 1 to a 0.
Now we are getting into the rare/uncommon methods. Like the earlier "sum of"s, but just the lower or upper half of a byte.
Some protocols have bytes which are deliberately skipped in the calculations. Some ofthem have a series of calculations based on the lower nibbles of bytes, and then for the upper nibbles for other bytes. Shrug. It can get plain weird.
The Samsung A/C protocol calculated it's integrity check value by counting all the 1
bits in all the preceding bytes.
e.g. {0x12, 0x34, 0x56}
converted to binary {0b00010010, 0b00110100, 0x01010110}
which means 2 x 1's + 3 x 1's + 4 x 1's == 9
, thus a check value of 9
Some devices opt for a simpler approach, or even in combination to Check Bytes.
The listening device records the first message, and immediately waits to hear the exact same message again with in a certain short time period. If it doesn't get it, it ignores the message entirely.
This is often characterised by having a complete duplication in the series of bytes in the state[]
value. This can often be solved by just setting the repeat
argument to 1
in your sendBlah()
routine's proto.
Some integrity checks are not limited to a single byte. They replay the same (or part of the) message but flip all the data bits in the whole (or part) of the message.
e.g. state[4] = {0x12, 0xF7, 0xED, 0x08};
It's really on a message of {0x12, 0xF7}
with the other half inverted. The symbol for inverting in C++ is ~
.
That is: 0x12 == ~0xED
and 0xF7 == ~0x08
; or in binary 0b00010010 == ~0b11101101
and 0b11110111 == ~0b00001000
.
i.e. 1
s become 0
s and 0
s become 1
s.
Like previously, sometimes it's the whole byte, some times it done at the level of a nibble.
This can often be best spotted that there is always two bytes that change for every setting, and neither of those bytes is not the last byte of the message or section.
Similar to the previous type, instead of a sequence sent normally, and then sent inverted, it's done at the per byte level. Using the same data as the last type: state[4] = {0x12, 0xED, 0xF7, 0x08};
where again: 0x12 == ~0xED
and 0xF7 == ~0x08
Working these integrity values is often the hardest part of getting a protocol supported.
Basically, you're finished when you can explain for every bit that changes between messages, what every bit does or controls, or why it changes. There are often bits/parts of the message that never change, you don't need to be able to explain those. Just the bits that can change.
Remember, you don't need to document every feature. Most people don't need the clock, or the timer functions, or the special mode that allows the remote to tell the A/C unit what the current temperature is where it is etc. Stick to the basics of Power, Mode, Temp, & Fan Speed first; Oh and the dreaded checksum if you ever want to be able create your own messages. You can always add/decode the other features later.
Once you can do that, you're pretty much done with the analysis phase and we can move on to coding it all up somehow.
It comes down to mostly two options:
If you've done all the steps in this and related documents, and it works, then create an new Issue, and put all the relevant information in to it. @crankyoldgit will eventually get around to adding it, and asking you for more data, and heaps of questions etc. It could take days, it could take weeks. It depends on a lot of factors, and how good your data and documentation are.
There are plenty of examples in the library to crib & copy from to get an idea of how to do it. There is always help available via logging an Issue if you need help.