Rnbo & Raspberry Pi Keyboard

Jean-Francois Charles's icon

Hi,
I would like to control a rnbo process loaded in rPi with a USB keyboard connected to rPi.
I guess that one of the simplest ways is to use a Python script that would send me ASCII values through OSC to update some rnbo param. Is there any such example around?

In addition, what if I want the script to execute automatically when I boot the rPi (the rPi is loading automatically the rnbo process, what should I do to have the rPi also load the script). Could someone point me in the right direction / tutorial?

Thanks!

Jean-Francois Charles's icon

Also, if there is an example somewhere of a script allowing preset selection using a keyboard connected to the rPi, I'd be curious to study it.
Thanks.

Jan M's icon

Hello JEAN-FRANCOIS,

For the automatic execution of a script at startup (one of several possibilities):

Step 1: configure your Pi to automatically log is as a certain (let's say the user is pi)

Step 2: in the console/terminal go to the home folder of user pi: cd /home/pi

Step 3: list all files in the directory including the hidden files that start with a dot (.): ls -la
and look if there is a .bashrc file (it should be)

Step 4: open the file .bashrc (vi/vim/nano ar any other texteditor: e.g: nano .bashrc) and add there command you use to run the script at the end.

The .bashrc file is automatically executed when a user opens a terminal session. it is used to to configure the bash for that account and can be used of other things as well that need to be loaded on a session start.

Just be aware if you do it this way, each bash session will start a new thread running that script. so if you want to login on ssh for example to work on your Pi, you'll have to quit the thread.

As for reading keyboard input: you would need to read the keyboard input in your script and send the value by OSC to a param of the rnbo-runner. The information/documentation is a bit scattered throughout the rnbo webpages.

Here are some possible starting points:

the last article shows how to send OSC messages to the rnbo-runner locally, you would need to modify the script to read/send keyboard input instead of GPIO pins

Jan M's icon

the OSC command for preset loading should be

/rnbo/inst/0/presets/load <numeric-preset-index>

Jean-Francois Charles's icon

Jan, thanks for this. Very useful information & details.

Alex Norman's icon

IIRC, presets are loaded by name:

/rnbo/inst/0/presets/load <preset-name>

If you look in the http JSON response, you should see the description:

                    "load":{
                      "FULL_PATH":"/rnbo/inst/0/presets/load",
                      "TYPE":"s",
                      "VALUE":"",
                      "ACCESS":2,
                      "CLIPMODE":"none",
                      "DESCRIPTION":"Load a preset with the given name"
                    },

Alex Norman's icon

We use "systemd" to automatically run the oscquery runner and a few other services on the pi.

You could consider adapting something like that, pointing at your script, for setting up your keyboard forwarding service.

Jean-Francois Charles's icon

Thanks Alex for the precisions. I saw that the runner was not started in .bashrc, and was going to ask how it was started. Good to know it is systemd.

Jean-Francois Charles's icon

Thanks again for the help. I'm getting somewhere. I can modify RNBO params with keys pressed on my computer when I'm ssh-ing the rPi. I can also launch the script with .bashrc (just added the line python + script name at the end).
Now, I don't know how to make it work with the rPi keyboard. When I stop ssh and restart the rPi, the keys pressed on the rPi keyboard don't work.
Let me know if you have any pointer. Here is the kind of python code I use:

import tty, sys, termios
import liblo as OSC
import sys
try:
  target = OSC.Address(1234)
except OSC.AddressError as err:
  sys.exit()
filedescriptors = termios.tcgetattr(sys.stdin)
tty.setcbreak(sys.stdin)
x = 0
try:
  while True:
    x=sys.stdin.read(1)[0]
if x == "r":
      print("key r pressed")
      OSC.send(target, "/rnbo/inst/0/params/mod/normalized", 0.5)
    if x == "w":
      print("key w pressed")
      OSC.send(target, "/rnbo/inst/0/params/mod/normalized", 0.)
# back to cooked mode
except KeyboardInterrupt:
  termios.tcsetattr(sys.stdin, termios.TCSADRAIN, filedescriptors)
Jean-Francois Charles's icon

Hmm... Maybe the .bashrc is not executed at rPi start, actually, but only when I open a shell window (ssh in my case). Will investigate...

Jean-Francois Charles's icon

Updated my .bashrc's last line adding sudo: sudo python name.py
Now, when I ssh after a reboot, I can see this error:

Running at boot
Traceback (most recent call last):
File "/home/pi/jfc-keys-02.py", line 2, in <module>
import liblo as OSC
ModuleNotFoundError: No module named 'liblo'
Jan M's icon

Hmm interesting - and a bit counter intuitive that the module is not found when running with more permissions... But then, it's python so coherence is as bit of a luxury :)

I found this thread, maybe it helps:

Jean-Francois Charles's icon

Indeed, I don't have any error when running the script without sudo.
My problem remains that things are working fine with my computer keyboard from ssh, but when 'off ssh', it doesn't work with the rPi keyboard...
There is clearly something I don't understand.

Jan M's icon

I don't have an answer, but is seems that when running via ssh the keyboard input is graded by the tty (serial?) interface of the console using stdin. when you run without ssh, I don't know where the keyboard is sending its data to and where/how to grab them.

and maybe there are alternative libraries available worth trying like this one:

Jean-Francois Charles's icon

Great, thanks for these links. I made a version using the pygame library, but I'm afraid it doesn't work when you don't have a display connected to the pi: keystrokes are detected only if you first create a window, but I get an error message when creating the window - I think that's because my pi doesn't have a display. Will try something else, I'll check your links - your first link is exactly about that!

Jan M's icon

the error might be because if you are running the Pi headless, meaning without starting a desktop then there is also no window-server (x-org or wayland on linux) running to create a window.

Jean-Francois Charles's icon

Indeed, headless is another limitation.
I finally made it work using the 'keyboard' library (sudo pip install keyboard).
Then, running the process as root (the keyboard library needs the user to be root).
Now, I had to reinstall liblo3 from root (sudo) otherwise I had a ModuleNotFound error.
So, finally, it's working with code like this (with this, I'm not handling the numerous keystrokes we get even with a short key press, but I can work on that later). Thanks a lot for the help.

import keyboard
(...) 
 try:
 while True:
  if keyboard.is_pressed('r'):
    print("key pressed: r")
    OSC.send(target, "/rnbo/inst/0/params/mod/normalized", 0.5)
  elif keyboard.is_pressed('w'):
    print("key pressed: w")
    OSC.send(target, "/rnbo/inst/0/params/mod/normalized", 0.)
except KeyboardInterrupt:
 print("exiting cleanly...")
Jean-Francois Charles's icon

Following-up: now tested preset recall.
Indeed, thanks Alex Norman for this one: you recall the preset with the snapshot name. If 2 snapshots for the [rnbo~] object in the parent Max patch are called first and second, then you can recall them with:

while True:
  if keyboard.is_pressed('1'):
  OSC.send(target, "/rnbo/inst/0/presets/load", "first")
  elif keyboard.is_pressed('2'):
  OSC.send(target, "/rnbo/inst/0/presets/load", "second")
Jan M's icon

Thank's for sharing all this, Jean-Francois,

lots of useful information that came together in this thread!

Jean-Francois Charles's icon

Here for a more elegant version. With if keyboard.is_pressed(), we get lots of hits even with a short key press because the loop is executing fast. This better version generates just one event at the moment the key is pressed down.

import liblo as OSC, sys, keyboard
try:
  target = OSC.Address(1234)
except OSC.AddressError as err:
  sys.exit()
def onkeypress(event):
  if event.name == '1':
    OSC.send(target, "/rnbo/inst/0/presets/load", "first")
  elif event.name == '2':
    OSC.send(target, "/rnbo/inst/0/presets/load", "second")
# hook event handler
keyboard.on_press(onkeypress)
try:
  while True:
    pass
  except KeyboardInterrupt:
    print("exiting cleanly...")
finally:
# stop receiving the callback
  keyboard.unhook_all()