Using rotary encoders for keyboard emulation (RPi)

> Coding, hacking, computer graphics, game dev, and such...
User avatar
fips
Site Admin
Posts: 170
Joined: Wed Nov 12, 2008 9:49 pm
Location: Prague
Contact:

Using rotary encoders for keyboard emulation (RPi)

Post by fips »

I've been using the official Raspberry Pi 7" touchscreen display as a home dashboard (running Domoticz in Chromium kiosk mode) for a couple of years now, overall with great success. Recently, however, the capacitive touchscreen started to act up, and gradually became totally unresponsive. This accelerated my long-standing wish of making the whole setup more tactile and human friendly by employing a rotary encoder for content scrolling, as an alternative to touch or keyboard navigation, which has always frustrated me a bit for its clumsiness. So I set to work and shortly after came up with this:

Image

For this particular project, I've decided to buy a bunch of pre-built rotary encoder modules on eBay, instead of using just bare encoders. The module has the advantage of including an on-board debouncing circuit (a kind of RC filter to my understanding). So no additional components are needed, and from my testing the encoder readings seem quite stable. This saves quite a bit of effort.

Image

So the actual wiring is pretty simple. Just connect encoder's pins: VCC, GND, S1(A), S2(B), to the RPi ones: 3V3(Pin1), Ground(Pin9), GPIO17(Pin11), GPIO27(Pin13), like so:

Image

As for the content scrolling, I've found out that the simplest and most flexible way is to just simulate UP and DOWN key presses each time the encoder's shaft rotates in the respective direction (the fact that a key press wakes up the display is a nice bonus here and makes the whole setup responsive and joy to use). For the keyboard emulation, we will make use of the uinput kernel module, which needs to be enabled first, by editing '/etc/modules', like so:

Code: Select all

$ sudo nano /etc/modules
then add 'uinput' at the end, in my case '/etc/modules' looks like this:

Code: Select all

# /etc/modules: kernel modules to load at boot time.
#
# This file contains the names of kernel modules that should be loaded
# at boot time, one per line. Lines beginning with "#" are ignored.

i2c-dev
uinput
then reboot, after that install Python's uinput module, like so:

Code: Select all

$ sudo pip install python-uinput
Finally we need some Python code to glue all this together. I've spent some time figuring out how rotary encoders work and mainly how to make use of interrupts for edge detection instead of polling encoder's state, this is quite important, because polling would needlessly burn your CPU cycles, which is not a good idea especially on cooperative platforms like RPi. Anyway, the code came out like this (it needs to be run as sudo):

Code: Select all

#!/usr/bin/env python
#[ROTKEY BY FIPS @ 4FIPS.COM, (c) 2018 FILIP STOKLAS, MIT-LICENSED]

import RPi.GPIO as GPIO
import uinput
from time import sleep

pin_a = 11 # GPIO 17
pin_b = 13 # GPIO 27

GPIO.setmode(GPIO.BOARD)
GPIO.setup(pin_a, GPIO.IN)
GPIO.setup(pin_b, GPIO.IN)

device = uinput.Device([uinput.KEY_UP, uinput.KEY_DOWN])
seq_a = seq_b = 0

def on_edge(pin):
    global seq_a, seq_b
    a = GPIO.input(pin_a)
    b = GPIO.input(pin_b)
    seq_a = ((seq_a << 1) | a) & 0b1111
    seq_b = ((seq_b << 1) | b) & 0b1111
    if seq_a == 0b0011 and seq_b == 0b1001:
        device.emit_click(uinput.KEY_UP)
    elif seq_a == 0b1001 and seq_b == 0b0011:
        device.emit_click(uinput.KEY_DOWN)

GPIO.add_event_detect(pin_a, GPIO.BOTH, callback=on_edge)
GPIO.add_event_detect(pin_b, GPIO.BOTH, callback=on_edge)

try:
    while True:
        sleep(3600)
except KeyboardInterrupt:
    print("...DONE")
    GPIO.cleanup()
Overall, I'm pretty happy with the result. It patiently sits on my shelf, providing instant access to my home automation system.

Related articles:
Home automation with Domoticz, ESP8266 and BME280
Controlling the TP-Link HS110 smart plug with Domoticz