I’ve had a little 3 key Pimoroni Keybow sat on my desk for a while. It was running the same basic config I had setup when I bought it, namely mapping the three buttons to volume down, mute and volume up respectively.

While this was useful, it felt like there where better uses for it.
With more and more time being spent in video meetings having quick shortcuts to mute the mic or toggle the camera on/off sounded like a good idea. But then I wondered if I could find a way to switch the key mapping on the fly.
The key mapping is done by editing a short Lua script. This is stored on the sdcard that the Pi Zero that holds the Keybow boots from. This means the layout is normally fixed. Except the latest version (0.0.4) of the sdcard image on the Pimoroni Github page added support for starting a USB serial link as well as the HID used to send the keyboard events. This is exposed in the Lua environment so I managed to build the following script.
require "keybow"
-- Keybow MINI volume/zoom controls --
function setup()
keybow.use_mini()
keybow.auto_lights(false)
keybow.clear_lights()
keybow.set_pixel(0, 0, 255, 255)
keybow.set_pixel(1, 255, 0, 255)
keybow.set_pixel(2, 0, 255, 255)
end
-- Key mappings --
state = 'zoom'
function handle_minikey_02(pressed)
if state == 'zoom' then
if pressed then
keybow.set_modifier(keybow.LEFT_ALT, keybow.KEY_DOWN)
keybow.tap_key("v")
keybow.set_modifier(keybow.LEFT_ALT, keybow.KEY_UP)
end
elseif state == 'media' then
keybow.set_media_key(keybow.MEDIA_VOL_UP, pressed)
end
end
function handle_minikey_01(pressed)
if state == 'zoom' then
if pressed then
keybow.set_modifier(keybow.LEFT_ALT, keybow.KEY_DOWN)
keybow.tap_key("a")
keybow.set_modifier(keybow.LEFT_ALT, keybow.KEY_UP)
end
elseif state == 'media' then
keybow.set_media_key(keybow.MEDIA_MUTE, pressed)
end
end
function handle_minikey_00(pressed)
if state == 'zoom' then
if pressed then
keybow.set_modifier(keybow.LEFT_ALT, keybow.KEY_DOWN)
keybow.tap_key("n")
keybow.set_modifier(keybow.LEFT_ALT, keybow.KEY_UP)
end
elseif state == 'media' then
keybow.set_media_key(keybow.MEDIA_VOL_DOWN, pressed)
end
end
local function isempty(s)
return s == nil or s == ''
end
function tick()
local line
line = keybow_serial_read()
if not isempty(line) then
-- keybow_serial_write( line .. "\n" )
if line == 'zoom' then
keybow.clear_lights()
keybow.set_pixel(0, 0, 255, 255)
keybow.set_pixel(1, 255, 0, 255)
keybow.set_pixel(2, 0, 255, 255)
state = 'zoom'
elseif line == 'media' then
keybow.clear_lights()
keybow.set_pixel(0, 255, 0, 255)
keybow.set_pixel(1, 0, 255, 255)
keybow.set_pixel(2, 255, 0, 255)
state = 'media'
end
end
end
The serial port gets setup on /dev/ttyACM0
on my laptop so I’m toggling between the 2 modes with echo media > /dev/ttyACM0
and echo zoom > /dev/ttyACM0
.
In media mode it works exactly the same as before, but in zoom mode it toggles the camera on/off, toggles mute on/off and cycles through the available cameras.
This worked but keybow_serial_read()
call added 1 second of latency to each call to the tick
function which really wasn’t great as it was possible to miss key presses.
A bit of digging in the git rep turned up the file that implemented the serial access and this bit of code:
int serial_open(){
if(port_fd > -1) return 0;
port_fd = open(KEYBOW_SERIAL, O_RDWR);
if(port_fd > -1){
printf("Open success\n");
tcgetattr(port_fd, &termios);
termios.c_lflag &= ~ICANON;
termios.c_cc[VTIME] = 10;
termios.c_cc[VMIN] = 0;
tcsetattr(port_fd, TCSANOW, &termios);
}
return 0;
}
The termios.c_cc[VTIME] = 10;
was what was causing the delay. I rebuilt the library changing the value to 1
and 0
. The value is in deciseconds (1/10 seconds)
With 1
the delay was cut to a tenth of a second, which was OK, but meant you had to be very deliberate in pushing the button to make sure it didn’t get missed, which with a mute toggle is a little risky.
With 0
it worked perfectly.
The script also changes the backlight colour for the keys based on mode so I can see which is active. It should be possible to add more modes as needed.
Next up is to see if I can script the toggling the mode based on if Zoom is the currently active window. Looks like it should be possible with tools like xprop
or xdotool
.