RaspIR

Because Philips Hue is far too expensive and convenient

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.

The 2x8 connector at top right corresponds to pins 1 through 16 of a raspberry pi. D1 and D2 are the infrared LEDs, R1 is a 10k ohm pull-down resisitor, and Q1 is the 2N222 transistor. Note that the LEDs are connected to the 5V rail, but the TSOP38238 is on the 3.3V rail. Created in KiCad.

Circuit Diagram

The 2x8 connector at top right corresponds to pins 1 through 16 of a raspberry pi. D1 and D2 are the infrared LEDs, R1 is a 10k ohm pull-down resisitor, and Q1 is the 2N222 transistor. Note that the LEDs are connected to the 5V rail, but the TSOP38238 is on the 3.3V rail. Created in KiCad.

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.

Blue is front silkscreen, yellow is the board edge. Red is the top layer of copper, green is the bottom.

PCB Layout

Blue is front silkscreen, yellow is the board edge. Red is the top layer of copper, green is the bottom.

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:

Please ignore that dodgy soldering on the PCB header.

The Finished Board

Please ignore that dodgy soldering on the PCB header.

And attached to the Raspberry Pi for scale:

The footprint is small enough that you could get away with using a Pi Zero

The Board In Situ

The footprint is small enough that you could get away with using a Pi Zero

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:

For what effectively amounts to less than 100 lines of code (in a single dart file as well), this is surprisingly nice.

Flutter App

For what effectively amounts to less than 100 lines of code (in a single dart file as well), this is surprisingly nice.

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.