Latest posts for tag hw
Handling keyboard-like devices
I acquired some unusual input devices to experiment with, like a CNC control panel and a bluetooth pedal page turner.
These identify and behave like a keyboard, sending nice and simple keystrokes, and can be accessed with no drivers or other special software. However, their keystrokes appear together with keystrokes from normal keyboards, which is the expected default when plugging in a keyboard, but not what I want in this case.
I'd also like them to be readable via evdev and accessible by my own user.
Here's the udev rule I cooked up to handle this use case:
# Handle the CNC control panel
SUBSYSTEM=="input", ENV{ID_VENDOR}=="04d9", ENV{ID_MODEL}=="1203", \
OWNER="enrico", ENV{ID_INPUT}=""
# Handle the Bluetooth page turner
SUBSYSTEM=="input", ENV{ID_BUS}=="bluetooth", ENV{LIBINPUT_DEVICE_GROUP}=="*/…mac…", ENV{ID_INPUT_KEYBOARD}="1" \
OWNER="enrico", ENV{ID_INPUT}="", SYMLINK+="input/by-id/bluetooth-…mac…-kbd"
SUBSYSTEM=="input", ENV{ID_BUS}=="bluetooth", ENV{LIBINPUT_DEVICE_GROUP}=="*/…mac…", ENV{ID_INPUT_TABLET}="1" \
OWNER="enrico", ENV{ID_INPUT}="", SYMLINK+="input/by-id/bluetooth-…mac…-tablet"
The bluetooth device didn't have standard rules to create /dev/input/by-id/
symlinks so I added them. In my own code, I watch /dev/input/by-id
with
inotify to handle when devices appear or disappear.
I used udevadm info /dev/input/event…
to see what I could use to identify the
device.
The Static device configuration via udev page of libinput's documentation has documentation on the various elements specific to the input subsystem
Grepping rule files in /usr/lib/udev/rules.d
was useful to see syntax
examples.
udevadm test /dev/input/event…
was invaluable for syntax checking and testing
my rule file while working on it.
Finally, this is an extract of a quick prototype Python code to read keys from the CNC control panel:
import libevdev
KEY_MAP = {
libevdev.EV_KEY.KEY_GRAVE: "EMERGENCY",
# InputEvent(EV_KEY, KEY_LEFTALT, 1)
libevdev.EV_KEY.KEY_R: "CYCLE START",
libevdev.EV_KEY.KEY_F5: "SPINDLE ON/OFF",
# InputEvent(EV_KEY, KEY_RIGHTCTRL, 1)
libevdev.EV_KEY.KEY_W: "REDO",
# InputEvent(EV_KEY, KEY_LEFTALT, 1)
libevdev.EV_KEY.KEY_N: "SINGLE STEP",
# InputEvent(EV_KEY, KEY_LEFTCTRL, 1)
libevdev.EV_KEY.KEY_O: "ORIGIN POINT",
libevdev.EV_KEY.KEY_ESC: "STOP",
libevdev.EV_KEY.KEY_KPPLUS: "SPEED UP",
libevdev.EV_KEY.KEY_KPMINUS: "SLOW DOWN",
libevdev.EV_KEY.KEY_F11: "F+",
libevdev.EV_KEY.KEY_F10: "F-",
libevdev.EV_KEY.KEY_RIGHTBRACE: "J+",
libevdev.EV_KEY.KEY_LEFTBRACE: "J-",
libevdev.EV_KEY.KEY_UP: "+Y",
libevdev.EV_KEY.KEY_DOWN: "-Y",
libevdev.EV_KEY.KEY_LEFT: "-X",
libevdev.EV_KEY.KEY_RIGHT: "+X",
libevdev.EV_KEY.KEY_KP7: "+A",
libevdev.EV_KEY.KEY_Q: "-A",
libevdev.EV_KEY.KEY_PAGEDOWN: "-Z",
libevdev.EV_KEY.KEY_PAGEUP: "+Z",
}
class KeyReader:
def __init__(self, path: str):
self.path = path
self.fd: IO[bytes] | None = None
self.device: libevdev.Device | None = None
def __enter__(self):
self.fd = open(self.path, "rb")
self.device = libevdev.Device(self.fd)
return self
def __exit__(self, exc_type, exc, tb):
self.device = None
self.fd.close()
self.fd = None
def events(self) -> Iterator[dict[str, Any]]:
for e in self.device.events():
if e.type == libevdev.EV_KEY:
if (val := KEY_MAP.get(e.code)):
yield {
"name": val,
"value": e.value,
"sec": e.sec,
"usec": e.usec,
}
Edited: added rules to handle the Bluetooth page turner
Upgrading LineageOS 14 to 16
The LineageOS updater notified me that there will be no more updates for LineageOS 14, because now development on my phone happens on LineageOS 16, so I set aside some time and carefully followed the upgrade instructions.
I now have a phone with Lineageos 16, but the whole modem subsystem does not work.
Advice on #lineageos was that "the wiki instructions are often a bit generic.. offical thread often has the specific details".
Official thread is here, and the missing specific detail was "Make sure you had Samsung's Oreo firmware bootloader and modem before installing this.".
It looks like nothing ever installed firmware updates, since the Android that came with my phone ages ago. I can either wipe everything and install a stock android to let it do the upgrade, then replace it with LineageOS, or try a firmware upgrade.
This link has instructions for firmware upgrades using haimdall, which is in Debian, instead of Odin, which is in Windows.
Finding firmwares is embarassing. They only seem to be available from links on shady download sites, or commercial sites run by who knows whom. I verify sha256sums on LineageOS images, F-Droid has reproducible builds, but at the base of this wonderful stack there's going to be a blob downloaded off some forum on the internet.
In this case, this link points to some collection of firmware blobs.
I downloaded the pack and identified the ones for my phone, then unpacked the tar files and uncompressed the lz4 blobs.
With heimdall, I identified the mapping from partition names to blob names:
heimdall print-pit --no-reboot
Then I did the flashing:
heimdall flash --resume --RADIO modem.bin --CM cm.bin --PARAM param.bin --BOOTLOADER sboot.bin
The first time flashing didn't work, and I got stuck in download mode. This explains how to get out of download mode (power + volume down for 10s).
Second attempt worked fine, and now I have a working phone again:
heimdall flash --RADIO modem.bin --CM cm.bin --PARAM param.bin --BOOTLOADER sboot.bin
Ansible config for my stereo
I bought a Raspberry Pi 2 and its case. I could not reuse the existing SD card because it wants a MicroSD.
A wise person once told me:
First you do it, then you document it, then you automate it.
I had done the first two, and now I've redone the whole setup with ansible, here: stereo.tar.xz.
Stereo remote control
Wouldn't it be nice if I could use the hifi remote control to control mpd?
It turns out many wishes can come true when one has a GPIO board.
A friend of mine had a pile of IR receiver components in his stash and gave me one. It is labeled "38A 1424A", and the closest matching datasheet I found is this one.
I wired the receiver with the control pin on GPIO port 24, and set up lirc by following roughly this guide.
Enable lirc_rpi
support
I had to add these lines to /boot/config.txt
to enable lirc_rpi
support:
dtoverlay=lirc-rpi,gpio_in_pin=24,gpio_out_pin=22
dtparam=gpio_in_pull=up
At first I had missed configuration of the internal pull up resistor, and reception worked but was very, very poor.
Then reboot.
Install and configure lirc
apt install lirc
I added these lines to /etc/lirc/hardware.conf
:
DRIVER="default"
DEVICE="/dev/lirc0"
MODULES="lirc_rpi"
Stopped lircd
:
systemctl stop lirc
Tested that the receiver worked:
mode2 -d /dev/lirc0
Downloaded
remote control codes for my hifi
and put them in /etc/lirc/lircd.conf
.
Started lircd
systemctl start lirc
Tested that lirc could parse commands from my remote control:
$ irw
0000400405506035 00 CD_PAUSE RAK-SC304W
0000400405506035 01 CD_PAUSE RAK-SC304W
0000400405506035 02 CD_PAUSE RAK-SC304W
0000400405505005 00 CD_PLAY RAK-SC304W
0000400405505005 01 CD_PLAY RAK-SC304W
Interface lirc
with mpd
I made this simple lirc program and saved it in ~pi/.lircrc
:
begin
prog = irexec
button = CD_NEXT
config = mpc next
end
begin
prog = irexec
button = TAPE_FWD
config = mpc next
end
begin
prog = irexec
button = TAPE_REW
config = mpc prev
end
begin
prog = irexec
button = CD_PREV
config = mpc prev
end
begin
prog = irexec
button = TAPE_PAUSE
config = mpc pause
end
begin
prog = irexec
button = CD_PAUSE
config = mpc pause
end
begin
prog = irexec
button = CD_PLAY
config = mpc toggle
end
begin
prog = debug
button = TAPE_PLAY_RIGHT
config = mpc toggle
end
Then wrote a systemd unit file to start irexec and saved it as /etc/systemd/system/mpd-irexec.service
:
[Unit]
Description=Control mpd via lirc remote control
After=lirc mpd
[Service]
Type=simple
ExecStart=/usr/bin/irexec
Restart=always
User=pi
WorkingDirectory=~
[Install]
WantedBy=multi-user.target
Then
systemctl start mpd-irexec
to start irexec, and
systemctl enable mpd-irexec
to start irexec at boot.
Profit!
All of this was done by me, with almost no electronics training, following online tutorials for the hardware parts.
To connect components I used a breadboard and female-male jumper leads, so I didn't have to solder, for which I have very little practice.
Now the Raspberry Pi is so much a part of my hifi component that I can even control it with the hifi remote control.
Given that I disconnected the CD and tape players, there are now at least 16 free buttons on the remote control that I can script however I like.
Shutdown button for my Raspberry Pi
My Raspberry Pi hifi component setup lacked a way to cleanly shutdown the system without ssh.
I wished the Raspberry Pi had a button so I can tell it to shutdown.
I added one to the GPIO connector.
It turns out many wishes can come true when one has a GPIO board.
This is the /usr/local/bin/stereo-gpio
script that reacts to the button press
and triggers a shutdown:
#!/usr/bin/python3
# http://razzpisampler.oreilly.com/ch07.html
# http://web.archive.org/web/20160305001215/http://razzpisampler.oreilly.com/ch07.html
from RPi import GPIO
import time
import subprocess
def on_button(pin):
print("Button pressed", pin)
GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.IN, pull_up_down=GPIO.PUD_UP)
while True:
pin = GPIO.wait_for_edge(18, GPIO.FALLING)
if pin == 18:
subprocess.check_call(["/bin/systemctl", "halt"])
This is the /etc/systemd/system/stereo-gpio.service
systemd unit file that
runs the script as a daemon:
[Unit]
Description=Stereo GPIO manager
[Service]
Type=simple
ExecStart=/usr/local/bin/stereo-gpio
Restart=always
[Install]
WantedBy=multi-user.target
Then
systemctl start stereo-gpio
to start the script, and
systemctl enable stereo-gpio
to start the script at boot.
Raspberry Pi as a Hi-Fi component
I have a 25 years old Technics hifi system that still works fine, and I gave it a new life by replacing the CD player and cassette player modules with a Raspberry Pi.
Connection
Each component of the hifi has a mains input and a mains plug that is used to power the next component. The element where the main power lead goes in is the radio component, which has a remote control receiver, a watch and a timer, and will power on the rest of the system when turned on by its power button, the remote control, or the alarm function.
I disconnected the cassette and cd player modules, and plugged the Raspberry Pi phone charger/power supply in the free plug behind the amplifier module, at the end of the (now very short) power lead chain.
I also connected the audio output of the Raspberry Pi to the CD input of my stereo. The advantage of CD over AUX is that the remote control buttons for switching audio sources don't cover the AUX inputs.
With alsamixer
I adjusted the output volume to match that of the radio
component, so that I can switch between the two without surprising jumps in
volume. I used alsactl store
to save the mixer levels.
Now when I turn the hifi on I also turn the Raspberry Pi on, and when I turn the hifi off, I also cut power from the Raspberry Pi.
Operating system
Operating system install instructions:
- I downloaded a Raspbian Jessie Lite image
- I put it on an SD card
- I created an empty
ssh
file on the boot partition - I put the SD card on the Raspberry Pi and turned on the stereo.
ssh pi@raspberrypi
passwordraspberry
sudo raspi-config
to change the hostname, the password, and to enlarge the root partition to include all the rest of the space available in the SD card.
Music Player Daemon
This is the set up of the music player part, with mpd.
apt install mpd
The configuration file is /etc/mpd.conf
. The changes I made are:
Make mpd accessible from my local network:
bind_to_address "any"
Make mpd discoverable:
zeroconf_enabled "yes"
zeroconf_name "stereo"
Allow anyone who visits me to control the playlist, and only me to run admin functions:
password "SECRET@read,add,control,admin"
default_permissions "read,add,control"
At my first try, mpd hung when changing songs.
I had to disable dmix by
uncommenting the device
option in the audio_output
configuration.
use_mmap
is cargo-culted from
the archlinux wiki.
audio_output {
type "alsa"
name "My ALSA Device"
device "hw:0,0" # optional
use_mmap "yes"
}
If at some point I'll decide to use other audio software on the system, I'll probably want to play via pulseaudio.
Sending music to the stereo
I made a little script to sync the music directory on my laptop with
/var/lib/mpd/music
:
#!/bin/sh
rsync -avz --filter=". sync-stereo.filter" --copy-links --prune-empty-dirs --delete ./ pi@stereo:/var/lib/mpd/music
ssh pi@stereo "chmod u=rwX,go=rX -R /var/lib/mpd/music"
It uses this sync-stereo.filter
rules file for rsync:
hide /_archive
include */
include **.mp3
hide *
mpd clients
mpc
$ mpc -h stereo status
UltraCat - Unexpected Little Happenings
[playing] #15/22 0:03/4:06 (1%)
volume: 80% repeat: off random: on single: off consume: off
M.A.L.P.
On my phone I installed M.A.L.P. and now I have a remote control for mpd.
In its settings, I made a profile for home where I just had to set the hostname for the stereo and the admin password.
Cantata
On my laptop I installed cantata, set the hostname and password in the preferences, and had the client ready.
Profit!
Now I can take the remote control of my hi-fi, turn it on, and after a while mpd will resume playing the song that was playing when I last shut it down.
I also have realtime player status on my phone and on my laptop, and can control music from either at any time. Friends who visit me can do that as well.
Everything was rather straightforward, well documented and easy to replicate. The hardware is cheap and very easy to come by.