I can now turn my lights on and off from my phone
Short update to keep this half-working with Linux Kernel 4.19 here
I forget exactly what drove me to do this, probably sheer laziness; I was getting tired of getting out of bed to turn the lights in my bedroom on and off. In the age of the smartphone and internet of things, it seemed obvious to try some sort of internet-enabled solution, just in case I was ever on the other side of the planet and wanted to turn my lights on. Looking around online it seemed that the only out-of-the-box solutions were either prohibitively expensive and/or required bulbs controlled through a wifi-connected RF base station. Besides, doing that kind of thing would really damage what little hacker cred I have to begin with.
Then it occurred to me that with a cheap (~£10) IR-controlled lightbulb and some IR LEDs, I could probably hack something together using a Raspberry Pi. Thankfully, I already had one of those lying around (it sits on my desk and wakes my desktop up when I throw TCP packets at it).
An additional bonus was that it gave me an excuse to try out Flutter, Google’s new app development framework for Android and iOS.
Before I begin, I have to disclose that large parts of this were informed by a tutorial on alexba.in. Without that tutorial I probably would never have managed this.
I’ve been meaning to write this up for a while now, mainly just as a reminder to myself on how I glued everything together!
Parts
Electronics-wise this is quite a simple project:
- Infrared LEDs (I used two, because why not)
- 10kΩ resistor
- NPN transistor (e.g. 2N2222)
- TSOP38238 Infrared Receiver
Discounting minimum quantities, I think that amounts to about £0.70 in parts. The PCBs I had fabbed (more on that later) cost more than that.
LIRC
The actual sending and receiving of IR data is performed with LIRC, a Linux package, installed on the Raspberry Pi with:
1sudo apt-get install lirc
We then have to tell LIRC which pins to use for sending and receiving data.
Add the following to /etc/modules
:
1lirc_dev
2lirc_rpi gpio_in_pin=17 gpio_out_pin=27
And add the following to /boot/config.txt
:
1dtoverlay=lirc-rpi,gpio_in_pin=17,gpio_out_pin=27
Finally, modify /etc/lirc/lirc_options.conf
:
1driver = default
The Circuits
Below is the basic circuit diagram.
This was all assembled on a breadboard that lived on my desk, with jumper cables galore. Sadly, I have no pictures of the monstrosity.
KiCad
Eventually, I grew tired of the rat’s nest of wires sat on my desk and set about designing a two-layer PCB to replace it, which I designed in KiCad.
I’m impressed with KiCad; for someone who before this had never really done any PCB design, this was pretty painless, finding footprints for each part was probably the most irritating bit.
The board measures less than an inch square, 0.95" x 0.75". Originally I was going to use a full length 2x20 connector for the Raspberry Pi, but the overall footprint of these parts was so small that it simply wasn’t worth the extra cost of fabbing a board that size, hence the 2x8.
I sent these plans to OSH Park and got three copies of the board for $3.60 with free postage. Nice purple boards, bit of a wait but for a hobbyist who just wants a couple of copies to mess about with, it doesn’t really matter.
Here’s a glamour shot of the board:
And attached to the Raspberry Pi for scale:
Learning IR Codes
Even this was quite simple.
1sudo irrecord -d /dev/lirc0 /etc/lirc/lircd.conf.d/my_remote.conf
And follow the instructions on screen to learn each of the buttons on your remote.
Then if you take a peek at /etc/lirc/lircd.conf.d/my_remote.conf
:
1alecks@rp3> cat /etc/lirc/lircd.conf.d/my_remote.conf
2
3# Please make this file available to others
4# by sending it to <[email protected]>
5#
6# this config file was automatically generated
7# using lirc-0.9.0-pre1(default) on Tue Jul 25 20:14:01 2017
8#
9# contributed by
10#
11# brand:
12# model no. of remote control:
13# devices being controlled by this remote:
14#
15
16begin remote
17
18 name AURAGLOW
19 bits 16
20 flags SPACE_ENC|CONST_LENGTH
21 eps 30
22 aeps 100
23
24 header 9067 4477
25 one 579 1660
26 zero 579 555
27 ptrail 579
28 repeat 9070 2212
29 pre_data_bits 16
30 pre_data 0xF7
31 gap 107975
32 toggle_bit_mask 0x0
33
34 begin codes
35 brightness_up 0x00FF
36 brightness_down 0x807F
37 off 0x40BF
38 on 0xC03F
39 red 0x20DF
40 green 0xA05F
41 blue 0x609F
42 white 0xE01F
43 r1 0x10EF
44 g1 0x906F
45 b1 0x50AF
46 flash 0xD02F
47 r2 0x30CF
48 g2 0xB04F
49 b2 0x708F
50 r3 0x08F7
51 b3 0x8877
52 b3 0x48B7
53 g3 0x8877
54 r4 0x28D7
55 g4 0xA857
56 b4 0x6897
57 strobe 0xF00F
58 fade 0xC837
59 smooth 0xE817
60 end codes
61
62end remote
The brand of this particular remote controlled bulb is “Auraglow”, by the way. It’s got lots of pretty colours and strobey functions.
You can test each of the buttons with:
1irsend SEND_ONCE <REMOTE_NAME> <CODE>
For example, in my case:
1irsend SEND_ONCE AURAGLOW red
Sets the lights to red.
Controlling over the network
The next thing I wanted to be able to do was to trigger the sending of these IR signals remotely. There’s probably a simpler way of doing this, but I chose good old Python.
1import socket
2import sys
3import re #Added in edit, see below
4from subprocess import call
5
6UDP_PORT = 5006
7UDP_IP = "0.0.0.0"
8
9if len(sys.argv) > 0:
10 UDP_PORT = int(sys.argv[1])
11
12sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # Internet,UDP
13sock.bind((UDP_IP, UDP_PORT))
14
15print("Listening on : "+str(UDP_IP)+":"+str(UDP_PORT)+"...")
16
17while True:
18 data = b''
19 addr = ""
20 data, addr = sock.recvfrom(1024)
21
22 print("received message: ", data, " from: ", addr)
23 print("length: ", len(data))
24 print("decode: ", data.decode())
25
26 m = data.decode()
27 m = re.split("[^a-zA-Z0-9 _]", m)[0] #Edit: This line prevents shell injection!
28 call("irsend SEND_ONCE " + m,shell=True)
Code also available in this github repo.
This script listens on UDP port 5006
for a string that it can tack onto the end of irsend SEND_ONCE
to form a command. So I just send AURAGLOW strobe
to that port and it’s time for a rave.
Is it secure? Probably not. But it works. Remember, this is being driven by laziness.
Edit: It’s definitely not secure, like ‘vulnerable to shell injection’ not secure. Since we need shell=True
in the call command, we have to do some input sanitisation instead. I don’t think this is necessarily best practice (i.e. I think best practice would be to quote the command, but I couldn’t get that to work).
Adding this line: m = re.split("[^a-zA-Z0-9 _]", m)[0]
(the second to last one) stops any funny business.
Set this is run at boot with a systemd service file, /etc/systemd/system/lirclistener.service
:
1[Unit]
2Description=Python script processing lirc requests
3Want=network.target
4After=network.target
5
6[Service]
7User=root
8Group=root
9WorkingDirectory=/home/alecks/git/lircListener
10ExecStart=/usr/bin/python3 lircListener.py 5006
11Restart=always
12RestartSec=5
13
14[Install]
15WantedBy=multi-user.target
And then enable and start it with:
1sudo systemctl enable lirclistener.service
2sudo systemctl start lirclistener.service
Android App
I wrote a very quick and dirty app using Flutter. I’m very ashamed of this code. I will not be sharing it until I have made it look sane. Put it this way, at the moment each button calls a different method. Not the same method with different arguments, different methods with different names like so:
1void _sendBrightnessUp() {
2 setState(() {
3 LircClient.sendCommand(address,port,"AURAGLOW brightness_up");
4 });
5}
Then the button itself:
1new RaisedButton(
2 child: new Icon(Icons.brightness_high),
3 onPressed: _sendBrightnessUp,
4)
Disturbing, no?
I couldn’t for the life of me figure out how to generalise that function to a general string, since onPressed
requires a function with no arguments; I’m very clearly missing a trick here. But I wrote this at 2am, and clearly, didn’t care, I wanted to be able to turn the lights off and go to bed. Rather, go to bed and turn the lights off from my phone.
The secret with sending packets of data like this turned out to be DatagramSockets; Android doesn’t like to let you open a TCP/UDP socket and just leave it open for you to use later.
Anyway, here’s a screenshot:
All in all, I think this is a horrid, hacky way to it. If there’s any home automation people reading this, they’re deffo gesticulating furiously.