0x00 Intro

While doing my Home Automation I decide I will like to have control from it over the IR controlled devices.
I know there are quite a few solutions out there for that, but where is the fun using those?

In this article we will focus only on Toshiba AC.
You probably know this, but in case you don't. There is a difference between the normal (TV,VCR,TiVo)
remotes and the ones for AC. The "normal" ones send a short command. While AC remotes send all of
their setting every time. Even if you change just the temperature for example ,it will still send
fan speed, mode, and so on. Oh ...and you should expec checksum at the end of the packet.

0x01 Reading data

So ...let say you already have your IR reciver setup, now you do
mode2 -d /dev/lirc0 you go press some stuff on the remote and your terminal spits something like:

space 16717235
pulse 7704181
space 4357
pulse 562
space 1587
pulse 560
space 1611
pulse 558

0x02 Converting pulse/space to bin/hex

Ignore any values above 15000, you should see few at the begining.
After that you see just pulses and spaces ,and how long they ware.
We don't really care about the pulses, we only care about the spaces.

You may need to adjust this value, but for me 1000 seems to be a good threshold for separation.
Hm.. okay , lets dump one power on and one power off and compare them.

A lot of data.... or at least it looks that way.
Lets "translate" this to binary. Few lines of python should do the trick.

result = ''
f = open('logfile', 'r')

for line in f.readlines():
    if 'space' in line:
        s = int(line.split()[-1])
        if s > 1000:
            result += '1'
        else:
            result += '0'

f.close()

print("%s %s" % (len(result), result))

0x03 Looking for patterns

Okay, now we can see the repeating pattern and the different part.
Lets now make this into hex to see if that will help us.
Again small python script to all the work for us.

#!/usr/bin/env python

import os
import sys


def die(message, exit_code=0):
    print(message)
    sys.exit(exit_code)


def check_line(line):
    value = int(line.split()[-1])
    if value > 1000:
        return 1
    return 0


def array2string(arr):
    return ''.join(str(e) for e in arr)


def read_file(in_file):
    tmp_array = []

    if not os.path.exists(in_file):
        die("%s not found" % in_file, 1)

    with open(in_file, 'r') as fh:
        for line in fh.readlines():
            if 'space' in line:
                tmp_array.append(
                    check_line(line)
                )

    return array2string(tmp_array)


def bin2hex(in_str):
    hex_array = []

    while in_str:
        hex_array.append(
            hex(
                int(in_str[:8], 2)
            )
        )
        in_str = in_str[8:]

    return hex_array


def main():
    n = 0
    long_hex = '0x'

    bin_string = read_file(input_file)
    # Print in hex
    hex_array = bin2hex(bin_string)
    for hex_val in hex_array:

        long_hex += str(hex_val[2:].zfill(2))
        hex_val = '0x' + hex_val[2:].zfill(2)

        print('Byte%2s - %4s [%8s]' % (
            n, hex_val,
            bin(int(hex_val, 16))[2:].zfill(8)
        ))
        n += 1

    # Print in bin
    print("[%s] %s" % (
        len(bin_string), bin_string))

    # Print in one big hex
    print(long_hex)


# Argument check
if len(sys.argv) < 2:
    print("Usage:\n  %s <file>\n" % sys.argv[0])
    sys.exit(0)
else:
    input_file = sys.argv[1]
    print("Using input file: %s" % input_file)

# Main
if __name__ == '__main__':
    main()

Okay, so now we're getting new results

Using input file: poweron.log
Byte 0 - 0xf9 [11111001]
Byte 1 - 0x06 [00000110]
Byte 2 - 0x81 [10000001]
Byte 3 - 0xfe [11111110]
Byte 4 - 0x00 [00000000]
Byte 5 - 0xa8 [10101000]
Byte 6 - 0x01 [00000001]
Byte 7 - 0x80 [10000000]
Byte 8 - 0x29 [00101001]
Byte 9 - 0x7e [01111110]
Byte10 - 0x41 [01000001]
Byte11 - 0xa0 [10100000]
Byte12 - 0x7f [01111111]
Byte13 - 0x80 [10000000]
Byte14 - 0x2a [00101010]
Byte15 - 0x00 [00000000]
Byte16 - 0x60 [01100000]
Byte17 - 0x0a [00001010]
Byte18 - 0x02 [00000010]
[147] 111110010000011010000001111111100000000010101000000000011000000000101001011111100100000110100000011111111000000000101010000000000110000000001010010
0xf90681fe00a80180297e41a07f802a00600a02

Using input file: poweroff.log
Byte 0 - 0xf9 [11111001]
Byte 1 - 0x06 [00000110]
Byte 2 - 0x81 [10000001]
Byte 3 - 0xfe [11111110]
Byte 4 - 0x00 [00000000]
Byte 5 - 0xa8 [10101000]
Byte 6 - 0x03 [00000011]
Byte 7 - 0x80 [10000000]
Byte 8 - 0x2b [00101011]
Byte 9 - 0x7e [01111110]
Byte10 - 0x41 [01000001]
Byte11 - 0xa0 [10100000]
Byte12 - 0x7f [01111111]
Byte13 - 0x80 [10000000]
Byte14 - 0x2a [00101010]
Byte15 - 0x00 [00000000]
Byte16 - 0xe0 [11100000]
Byte17 - 0x0a [00001010]
Byte18 - 0x06 [00000110]
[147] 111110010000011010000001111111100000000010101000000000111000000000101011011111100100000110100000011111111000000000101010000000001110000000001010110
0xf90681fe00a803802b7e41a07f802a00e00a06

Lets compare things now.

Command Len byte0 byte1 byte2 byte3 byte4 byte5 byte6 byte7 byte8 byte9 byte10 byte11 byte12 byte13 byte14 byte15 byte16 byte17 byte18
PowerOFF 147 11111001 00000110 10000001 11111110 00000000 10101000 00000011 10000000 00101011 01111110 01000001 10100000 01111111 10000000 00101010 00000000 11100000 0000101 0110
PowerON 147 11111001 00000110 10000001 11111110 00000000 10101000 00000001 10000000 00101001 01111110 01000001 10100000 01111111 10000000 00101010 00000000 01100000 0000101 0010
Command byte0 byte1 byte2 byte3 byte4 byte5 byte6 byte7 byte8 byte9 byte10 byte11 byte12 byte13 byte14 byte15 byte16 byte17 byte18
PowerOFF 0xf9 0x06 0x81 0xfe 0x00 0xa8 0x03 0x80 0x2b 0x7e 0x41 0xa0 0x7f 0x80 0x2a 0x00 0xe0 0x0a 0x06
PowerON 0xf9 0x06 0x81 0xfe 0x00 0xa8 0x01 0x80 0x29 0x7e 0x41 0xa0 0x7f 0x80 0x2a 0x00 0x60 0x0a 0x02

Okay, we can clearly see overall the same packet (as we expect) and changes just in byte6, byte8 and byte16 (checksum).
Both byte6 & byte8 switch the 7th bit

0x04 Repeating patterns

Lets looks at the data again but in binary , to make sure we didnt miss anything.

So you see it ? It is the same thing repeating two times....
11111001000001101000000111111110000000001010100000000001100000000010100101
1111100100000110100000011111111000000000101010000000000110000000001010010

11111001000001101000000111111110000000001010100000000011100000000010101101
1111100100000110100000011111111000000000101010000000001110000000001010110

Nice....
So we end up with

0x05 On/Off Status

Okay! This makes more sense. Now we have 8 bytes in total, and changes in bytes 6 & 8.
Byte6 bit7 is responsible for ON/OFF state of the AC. And Byte8 is the checksum, so that is why this changes.

Lets make this a bit more readable

Command byte0 byte1 byte2 byte3 byte4 byte5 byte6 byte7 byte8
PowerON 11111001 00000110 10000001 11111110 00000000 10101000 00000001 10000000 00101001
PowerOFF 11111001 00000110 10000001 11111110 00000000 10101000 00000011 10000000 00101011

Now it is time to go dump one by one all functions we will like to control.
First I'll go find how is the temperature value stored and where, then the mode (cold/hot/dry/auto).

0x06 Temperature value

Temp byte0 byte1 byte2 byte3 byte4 byte5 byte6 byte7 byte8
17 11111100 10000011 01000000 11111111 00000000 01000000 00000000 11000000 00000000
18 11111100 10000011 01000000 11111111 00000000 01000100 00000000 11000000 00000100
19 11111100 10000011 01000000 11111111 00000000 01001000 00000000 11000000 00001000
20 11111100 10000011 01000000 11111111 00000000 01001100 00000000 11000000 00001100
21 11111100 10000011 01000000 11111111 00000000 01010000 00000000 11000000 00010000
22 11111100 10000011 01000000 11111111 00000000 01010100 00000000 11000000 00010100
23 11111100 10000011 01000000 11111111 00000000 01011100 00000000 11000000 00011100
24 11111100 10000011 01000000 11111111 00000000 01100000 00000000 11000000 00100000
25 11111100 10000011 01000000 11111111 00000000 01100100 00000000 11000000 00100100
26 11111100 10000011 01000000 11111111 00000000 01101000 00000000 11000000 00101000
27 11111100 10000011 01000000 11111111 00000000 01101100 00000000 11000000 00101100
28 11111100 10000011 01000000 11111111 00000000 01110000 00000000 11000000 00110000
29 11111100 10000011 01000000 11111111 00000000 01110100 00000000 11000000 00110100
Temp byte0 byte1 byte2 byte3 byte4 byte5 byte6 byte7 byte8
17 0xFC 0x83 0x40 0xFF 0x00 0x40 0x00 0xC0 0x00
18 0xFC 0x83 0x40 0xFF 0x00 0x44 0x00 0xC0 0x04
19 0xFC 0x83 0x40 0xFF 0x00 0x48 0x00 0xC0 0x08
20 0xFC 0x83 0x40 0xFF 0x00 0x4C 0x00 0xC0 0x0C
21 0xFC 0x83 0x40 0xFF 0x00 0x50 0x00 0xC0 0x10
22 0xFC 0x83 0x40 0xFF 0x00 0x54 0x00 0xC0 0x14
23 0xFC 0x83 0x40 0xFF 0x00 0x5C 0x00 0xC0 0x1C
24 0xFC 0x83 0x40 0xFF 0x00 0x60 0x00 0xC0 0x20
25 0xFC 0x83 0x40 0xFF 0x00 0x64 0x00 0xC0 0x24
26 0xFC 0x83 0x40 0xFF 0x00 0x68 0x00 0xC0 0x28
27 0xFC 0x83 0x40 0xFF 0x00 0x6C 0x00 0xC0 0x2C
28 0xFC 0x83 0x40 0xFF 0x00 0x70 0x00 0xC0 0x30
29 0xFC 0x83 0x40 0xFF 0x00 0x74 0x00 0xC0 0x34

Strange pattern...but pattern.

0x06 Modes

The available modes are:

Mode byte0 byte1 byte2 byte3 byte4 byte5 byte6 byte7 byte8
Auto 11111100 10000011 01000000 11111111 00000000 01010100 00000000 00000000 00010100
Cold 11111100 10000011 01000000 11111111 00000000 01000100 00000000 01000000 00000100
Dry 11111100 10000011 01000000 11111111 00000000 01010100 00000000 10000000 00010100
Hot 11111100 10000011 01000000 11111111 00000000 01010100 00000000 11000000 00010100
Fan 11111100 10000011 01000000 11111111 00000000 01010100 00000001 00000000 00010101

It looks like byte5 & byte7 are the ones responsible for the mode.

0x07 Sum up our findings

What do we know so far?

* - They may not be static, but we don't seem to care about them. And we can even use it as a prefix to match if needed.

0x08 Calculate checksum

If we'll be generating modification on-will, we need to be able to calculate the checksum

TBA...