OctoPrint with USB Keyboard

I need to control my printer(s) with a wireless keyboard. I can plug the keyboard into the host port of the PI and see it in Debian.
I need to:-

  1. Prevent any other application including the Debian console from getting the usb input from that particular device (and that device only).
  2. Decode the Usb HID information to pipe it to the OctoPrint server (in the backend)
  3. Have the server send the relevant command to Marlin (and I might have to modify Marlin 2.0) to perform the required task.

I already know that octoprint's web interface can accept keyboard input but I want to map those functions (as is) to a physical keyboard that is plugged in.

Can anyone help with any of those 3 requirements. I'll be publishing my solution, assuming it's practical, to git hub for all to use afterwards.

This only works with a compatible browser and the control tab active. So it's still really being controlled by the remote client, not a local keyboard on the pi.

I think in order to do what you're proposing you would have to run a background service of some kind to middle-man the keyboard input. You might be able to develop something using pynput or something similar. I've never worked with this library, just found it doing a cursory search.

Found another possibility from the linux side for dealing with #1. The program that you would run would handle sending the api commands to octoprint.

1 Like

I'm not sure I follow the one about using getty. If I understand it, you have to create a login session then pipe the data from the keyboard to it?

I haven't tried it yet but the way I understand it you are redirecting the keyboard input to the script in the command and turning off the input to the console. I will preface this with the fact that I'm not a linux guy and that might break something else. I was able to put together a python script that would echo back the keyboard input using the keyboard module.

import keyboard

def on_press(key):
	try:
		print('alphanumeric key {0} pressed'.format(key.name))
	except AttributeError:
		print('special key {0} pressed'.format(key))

keyboard.on_press(on_press)

Ah, but the objective isn't to remove every keyboard's input altogether, just a specific device. I can see the data that comes from it and it's possible to hijack it so that nothing else can access it. But I am sure that some linux person out there has a process that can decode usb hid keyboard events and spit out some key down and key up events for me. I should be able to pipe that to OctoPrint somehow..

@foosel Is it proper to have an Octoprint plugin that installs itself as a service on the OctoPI?

I want to try and make a plugin that installes the Dotnet Core 3.0 framework, then downloads the code from git hub, compiles it and installs itself as a service. It will then communicate with OctoPrint via the API;

Since that involves changes outside of OctoPrint, no. Any such changes should at the very least involve asking the user, and preferably prompt them to do the required steps (e.g. via a provided installer script). See also the plugin registration checklist:

  • Your plugin doesnโ€™t attempt to modify the userโ€™s system without their knowledge, e.g. by trying to install additional system packages, services or the like. If your plugin needs additional steps like this to function, add a wizard dialog that prompts the user to do these things, do not do them automatically.

    Exception: Fetching additional Python dependencies from the Python Package Index through plugin_requires in your setup.py is fine.

Ok then I should do this as being totally independent to OctoPrint and just mention that it can control OctoPrint even though the configuration interface might be in OctoPrint.

1 Like
xinput list
# Note the ID of the keyboard in question
xinput float 13 # For example, if the ID was 13
# This should stop this keyboard's input from being directed to the window with keyboard focus

Create a udev rule so that a non-root can read from it (noting that you'd need to adjust for your device's idVendor and idProduct - see dmesg to find this out):

SUBSYSTEM=="input", ATTRS{idVendor}=="1d57", ATTRS{idProduct}=="001c" MODE="0644"

Format of event.

ls -l /dev/input/by-path/*

:frowning: Got my hopes up there Guru.

xinput requires the x gui subsystem. Won't work in console only. Nice to know though.

My bad.

I could actually use all this myself given this project with mouse instead of keyboard.

The udev and link below that should still be pertinent for you.

1 Like

Yeah, I think the pynput module I originally posted also requires that. However, I think the keyboard module has a means somehow to not if ran with the sudo command and therefore possible when run as a service, but again I haven't tested this myself. I may do some testing this weekend on it to see if it works or not. From the perspective of killing the keyboard input to the rest of the system, why would that be an issue. From what I was reading in the post "Redirect physical keyboard input to SSH" linked above you could still SSH to the device separately and still run commands etc, and even use the screen command to monitor what keyboard input/entry was doing.

I found a utility called usbhid-dump. I think that if I can get a python wrapper around it, I might be able to make a workable solution. (Unfortunately I don't know python :slight_smile:) Time to learn I guess.

How do I test python scripts in OctoPrint anyways?

EDIT: Crap this thing requires root privileges. Does OctoPrint run with root priv? (i'm guessing not)

http://manpages.ubuntu.com/manpages/xenial/man8/usbhid-dump.8.html

Look in the /etc/sudoers.d folder for some files which alter the instances in which the pi user, for example, can do password-less sudo. (Go and do likewise.)

Alright so far I have this linux command, which must be runs as admin:

usbhid-dump -d 248a:8367 -es -t 0 | egrep '^\s[0-9A-F]'

usbhid-dump : is the program that that allows for listening to the keyboard.
-d 248a:8367 : is a filter for a specified device by manufacture and product id. If omitted then every keyboard will produce events.
-es -t 0 : start capture, indefinitely.
| egrep '^\s[0-9A-F]' : from that output, filter out the data that we need

which produces the output:

00 00 04 00 00 00 00 00
00 00 00 00 00 00 00 00

From which I've determined that this is the letter A key being depressed. Only the last six hexes are keys being depressed. If there is more than one key then the last six will produce non zero hexes.
This is from the usb 1.1 hid spec (https://www.usb.org/sites/default/files/documents/hid1_11.pdf pg73)

While I understand what's going on in "printer.py" conceptually, how do I call printer.jog from an external python script? (assuming I use a python script to box up all this stuff)

EDIT: Or would a plugin have "access" to call the printer.jog function internally?

1 Like

A plugin would have access to the injected property self._printer that would have access to all the commands. One of them being jog, basically any of the functions listed at the link below.

http://docs.octoprint.org/en/master/modules/printer.html#octoprint.printer.PrinterInterface

2 Likes

Ok I managed to write a python script to detect the key presses:

# coding: utf-8
import subprocess
import re
usbDump = subprocess.Popen(["usbhid-dump", "-d 248a:8367","-es","-t 10000"], stdout = subprocess.PIPE)

checkForKeys ={
        0x50: "โ†",
        0x52: "โ†‘",
        0x4F: "โ†’",
        0x51: "โ†“",
        0x28: "Ok"
    }
try:
    while True:
      m = re.search("(\s[0-9A-F]{2}){8}", usbDump.stdout.readline())
      if m :
          keyData = m.group(0).split()
          keyData = [int(i,16) for i in keyData]
          for i in range(len(keyData)):
              if keyData[i] in list(checkForKeys):
                  print(checkForKeys.get(keyData[i], ""))
             
finally:
    usbDump.kill()

Before I try and turn this into a plugin, does it all look sorta sane?

Check to make sure that the keypresses are the same if the user has the CAPSLOCK toggled to ON (and you might try the NumLock ON/OFF as well). I don't know if readline() is well-behaved but you might want to add a little delay in there inside that loop to allow some breathing room.

Yes the key presses would be the same. The keyboard doesn't not control capitalization; the host does. the usb hid spec only specifies the key numbers that are depressed. In fact if you press caps lock, it is up to the host to send back a message to turn the caps lock light on. Same goes for num lock and scroll lock.