07
- February
2021
Posted By : Dave Hartburn
Raspberry Pi: IR Remote and Python

Small infrared remotes are a cheap way to add a level of remote control to a Raspberry Pi project. The one pictured below was under £5 (from Everything Pi) for the remote and IR receiver module. As the other code I was working on was in python, I also wanted a python program which could detect and respond to the remote.

The following is based on Raspian Buster. From Raspian Stretch, support for the traditional IR handler, LIRC was dropped in favour of ir-keytable. I found documentation on how to use this with python hard to come by, hopefully this article will help others finding the same.

Wiring it up

The wiring connections are fairly simple. The receiver module had two pins marked + and -, connect these to the 3.3v output and GND. The other pin is for data, I connected this to GPIO 18, pin 12.

The remote requires a CR2025 battery, I didn’t have one but a CR2032 works fine.

Once connected, point the remote and the sensor and if it detects a signal, a small LED on the module flashes. Another way to test if the remote is working is to look at the end of the remote with a camera, it should detect the flash.

ir-keytable configuration

To start using ir-keytable, a device has to be configured. As root, edit /boot/config.txt and uncomment the line below, ensuring you have the correct pin set, and reboot.

dtoverlay=gpio-ir,gpio_pin=18

Install the ir-keytable package:

sudo apt install ir-keytable

There are a number of well known remote definition files in /lib/udev/rc_keymaps, however knowing what to look for in an unbranded cheap remote like mine is not easy. Creating a custom keymap is probably the easiest way forward.

To test input run:

sudo ir-keytable -v -t -p all

This should report it has found an input device (possibly /dev/input/event0) and list currently supported protocols. Begin pressing data on your remote and you should see events fill the screen, a key down and key up for each press:

Testing events. Please, press CTRL-C to abort.
54703.160065: lirc protocol(nec): scancode = 0x40
54703.160094: event type EV_MSC(0x04): scancode = 0x40
54703.160094: event type EV_KEY(0x01) key_down:(0x0006)
54703.160094: event type EV_SYN(0x00).
54703.220082: lirc protocol(nec): scancode = 0x40 repeat
54703.220110: event type EV_MSC(0x04): scancode = 0x40
54703.220110: event type EV_SYN(0x00).
54703.340030: event type EV_KEY(0x01) key_up: (0x0006)
54703.340030: event type EV_SYN(0x00).

In this case I pressed the number 5 key, which I can see has the code 0x40. I can also see it is using the nec protocol. Look at one of the examples in /lib/dev/rc_keymaps for an idea of the keymap file. Open a blank text document, give your remote a name then using the ir-keymap test above, run through and press each button, assigning it to an event code, to build a keymap file like below.

I called my remote ultraThin as this is how it was advertised when sold. For a list of valid event codes, see the kernel header file at https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h

[[protocols]]
name = "UltraThin"
protocol = "nec"
varient = "nec32"

[protocols.scancodes]
0x45 = "KEY_1"
0x46 = "KEY_2"
0x47 = "KEY_3"
0x44 = "KEY_4"
0x40 = "KEY_5"
0x43 = "KEY_6"
0x07 = "KEY_7"
0x15 = "KEY_8"
0x09 = "KEY_9"
0x16 = "KEY_NUMERIC_STAR"
0x19 = "KEY_0"
0x0d = "KEY_NUMERIC_POUND"
0x18 = "KEY_UP"
0x08 = "KEY_LEFT"
0x1c = "KEY_OK"
0x5a = "KEY_RIGHT"
0x52 = "KEY_DOWN"

We can test the file by flushing any configuration, ensuring our configuration is fully listed and running the test again. Below flushes the configuration, loads our file followed by me pressing 7, * and the right arrow (sudo -s is considered bad practice but makes life easier):

astroberry@astropi:/tmp $ sudo -s
root@astropi:/tmp# ir-keytable -c
Old keytable cleared
root@astropi:/tmp# ir-keytable -w ultraThin.toml 
Wrote 17 keycode(s) to driver
Protocols changed to nec 
root@astropi:/tmp# ir-keytable -r
scancode 0x0007 = KEY_7 (0x08)
scancode 0x0008 = KEY_LEFT (0x69)
scancode 0x0009 = KEY_9 (0x0a)
scancode 0x000d = KEY_NUMERIC_POUND (0x20b)
scancode 0x0015 = KEY_8 (0x09)
scancode 0x0016 = KEY_NUMERIC_STAR (0x20a)
scancode 0x0018 = KEY_UP (0x67)
scancode 0x0019 = KEY_0 (0x0b)
scancode 0x001c = KEY_OK (0x160)
scancode 0x0040 = KEY_5 (0x06)
scancode 0x0043 = KEY_6 (0x07)
scancode 0x0044 = KEY_4 (0x05)
scancode 0x0045 = KEY_1 (0x02)
scancode 0x0046 = KEY_2 (0x03)
scancode 0x0047 = KEY_3 (0x04)
scancode 0x0052 = KEY_DOWN (0x6c)
scancode 0x005a = KEY_RIGHT (0x6a)
Enabled kernel protocols: lirc nec 
root@astropi:/tmp# ir-keytable -t
Testing events. Please, press CTRL-C to abort.
55313.370128: lirc protocol(nec): scancode = 0x7
55313.370152: event type EV_MSC(0x04): scancode = 0x07
55313.370152: event type EV_KEY(0x01) key_down: KEY_7(0x0008)
55313.370152: event type EV_SYN(0x00).
55313.440052: lirc protocol(nec): scancode = 0x7 repeat
55313.440076: event type EV_MSC(0x04): scancode = 0x07
55313.440076: event type EV_SYN(0x00).
55313.560035: event type EV_KEY(0x01) key_up: KEY_7(0x0008)
55313.560035: event type EV_SYN(0x00).
55314.510051: lirc protocol(nec): scancode = 0x16
55314.510076: event type EV_MSC(0x04): scancode = 0x16
55314.510076: event type EV_KEY(0x01) key_down: KEY_NUMERIC_STAR(0x020a)
55314.510076: event type EV_SYN(0x00).
55314.560050: lirc protocol(nec): scancode = 0x16 repeat
55314.560071: event type EV_MSC(0x04): scancode = 0x16
55314.560071: event type EV_SYN(0x00).
55314.680031: event type EV_KEY(0x01) key_up: KEY_NUMERIC_STAR(0x020a)
55314.680031: event type EV_SYN(0x00).
55316.900134: lirc protocol(nec): scancode = 0x5a
55316.900162: event type EV_MSC(0x04): scancode = 0x5a
55316.900162: event type EV_KEY(0x01) key_down: KEY_RIGHT(0x006a)
55316.900162: event type EV_SYN(0x00).
55316.970052: lirc protocol(nec): scancode = 0x5a repeat
55316.970077: event type EV_MSC(0x04): scancode = 0x5a
55316.970077: event type EV_SYN(0x00).
55317.060081: lirc protocol(nec): scancode = 0x5a repeat
55317.060106: event type EV_MSC(0x04): scancode = 0x5a
55317.060106: event type EV_SYN(0x00).
55317.180032: event type EV_KEY(0x01) key_up: KEY_RIGHT(0x006a)
55317.180032: event type EV_SYN(0x00).
^C
root@astropi:/tmp# exit
exit
astroberry@astropi:/tmp $ 

Now we know this works, we can make a permanent change.

cp ultraThin.toml /etc/rc_keymaps
vi /etc/rc_maps.cfg
  #driver table                    file
  *       *                        ultraThin.toml
  *       rc-adstech-dvb-t-pci     adstech_dvb_t_pci.toml
vi /etc/rc.local
# add the following line
/usr/bin/ir-keytable -a /etc/rc_maps.cfg -s rc0

On reboot, try ir-keytable -t again and you should see your key codes listed on a button press. This will now feed events into the standard linux event system, which means you can use event handling libraries from any language you wish to use. Test this by installing and running evtest. Remember we noted the input device earlier, now is the time to use it.

$ sudo apt install evtest
$ evtest /dev/input/event0
Input driver version is 1.0.1
Input device ID: bus 0x19 vendor 0x1 product 0x1 version 0x100
Input device name: "gpio_ir_recv"
Supported events:
  Event type 0 (EV_SYN)
  Event type 1 (EV_KEY)
    Event code 2 (KEY_1)
    Event code 3 (KEY_2)
    Event code 4 (KEY_3)
... output cut ...
Testing ... (interrupt to exit)
Event: time 1612701161.123493, type 4 (EV_MSC), code 4 (MSC_SCAN), value 19
Event: time 1612701161.123493, type 1 (EV_KEY), code 11 (KEY_0), value 1
Event: time 1612701161.123493, -------------- SYN_REPORT ------------
Event: time 1612701161.173486, type 4 (EV_MSC), code 4 (MSC_SCAN), value 19
Event: time 1612701161.173486, -------------- SYN_REPORT ------------
Event: time 1612701161.293459, type 1 (EV_KEY), code 11 (KEY_0), value 0
Event: time 1612701161.293459, -------------- SYN_REPORT ------------
Event: time 1612701162.103503, type 4 (EV_MSC), code 4 (MSC_SCAN), value 52
Event: time 1612701162.103503, type 1 (EV_KEY), code 108 (KEY_DOWN), value 1
Event: time 1612701162.103503, -------------- SYN_REPORT ------------
Event: time 1612701162.153497, type 4 (EV_MSC), code 4 (MSC_SCAN), value 52
Event: time 1612701162.153497, -------------- SYN_REPORT ------------
Event: time 1612701162.273457, type 1 (EV_KEY), code 108 (KEY_DOWN), value 0
Event: time 1612701162.273457, -------------- SYN_REPORT ------------

A key press of 0 and down has been successfully detected.

Using events for IR remotes in python

To read events, python needs the evdev package:

apt install python3-evdev

There is a tutorial about using the event system at https://python-evdev.readthedocs.io/en/latest/tutorial.html. First we can use list_devices() to locate all input devices on the system we can use. We loop through to find one called gpio_ir_recv, which it is the IR receiver.

After that we can use read_loop to read all events. In the code below there are two ways demonstrated of reading the events. Another consideration is writing a function to return data.keycode as a string of a key pressed, or a blank string if nothing is pressed.

# IR remote tester
# Assumes device already configured with ir-keytable

from evdev import *

devices = [InputDevice(path) for path in list_devices()]
# Define IR input
irin = None
for device in devices:
    #print(device.path, device.name, device.phys)
    if(device.name=="gpio_ir_recv"):
        irin = device

if(irin == None):
    print("Unable to find IR input device, exiting")
    exit(1)

print("IR input device found at", irin.path)

# Read events and return string
def readInputEvent(device):
    for event in device.read_loop():
        # Event returns sec, usec (combined with .), type, code, value
        # Type 01 or ecodes.EV_KEY is a keypress event
        # a value of  0 is key up, 1 is key down
        # the code is the value of the keypress
        # Full details at https://python-evdev.readthedocs.io/en/latest/apidoc.html

        # However we can use the categorize structure to simplify things
        # .keycode - Text respresentation of the key
        # .keystate - State of the key, may match .key_down or .key_up
        # See https://python-evdev.readthedocs.io/en/latest/apidoc.html#evdev.events.InputEvent
        if event.type == ecodes.EV_KEY:
            print("Event received")
            # Alert about keydown on the 8 key
            if(event.code == ecodes.KEY_8 and event.value==1):
                print("Detected keydown event on the 8")

            # Or use categorize. This is more useful if we want to write a function to
            # return a text representation of the button press on a key down
            #print(categorize(event))
            data = categorize(event)
            if(data.keycode=="KEY_5" and data.keystate==data.key_down):
                print("Detected keydown event on the 5")
            else:
                print(data.keycode, "up/down")
            print()

while True:
    readInputEvent(irin)

Leave a Reply