Introduction
I have a Toshiba RAS-B13N3KV2-E1
air conditioner system in my house that is not connected, meaning that I cannot turn it on or turn it off while I am outside. And many times, when I go out I forget to turn it on, or to program it to turn on and unfortunately when I come back, it is very warm inside. It would be perfect, if I could automate this air conditioner system in order to be able to switch on from outside, or even better to turn it on automagically if the temperature is too high.
In order to control my air conditioner system, I am using some remote controls using basic infra-red communication. Thus, the idea would be to have small system that I can reach from the network and that sends the good IR commands to my air conditioner system, in order to turn it on or off. I need two of them, because I have a module downstairs and another one upstairs. I shouldn’t be able to use only one, because IR needs to be in light of sight and there is no such place in my house, to be in light of sight of my two air conditioner modules.
The different steps of this project are probably going to be:
- Recover the correct IR commands sent by my both remote commands
- Set up a system (raspberry pi or ESP8266) to be able to be reached from the network and to be able to send the previously retrieved IR commands
- Implement some glue in homeassistant to be able to pilot, depending on the temperature and my presence at home, this air conditioner system
Of course, and obviously, everything needs to be in line with the WAF, otherwise things are going to go bad for myself.
Acting as my remote controller
IR commands detection
In order to get the IR commands sent by my official air conditioner remote, I use a raspberry pi and a simple IR receiver I got from an electronic starter kit.
By default, the raspbian kernel does not load any drivers related to IR, thus it is necessary to update its configuration, in order for the IR receiver to be recognized by the kernel. The dtoverlay=gpio-ir,gpio_pin=17
line in the /boot/config.txt
has to be uncommented for that.
The first pin of the IR receiver needs to be connected to the GPIO 17
of the RPI (according the configuration done previously), the middle pin needs to be connected to VCC
and the last one to the ground. Depending on the IR receiver used, it may vary.
If everything works well, we can read in the logs that the receiver has been detected by the kernel.
$ dmesg
...
[ 6.843792] Registered IR keymap rc-rc6-mce
[ 6.877002] IR RC6 protocol handler initialized
[ 6.914625] rc rc0: gpio_ir_recv as /devices/platform/ir-receiver@11/rc/rc0
[ 6.914899] rc rc0: lirc_dev: driver gpio_ir_recv registered at minor = 0, raw IR receiver, no transmitter
[ 6.915179] input: gpio_ir_recv as /devices/platform/ir-receiver@11/rc/rc0/input0
...
It is also possible to ensure that the gpio
modules are well loaded and the device exists.
$ lsmod | grep gpio
gpio_ir_recv 16384 0
$ cat /proc/bus/input/devices
I: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="gpio_ir_recv"
P: Phys=gpio_ir_recv/input0
S: Sysfs=/devices/platform/ir-receiver@11/rc/rc0/input0
U: Uniq=
H: Handlers=kbd event0
B: PROP=20
B: EV=100017
B: KEY=fff 0 0 4200 108fc32e 2376051 0 0 0 7 158000 4192 4001 8e9680 0 0 10000000
B: REL=3
B: MSC=10
Then, it is necessary to install the IR userland tools (and kernel module) for Linux.
$ sudo apt install lirc
The lowlevel utility mode2
from the lirc package give information about the signals sent by the remote controller.
$ mode2
Using driver devinput on device auto
Trying device: /dev/input/event0
Using device: /dev/input/event0
Warning: Running as root.
In accordance to the rule of tartine de confiture, nothing happens when I am using my remote control :( However, the use of my TV controller in front of my IR receiver, show that it works as intended.
$ mode2
Using driver devinput on device auto
Trying device: /dev/input/event0
Using device: /dev/input/event0
Warning: Running as root.
code: 0x63ab4561380108000400040000000100
code: 0x63ab4561380108000000000000000000
code: 0x63ab456183c408000400040000000100
code: 0x63ab456183c40800000000000000000
The IR receiver does not seem to be compatible with my air conditioner remote. The use of a TSOP38238
IR receiver provide the same results.
The modification of the /etc/lirc/lirc_options.conf
by adding driver = default
and device = /dev/lirc0
allows to solve this issue.
$ mode2 -d /dev/lirc0
Using driver default on device /dev/lirc0
Trying device: /dev/lirc0
Using device: /dev/lirc0
Warning: Running as root.
pulse 4356
space 4357
pulse 532
...
IR commands replay
Now the IR information are well received by the RPI, it is necessary to record this information in order to replay it.
The lirc tool irrecord
is used to record and analyse a sequence of IR commands.
$ irrecord -d /dev/lirc0 --driver default --disable-namespace
Using driver default on device /dev/lirc0
irrecord - application for recording IR-codes for usage with lirc
Copyright (C) 1998,1999 Christoph Bartelmus(lirc@bartelmus.de)
This program will record the signals from your remote control
and create a config file for lircd.
[...]
Press RETURN to continue.
Checking for ambient light creating too much disturbances.
Please don't press any buttons, just wait a few seconds...
No significant noise (received 0 bytes)
Enter name of remote (only ascii, no spaces) :Enter name of remote (only ascii, no spaces) :aircooling
Using aircooling.lircd.conf as output filename
Now start pressing buttons on your remote control.
[...]
Press RETURN now to start recording.
................................................................................
Got gap (7530 us)}
Please keep on pressing buttons like described above.
...............................................................................
Please enter the name for the next button (press <ENTER> to finish recording)
KEY_POWER
Now hold down button "KEY_POWER".
Please enter the name for the next button (press <ENTER> to finish recording)
Checking for toggle bit mask.
Please press an arbitrary button repeatedly as fast as possible.
Make sure you keep pressing the SAME button and that you DON'T HOLD
the button down!.
If you can't see any dots appear, wait a bit between button presses.
Press RETURN to continue.
...........................
[...]
Toggle bit mask is 0x60006.
You have only recorded one button in a non-raw configuration file.
This file doesn't really make much sense, you should record at
least two or three buttons to get meaningful results. You can add
more buttons next time you run irrecord.
Successfully written config file aircooling.lircd.conf
You have to script your fingers in order to randomly push the buttons of your remote control. However in my case, I have only one button to push. The result is a lirc configuration file, that you can use to replay IR commands.
begin remote
name aircooling
bits 72
flags SPACE_ENC
eps 30
aeps 100
header 4358 4363
one 530 1624
zero 530 546
ptrail 530
gap 7450
min_repeat 1
# suppress_repeat 1
# uncomment to suppress unwanted repeats
toggle_bit_mask 0x60006
frequency 38000
begin codes
KEY_POWER 0x000D03FC01608100E0
end codes
end remote
In order to send an IR signal, we need an IR transmitter. I used the one of my electronic starter kit. Such as for the IR receiver, some new configurations need to be done in order for the transmitter to be recognized by the kernel. The add of the line dtoverlay=gpio-ir-tx,gpio_pin=18
in /boot/config.txt
is sufficient. In my case, /dev/lirc0
becomes the transmitter and /dev/lirc1
the receiver.
The final electronic schema is therefore the following.
In order to replay an IR command, we have to use the irsend
binary. It reads the configuration file previously generated, and send the pattern specified on its command line. First, in order for lircd
to be aware of this new configuration file, it is necessary to restart it (every time modifications will be applied, a restart will be necessary).
The remote
parameter is used to find the correct system, and the code
parameter is used to send the correct command for that system. In my case, the remote
is aircooling
and the code
is KEY_POWER
, name I randomly choose in my configuration file.
$ mv aircooling.lircd.conf /etc/lirc/lircd.conf.d/
$ systemctl restart lircd
$ irsend SEND_ONCE aircooling KEY_POWER
Of course, and probably due to tartine_de_confiture law my air conditioner does not power on after sending this command :( Let’s dig into it.
IR communication deep dive
Warning: I have no knowledge in IR communication, therefore the following is only my interpretation
The IR messages sent by my remote command are a sequence of IR LED on and IR LED off. The duration of each state change. A couple of IR LED on and IR LED off is coding one information.
$ mode2 -d /dev/lirc1
[...]
pulse 4350
space 4361
pulse 531
space 1626
pulse 530
space 1624
pulse 530
space 1625
pulse 534
space 1621
pulse 534
space 544
pulse 532
space 545
pulse 533
space 1622
[...]
By reading the output of mode2
, we can detect four kind of duration:
- about 500
- about 1500
- about 4300
- about 7000
According to the generated file (that is not working), a pulse (IR LED on) of 500 followed by a pause (IR LED off) of 500 is coding a 0, while a pulse of 500 followed by a pause of 1500 is coding a 1.
The following crappy script, parse the sequence of pulse
, space
and translate it into sequence of 0
and 1
and other values, if the duration is different.
def parse(s):
def v(x):
if x<1000:
return 0
elif x < 2000:
return 1
elif x < 5000:
return 2
else:
return 3
h = {(0,0):"0",(0,1):"1",(2,2):"A",(0,3):"B"}
t = list(map(lambda x:int(x[6:]),s.split("\n")))[:-1]
r = []
for i in range(0,len(t),2):
r.append(h.get((v(t[i]),v(t[i+1])),str((t[i],t[i+1]))))
code = "".join(r)
return code
Now let’s try to decode the remote command push power button.
$ mode2 -d /dev/lirc1 > power_button.txt
$ decode.py power_button.txt
A111100100000110100000011111111000000000101100000100000010000000011100000BA111100100000110100000011111111000000000101100000100000010000000011100000
That’s quite interesting, we can see a kind of starter (called A
) then a kind of ender (called B
) and between them some information. There are 72 bits sent, and then the same 72 bits. The remote air conditioner control is sending twice the same message. If I change a bit my python script to translate binaries in hexadecimal the result is the following:
$ decode.py power_button.txt
['A', 'F20D03FC01608100E0', 'B', 'A', 'F20D03FC01608100E0']
We can clearly see the same message sent.
What is quite interesting, is that when I send IR commands with my IR transmitter, I can also receive those commands with the IR receiver, and then compare if the result is what I was waiting for.
So when I execute the command irsend SEND_ONCE aircooling KEY_POWER
, the dump is:
$ mode2 -d /dev/lirc1 | decode.py
['A', '000D03FC0160810000']
First, the irsend
is not sending twice the information, but the main problem here is that the information sent is different. According to the configuration, the irsend
command respects it, therefore the irrecord
made a mistake during the generation.
$ cat /etc/lirc/lircd.conf.d/aircooling.lircd.conf
[...]
KEY_POWER 0x000D03FC01608100D0
[...]
The value F20D03FC01608100E0
cannot be stored on a uint64_t
number and lircd
refuses to parse my aircooling configuration file.
Oct 2 14:34:49 raspberrypi lircd[981]: lircd-0.10.1[981]: Error: "0xF20D03FC01608100E0": must be a valid (uint64_t) number
The lirc
configuration also allows a raw mode syntax which is a bit different. It is directly the sequence of space
and pulse
that can be retrieved using the -m
option of mode2
(the final space
has to be removed). In addition, we have to precise the gap
value that is sum of each duration.
$ mode2 -d /dev/lirc1 -m
Using driver default on device /dev/lirc1
Trying device: /dev/lirc1
Using device: /dev/lirc1
Warning: Running as root.
16777215
4352 4361 530 1623 533 1624
531 1624 531 1625 530 546
532 544 531 1624 535 548
531 542 555 522 533 545
531 546 531 1625 532 1623
556 521 530 1625 530 547
530 547 531 546 531 546
531 546 556 522 530 1625
531 1624 532 1625 555 1600
530 1625 536 1572 615 1589
532 1625 530 546 531 547
530 546 535 541 531 546
[...]
$ cat /etc/lirc/lircd.conf.d/aircoolingraw.lircd.conf
begin remote
name aircoolingraw
flags RAW_CODES|CONST_LENGTH
eps 30
aeps 100
gap 120150
begin raw_codes
name KEY_POWER
4352 4361 530 1623 533 1624
531 1624 531 1625 530 546
532 544 531 1624 535 548
531 542 555 522 533 545
531 546 531 1625 532 1623
556 521 530 1625 530 547
530 547 531 546 531 546
531 546 556 522 530 1625
531 1624 532 1625 555 1600
530 1625 536 1572 615 1589
532 1625 530 546 531 547
530 546 535 541 531 546
[...]
end raw_codes
end remote
$ irsend SEND_ONCE aircoolingraw KEY_POWER
Now it works ! When configured in raw mode, lirc
send the correct IR messages and my air conditioner is power on thanks to RPI, even without sending the signal twice. A brief analysis shows also that the signal sent to power on and power off my air conditioner are not the same.
POWER_ON = 0xF20D03FC01608100E0
POWER_OFF = 0xF20D03FC01608700E6
Air conditioner remote commands reverse
My air conditioner allows to
- Be switched on/off
- To configure the desired temperature from 17°C to 30°C
- To change the fan speed
- To be configured to switch ON/OFF at a specific time
- …
In order to be fully automated, the same work as described previously has to be done for each command that I want to be available. By analyzing the commands sent, we can extract some patterns.
Every IR command contains:
- A header that starts with
0xF20D
- A length byte representing the full length added to 6 of the IR command
- A byte that is 0xFF - the value of the previous byte (a kind of error correction)
- The data of the packet
- A checksum to ensure that the signal is correct
def crc(x):
c = 0
for i in range(16):
c ^= (x>>(8*i))&0xFF
return c
The data represents the action that the air conditioner needs to perform:
- The first byte of the data is the type of action
0x01
is to configure temperature/fan0x09
is to configure eco mode :HiPower
orEco
0x21
is to configure the swing of the plastic part
If the command is 0x1
, then:
- The most significant 4 bits of the next byte are the temperature we want : 0x0 from 0xD means respectively from 17°C to 30°C
- The most significant 4 bits of the byte after are the fan speed we want : 0x0 is auto then 0x4,0x6,0x8,0xA,0xC is the fan speed
We can also realize, that the different mode available are just some modes stored values of temperature/fan speed that we can configure as we want, such as the quiet mode
that just change the fan speed.
Automation
Now I know exactly what to do, in order to switch ON and OFF my air conditioner, I need to make things more friendly than executing some dark Linux commands. In order to automate things in my house, I use homeassistant. It is very flexible and powerful and I love it. It allows to interface sensors with many things, it provides a nice interface and even a smartphone application. In addition, it does not depend on any dark private cloud that could decide to take the control of my house. Therefore the integration of my air conditioner with my homeassistant seems obvious.
Switch On/Off
MQTT to IR
I choose to pilot my air conditioner with the MQTT protocol which is widely used in the IOT world. In addition, it is very well integrated in homeassistant. For that, I need to develop a kind of proxy between the MQTT
messages that are going to be sent by homeassistant, and the IR commands that will be sent by LIRC
or directly to the gpio
driver. This proxy is coded in go because I want to learn this language. It listens on a specific MQTT topic
and as soon as it received a message, it sends:
- Either a network message to
lircd
through its unix socket, to simulate a push on a lirc configured button - Or the correct sequence of pulses/spaces to the
gpio
driver
The source code is available on my github.
In my case, I decided to directly communicate to the gpio
driver and fully bypass LIRC
. The reason behind is because LIRC
forces to create a predefined button for each command we want to send. It is not possible (or at least I did not find how to do that), to dynamically create a button depending on parameters. For example, in my case in order to be able to use all the possible temperatures (14) with all the possible fan speed (6), I would need to define 6*14=84
buttons… Even if I probably do not need all the possible combinations, I do not like this approach and I prefer to dynamically send my commands. In order to do that, I need to directly talk to the gpio
driver.
Air conditioner state
Even if I know how to switch off/on my air conditioner, it does not mean I know its state. Indeed, if someone use the remote controller, I have actually no way to know that. So I may think that my air conditioner is ON
, because I just sent the correct MQTT
message, while someone just switch off with the remote. If I was a good electrician, I would connect my raspberry pi directly on the mother board of my air conditioner in order to see if it is up or not. However, I am very bad in electronic and I do not want to plug my system on it, if I make a mistake I can break everything… So I need to find a safer way.
I could rely on my electrical consumption that I monitor closely, it would work to know if I correctly switch off or on my air conditioner (by looking for electrical gap), however it means I would need to change the state once just to get the state, and then change it again. After some days of breaking my head, I decided to use some door sensors. I already use many of them in my home, and they work smoothly (of course I get rid off the xiaomy chinese cloud). There are two parts, if they are closed enough the magnetic sensor inside detects it and the door is considered as closed, if they are too far, the door is considered as opened. I can use this exact same principle with my air conditioner. When it is on, there is a peace of plastic that moves. So if I put one part of the sensor on that peace of plastic, I can detect that it is not in place and therefore my air conditioner is on
.
Homeassistant Configuration
The configuration in homeassistant, is quite easy:
- It is composed of two scripts (ON and OFF)
- A switch
switch_on_aircooling:
alias: Switch on aircooling
mode: single
variables:
temperature: 22
fanspeed: 0
fields:
temperature:
name: Temperature
description: Temperature to set
selector:
number:
min: 17
max: 30
step: 1
mode: slider
fanspeed:
name: Fan speed
description: Fan speed to set
selector:
number:
min: 0
max: 12
step: 2
mode: slider
sequence:
- service: mqtt.publish
data:
topic: "airconditioner/downstair/command"
payload_template: "F20D03FC01{{ '%X' % (temperature - 17) }}0{{ '%X' % fanspeed }}000{{ '%X' % (xor(temperature-17,fanspeed)) }}1"
switch_off_aircooling:
alias: Switch off aircooling
mode: single
sequence:
- service: mqtt.publish
data:
topic: "airconditioner/downstair/command"
payload_template: "F20D03FC01608700E6"
This script uses a custom component providing the basic function xor
that homeassistant does not provide (what a shame !).
And the associated switch
:
switch:
- platform: template
switches:
airconditioner_salon:
value_template: "{{ states('binary_sensor.magnet_airconditioner_salon') }}"
turn_on:
service: script.turn_on
target:
entity_id: script.switch_on_aircooling
data:
variables:
temperature: 23
fanspeed: 0
turn_off:
service: script.turn_on
target:
entity_id: script.switch_off_aircooling
icon_template: "mdi:air-conditioner"
Final assembly
Everything works, but it needs to be WAF. In my house, just under my air conditioner, there is my LED matrix I have built. It means that I could use the RPI Zero in it, to also control my air conditioner. No new system, no new battery, no need of argumentation to convince my family members that this new geek stuff is mandatory for our life.
I just need to connect the IR emitter to some RPI gpio
, and then to create a hole in the LED Matrix for the LED.
(Yeah, I know I forget a resistance, meaning that my LED is probably going to die quite quickly…)
The final update of my LED Matrix is really minimal.
Conclusion
It was a very fun project to work on that allowed me to work on many different topics:
- Receive IR signals
- Decode and reverse those IR signals
- Send IR Signals
- Reverse the way of working of
lircd
to be able to bypass it - Program in
golang
At the end, the goal is reached to be able to control this unconnected air conditioner. I added this new sensor, in some homeassistant automation such as: If it is 10pm, no one is in the house and the inside temperature is above 25°C, switch on the air conditioner.
In addition of the fun part of the project, the result is also a better comfort in my home.