Bare metal IR means that button presses from an infrared remote control will be processed by a python script running on an Orange Pi Zero where LIRC (Linux Infrared Remote Control) and ir-keytable are not installed. What is being used is the Linux kernel support for IR remotes and the python-evdev package by Georgi Valkov.
While this note is about handling IR remotes on an Orange Pi Zero running Armbian Stretch, much would probably be valid for the Raspberry Pi. I will probably look into that some time later.
Linux
kernel then there is probably no need for LIRC or Python scripts and so on. Just read the sections 1
and 2 below to verify that the remote can be used. Read sections 6 and
7 to set the IR protocol or protocols permanently. Then look into using
ir-keytable
. I have a few notes on that subject which can
be found in the post entitled ir-keytable on
the Orange Pi Zero.Table of Contents
- Installing the Kernel Module
- Getting the Remote Scan Codes
- Python Prerequisites
- Handling IR Events
- A Short Python Script
- Setting the IR Protocol Permanently
- My Very Own IR Protocol Setter
Installing the Kernel Module
By default the IR receiver driver, called sunxi-cir
, is not
installed. That makes sense as the Orange Pi Zero does not have an IR
receiver on board. However the optional expansion card does contain one, and
this is what I will be using. The simplest and indeed the only way I know of
loading the kernel module when booting is to use the
armbian-config
utility. Enable the cir
module in
the System/Hardware settings. If you are not familiar with this
procedure, here are the details starting with launching the configuration
utility.
Make sure System is highlighted using the up and down cursor keys and then "clicking" on the < OK > button. That means pressing the space bar or Enter button with < OK > highlighted. If that button is not highlighted use the Tab or left and right cursor keys to make it so.
In the System settings
screen, highlight the Hardware
option and click on the < OK > button.
In the Toggle hardware configuration
screen highlight the
cir function and press the space bar so
that a star '*' appears to the left of cir. Click on the
< Save > button and then on the
< Exit > button.
Finally, click on the < Reboot > button.
After a minute or so, start a new SSH session with the OPiZ and perform some checks to see if the module is loaded and initialized.
Everything looks good. The module is loaded, it is initialized at boot
time and IR remote control button presses show up on input event0. Time to
try the remote control. Note if the xxd
filter is not installed,
just run the command without the pipe: | xxd
. The output will
look very weird, but who cares at this point.
It looked so promising, but nothing happened as I pressed buttons on the remote IR control aimed at the receiver. Break out of the loop by pressing the CtrlC combination. As can be seen, the remote is one of those very cheap remotes (about $1 US for the remote and a receiver) from the usual Chinese vendors. It seemed like a wise move, instead of trying this with the complex set box remote which I hope to use in the end. These cheap remotes are known to use the NEC protocol, so time to look at the supported protocol.
The output is the list of supported IR protocols, the one in square brackets is being used. I found that it was necessary to become the "superuser" to enable another protocol.
This worked, something was being read from the event as keys were pressed on the remote.
As before, press the CtrlC keys in combination when tired of looking at this unintelligible output in response to button presses on the remote.
If the nec
protocol does not work, open a second session and
change the protocol as the super user as shown above (or see the last section below). The change is
immediate so you can keep on clicking on the remote to see if
cat
reports an event in the first session.
One final remark at this juncture. The user, zero
here,
probably needs to be a member of the input
group to read
from the events queue. By default, this is true in Armbian.
Wondering where cir
comes from? I was. linux-sunxi.org had a reference to a Wikipedia page Consumer
IR which I found informative. Among other things it mentioned that "CIR
is the most common type of free-space optical communication" with a link to
the latter. Here we go, down the rabbit hole again.
The reference for all this is the section entitled mainline kernel (4.x) and sunxi-cir on the IR page at linux-sunxi.org.
Getting the Remote Scan Codes
The binary output that was read from the event0
input
is not easily interpreted. Of course there is a program to make things
easier.
Here is some information about the supported event types. As can be seen there are two types of events being reported:
- EV_MSC
- Described as "miscellaneous input data" because these events do not fall in the other defined categories such as EV_KEY (keyboard, buttons...), EV_REL (mouse movements), EV_SW (switches).
- EV_SYN
- Describe as "markers to separate events".
Each event type has a set of codes identifying events. SYN_REPORT is one of 4 codes associated with EV_SYN and it is "synchronize[s] and separate[s] events into packets of input data changes occurring at the same moment in time". This is not very meaningful here but it could be useful to differentiate between X and Y movements of the mouse that occurred simultaneously and two sequential movements along the X and Y axis.
The code MSC_SCAN is not defined in the documentation but it is obviously
refering to a scan code generated by the input device. The scan code
associated with each button on the IR remote is reported in the
value
field. The time
field is probably the
MSC_TIMESTAMP code which is the "number of microseconds elapsed since the
last reset". It is supposed to be 32 bit unsigned integer value, so it is not
immediately clear how the value should be read.
It did not take very long to create the following table to link the generated scan codes with the name of the buttons on the remote.
Code | Button | Code | Button | |||
---|---|---|---|---|---|---|
0x52 | 82 | 0 | 0x46 | 70 | UP | |
0x16 | 22 | 1 | 0x40 | 64 | OK | |
0x19 | 25 | 2 | 0x15 | 21 | DOWN | |
0x0d | 13 | 3 | 0x43 | 67 | RIGHT | |
0x0c | 12 | 4 | 0x44 | 68 | LEFT | |
0x18 | 24 | 5 | 0x42 | 66 | * | |
0x5e | 94 | 6 | 0x4a | 74 | # | |
0x08 | 8 | 7 | ||||
0x1c | 28 | 8 | ||||
0x5a | 90 | 9 |
Python Prerequisites
Python 3.5 is installed by default in Armbian
Strecth. However this is not the case for pip3
, the
Python package installer, and its associated tools which will be used to
install the needed library.
Now pip3
(pip
for Python 3) can be used
to get the needed library.
That is the only library that will be used here.
Handling IR Events
Documentation for the evdev
library can be found at Read the Docs. What follows is mostly a rehash of parts
of the Reading events tutorial with slight
modifications for the particulars in this context. I will begin with an
interactive Python session to test everything.
Press the keyboard combination CtrlC to interrupt the loop. The first field displayed by
evdev
when a remote event is read is a time code. Each button
press seems to come as two events, a button press with
code=type=04
and a synchronization event (EV_SYN
)
with code=type=00
which is not of much use. It can easily be
striped out.
A Short Python Script
This simple script will read all events from the input queue, and display the name of the buttons pressed on the IR remote based on their scan codes.
Download version: test_ir.py.
If I had written this in Pascal, I would have created an array of records, with the scan code as the first field and the button name as the second field. The array would be sorted by the scan code and a binary search would be used to find the button name given the scan code. It remains for me to find the idiomatic way of doing this in Python.
The output of the script can be seen below.
The script can be made to behave as if it is a program. That is the
purpose of the shebang line stating with #!
at the top of
the file. However in Linux the file has to be marked
as executable. In Windows the .py
extension should be sufficient as it should be associated with the
Python interpreter in the registry. The extension plays no role in Linux.
There is an asynchronous version of the script. Using a second session
running htop
to monitor each script in turn, I was surprised to
find that the asynchronous version seemed a little slower in handling button
presses. However neither version consumed any appreciable processor time when
no remote button was pressed and processing a sequence of events related to a
button press took barely 1% of the processor time with the slower
asynchronous implementation.
This is just verification that it is possible to handle at least some types of IR remotes without LIRC. The script is not really usable as it is because the repeated codes from the remote accumulate in the event queue. Either time codes need to be used or the event queue needs to be read one event at a time, flushing the queue as needed to ignore spurious repeated codes. This could be the subject of a future post perhaps.
Setting the IR Protocol Permanently
I glossed over the problem of setting the IR protocol to be used by the
sunxi-cir
module at the beginning of this post. Why was it
necessary to run a shell as root
using the su
command? In other words, why does the sudo
command not work? It
has to do with file permissions and persistence of the sudo
command.
As can be seen, writing to the protocols
file can only be
done by the owner of the file which is root
. The
echo
command is performed as user root
because of the
sudo
prefix, but writing the output of echo
to the file is being done as user zero
who does not have
that privilege. This reminds me of an error I sometimes make. The
combined command
will not work because only the first one is done as user root
.
It has to be written as follows.
Something akin to that has to be done. This is a well-known problem discussed at length in a stackoverflow exchange since 2008. Here is one of two generally agreed upon solutions to the problem.
This is understandable. The output of echo
is fed into
the input of tee
. That command is a filter that writes its
input to the specified file. The sudo
command means that
root
executes tee
which will have the needed write
privilege. Getting that straightened out was important because the Orange Pi Zero
is to be run as a headless server. It will not be possible to manually
set the IR protocol. Instead that is done as a cron
task
on each reboot.
Add the following line at the bottom of the file.
Luckily, tee
was available on Armbian but what can be done on a Linux
without that filter? The other proposed solution has root
create
a shell to which is passed the echo
command that worked when run
by root
.
Many prefer this solution as it does not involve a third program. There is a little caveat when multiple protocols need to be enabled.
Just make sure that single quotes are used around all the protocols to
be echoed to avoid confusion with the double quotes around the command passed
on to the shell. I suppose this version could just as easily used in the
cron
task.
Clearly there is some "magic" surrounding the protocols
file. Let's create a file named prots
with the same content
and same permissions as the original protocols
. When
the same echo
command is used to enable a protocol, the content
of prots
is just replaced.
Clearly access to the protocols
file is not direct. But how
is this done? I clearly have a lot to learn about Linux.
My Very Own IR Protocol Setter
I was inspired by hololeap's answer on the stackoverflow
exchange and decided to create a bash function that combined setting
and displaying IR protocols in one command. Edit the shell configuration
file .bashrc
in the home directory.
Just add the following lines and save the modified file.
The session has to be stopped and restarted for the command to be available or run the exec bash command. Instead of a "man page" explaining how to use it, here is an exhaustive list of examples.
- Display the supported and enabled IR protocols
- zero@opi:~$ irp IR protocols: rc-5 nec rc-6 [jvc] sony rc-5-sz sanyo sharp mce_kbd xmp imon [lirc]Enabled protocols are surrounded by [] brackets.
lirc
is always enabled. - Enable a single IR protocol
- zero@opi:~$ irp nec IR protocols: rc-5 [nec] rc-6 jvc sony rc-5-sz sanyo sharp mce_kbd xmp imon [lirc]
- Enable a supplementary IR protocol
- zero@opi:~$ irp +jvc IR protocols: rc-5 [nec] rc-6 [jvc] sony rc-5-sz sanyo sharp mce_kbd xmp imon [lirc]
- Disable a single IR protocol
- zero@opi:~$ irp IR protocols: rc-5 [nec] rc-6 [jvc] sony rc-5-sz sanyo sharp mce_kbd xmp imon [lirc] zero@opi:~$ zero@opi:~$ irp -jvc IR protocols: rc-5 [nec] rc-6 jvc sony rc-5-sz sanyo sharp mce_kbd xmp imon [lirc]
- Combine multiple operations
- zero@opi:~$ irp IR protocols: rc-5 [nec] rc-6 [jvc] sony rc-5-sz sanyo sharp [mce_kbd] xmp imon [lirc] zero@opi:~$ zero@opi:~$ irp "-jvc +sony +sanyo" IR protocols: rc-5 [nec] rc-6 jvc [sony] rc-5-sz [sanyo] sharp [mce_kbd] xmp imon [lirc]
- Disable all IR protocols
- zero@opi:~$ irp lirc IR protocols: rc-5 nec rc-6 jvc sony rc-5-sz sanyo sharp mce_kbd xmp imon [lirc]