Skip to content

Instantly share code, notes, and snippets.

@Gadgetoid
Last active August 25, 2023 01:50
  • Star 41 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save Gadgetoid/5a8ceb714de8e630059d30612503653f to your computer and use it in GitHub Desktop.
Pi 400 KB

Raspberry Pi 400 as a USB HID Keyboard

⚠️ This gist has been deprecated. See this repo instead: https://github.com/Gadgetoid/pi400kb

Hook your Pi 400 up to your PC somehow, using a USB Type-C cable into the power port. Anker make good ones- I used a 3m white one for my tests.

Setup

Add dtoverlay=dwc2 to /boot/config.txt

Reboot!

Run sudo modprobe libcomposite

RUn wget https://gist.github.com/Gadgetoid/5a8ceb714de8e630059d30612503653f/raw/35fde8da7fcd88e7ccd3913c729f2b14bbd4a0a7/pi400kb

Run sudo ./pi400kb

YOUR PI 400 IS NOW A FREAKING KEYBOARD FOR YOUR PC WHAAAAT!?

Your keyboard input will be detached from your Pi while it's forwarded to your host computer.

Press Ctrl + Raspberry to exit and restore your keyboard on the Pi.

#include "gadget-hid.h"
#include <errno.h>
#include <stdio.h>
#include <linux/usb/ch9.h>
#include <usbg/usbg.h>
#include <usbg/function/hid.h>
#include <usbg/function/midi.h>
static char report_desc[] = {
0x05, 0x01,
0x09, 0x06,
0xA1, 0x01,
0x05, 0x07,
0x19, 0xe0,
0x29, 0xe7,
0x15, 0x00,
0x25, 0x01,
0x75, 0x01,
0x95, 0x08,
0x81, 0x02,
0x95, 0x01,
0x75, 0x08,
0x81, 0x01,
0x95, 0x03,
0x75, 0x01,
0x05, 0x08,
0x19, 0x01,
0x29, 0x03,
0x91, 0x02,
0x95, 0x05,
0x75, 0x01,
0x91, 0x01,
0x95, 0x06,
0x75, 0x08,
0x15, 0x00,
0x26, 0xff,
0x00, 0x05,
0x07, 0x19,
0x00, 0x2a,
0xff, 0x00,
0x81, 0x00, // Input (Data, Array, Abs)
0xc0 // End collection
};
int initUSB() {
int ret = -EINVAL;
int usbg_ret;
struct usbg_gadget_attrs g_attrs = {
.bcdUSB = 0x0200,
.bDeviceClass = USB_CLASS_PER_INTERFACE,
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
.bMaxPacketSize0 = 64, /* Max allowed ep0 packet size */
.idVendor = VENDOR,
.idProduct = PRODUCT,
.bcdDevice = 0x0001, /* Verson of device */
};
struct usbg_gadget_strs g_strs = {
.serial = "0123456789", /* Serial number */
.manufacturer = "Pimoroni", /* Manufacturer */
.product = "Keybow" /* Product string */
};
struct usbg_config_strs c_strs = {
.configuration = "1xHID"
};
struct usbg_f_midi_attrs midi_attrs = {
.index = 1,
.id = "usb1",
.buflen = 128,
.qlen = 16,
.in_ports = 1,
.out_ports = 1
};
struct usbg_f_hid_attrs f_attrs = {
.protocol = 1,
.report_desc = {
.desc = report_desc,
.len = sizeof(report_desc),
},
.report_length = 16,
.subclass = 0,
};
usbg_ret = usbg_init("/sys/kernel/config", &s);
if (usbg_ret != USBG_SUCCESS) {
fprintf(stderr, "Error on usbg init\n");
fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret),
usbg_strerror(usbg_ret));
goto out1;
}
usbg_ret = usbg_create_gadget(s, "g1", &g_attrs, &g_strs, &g);
if (usbg_ret != USBG_SUCCESS) {
fprintf(stderr, "Error creating gadget\n");
fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret),
usbg_strerror(usbg_ret));
goto out2;
}
usbg_ret = usbg_create_function(g, USBG_F_HID, "usb0", &f_attrs, &f_hid);
if (usbg_ret != USBG_SUCCESS) {
fprintf(stderr, "Error creating function: USBG_F_HID\n");
fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret),
usbg_strerror(usbg_ret));
goto out2;
}
usbg_ret = usbg_create_config(g, 1, "config", NULL, &c_strs, &c);
if (usbg_ret != USBG_SUCCESS) {
fprintf(stderr, "Error creating config\n");
fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret),
usbg_strerror(usbg_ret));
goto out2;
}
usbg_ret = usbg_add_config_function(c, "keyboard", f_hid);
if (usbg_ret != USBG_SUCCESS) {
fprintf(stderr, "Error adding function: keyboard\n");
fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret),
usbg_strerror(usbg_ret));
goto out2;
}
usbg_ret = usbg_enable_gadget(g, DEFAULT_UDC);
if (usbg_ret != USBG_SUCCESS) {
fprintf(stderr, "Error enabling gadget\n");
fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret),
usbg_strerror(usbg_ret));
goto out2;
}
ret = 0;
out2:
usbg_cleanup(s);
s = NULL;
out1:
return ret;
}
int cleanupUSB(){
if(g){
usbg_disable_gadget(g);
usbg_rm_gadget(g, USBG_RM_RECURSE);
}
if(s){
usbg_cleanup(s);
}
return 0;
}
#include <linux/usb/ch9.h>
#include <usbg/usbg.h>
#include <usbg/function/hid.h>
#define VENDOR 0x04d9
#define PRODUCT 0x0007
#define HID_REPORT_SIZE 8
usbg_state *s;
usbg_gadget *g;
usbg_config *c;
usbg_function *f_hid;
int initUSB();
int cleanupUSB();
CFLAGS_ALL=-I../libusbgx/build/include -I../bcm2835-1.68/build/include -L../bcm2835-1.68/build/lib -I../lua-5.4.0/src -L../libusbgx/build/lib -L../libserialport/build/lib -L../lua-5.4.0/src -lpng -lz -lpthread -llua -lm -lbcm2835 -ldl
pi400: CFLAGS+=-static $(CFLAGS_ALL) -lusbgx -lconfig -DPI400_USB
pi400: pi400.c gadget-hid.c
$(CC) $^ $(CFLAGS) -o $@
pi400test: CFLAGS+=-static $(CFLAGS_ALL) -lusbgx -lconfig
pi400test: pi400.c gadget-hid.c
$(CC) $^ $(CFLAGS) -o $@
clean:
-rm pi400
-rm pi400test
#include "pi400.h"
#include "gadget-hid.h"
#include <sys/ioctl.h>
#include <linux/hidraw.h>
#include <linux/input.h>
#include <signal.h>
#include <errno.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdio.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <unistd.h>
#define KEYBOARD_DEV "/dev/input/by-id/usb-_Raspberry_Pi_Internal_Keyboard-event-kbd"
#define HID_REPORT_SIZE 8
#define GRAB 1
#define UNGRAB 0
int hid_output;
volatile int running = 0;
int key_index = 0;
void signal_handler(int dummy) {
running = 0;
}
int find_hidraw_device() {
int fd;
int ret;
struct hidraw_devinfo hidinfo;
char path[20];
for(int x = 0; x < 16; x++){
sprintf(path, "/dev/hidraw%d", x);
if ((fd = open(path, O_RDWR | O_NONBLOCK)) == -1) {
continue;
}
ret = ioctl(fd, HIDIOCGRAWINFO, &hidinfo);
if(hidinfo.vendor == VENDOR && hidinfo.product == PRODUCT) {
printf("Found keyboard at: %s\n", path);
return fd;
}
close(fd);
}
return -1;
}
int main() {
int ret;
int fd;
int uinput_fd;
unsigned char buf[HID_REPORT_SIZE];
fd = find_hidraw_device();
if(fd == -1) {
printf("Failed to open keyboard device\n");
return 1;
}
ret = initUSB();
uinput_fd = open(KEYBOARD_DEV, O_RDONLY);
ioctl(uinput_fd, EVIOCGRAB, UNGRAB);
usleep(500000);
ioctl(uinput_fd, EVIOCGRAB, GRAB);
do {
hid_output = open("/dev/hidg0", O_WRONLY | O_NDELAY);
} while (hid_output == -1 && errno == EINTR);
if (hid_output == -1){
printf("Error opening /dev/hidg0 for writing.\n");
return 1;
}
printf("Running...\n");
running = 1;
signal(SIGINT, signal_handler);
while (running){
int c = read(fd, buf, HID_REPORT_SIZE);
if(c != HID_REPORT_SIZE){
continue;
}
for(int x = 0; x < HID_REPORT_SIZE; x++)
{
printf("%x ", buf[x]);
}
printf("\n");
write(hid_output, buf, HID_REPORT_SIZE);
usleep(1000);
if(buf[0] == 0x09){
running = 0;
break;
}
}
for(int x = 0; x < HID_REPORT_SIZE; x++){
buf[x] = 0;
};
write(hid_output, buf, HID_REPORT_SIZE);
ioctl(uinput_fd, EVIOCGRAB, UNGRAB);
close(uinput_fd);
printf("Cleanup USB\n");
cleanupUSB();
return 0;
}
#include <bcm2835.h>
#include <pthread.h>
int initUSB();
int main();
void sendHIDReport();
This file has been truncated, but you can view the full file.
@lskal
Copy link

lskal commented Jan 3, 2021

Dear Philip, I'm quite new to Linux terminal and I'm having a small issue. Where exactly do I have to put the dtoverlay=dwc2 in config? I followed your instructions but when I got to the point about runn' sudo ./pi400kb I couldn't make it work even tho using ls -a the file was there.

@Hermann-SW
Copy link

Hermann-SW commented Jul 1, 2021

Thank you for this cool project.

Trejan helped me to get it compiled (which two libs need to be placed and built under "..").
Small changes were needed in Makefile as well as pi400.c to get it compiled (remove unneeded stuff).
https://www.raspberrypi.org/forums/viewtopic.php?f=144&t=314883&p=1883955#p1883808

Later I did remove all unnecessary stuff in fork (only libusbgx is needed) and added steps to build without issues to README.md in my fork:
https://gist.github.com/Hermann-SW/19c7522220a4ec3c8d6ecc89de2b0a60

@Hermann-SW
Copy link

Hermann-SW commented Jul 3, 2021

I made mouse and trackball connected to Pi400 work for laptop as well.
But not via gadget now, but over netcat session:
https://gist.github.com/Hermann-SW/a3785309ca0b85887bd417270bc56e31

Would it be possible to transfer the event0 mouse and event3 trackball events in same gadget that transfers keyboard events?
If not, could a mouse/trackball gadget run in parallel to pi400kb gadget?

I am testing Pi400 connected trackball working with laptop for hours now, just works -- would be perfect if keyboard plus mouse/trackball could be built into one gadget:

@FarnoodID
Copy link

Hi thanks for this brilliant code. What if the host is running on windows os. How can I use RP 400 as keyboard for my laptop?

@Hermann-SW
Copy link

Either follow instructions in "Setup", or build yourself, instructions in my fork:
https://gist.github.com/Hermann-SW/19c7522220a4ec3c8d6ecc89de2b0a60
Here you can see that the read keyboard characters are not phoned somewhere else:
https://gist.github.com/Hermann-SW/19c7522220a4ec3c8d6ecc89de2b0a60#file-pi400-c-L85-L102

When you run pi400kb on the Pi400, then the Pi400 acts as USB keyboard to any computer it is connected to.
So your Windows machine will see the Pi400 just as a keyboard.

@Secreto31126
Copy link

For any newbie in linux, after downloading the file with wget you must set it as executable with this command:

chmod +x ./pi400kb

Then sudo ./pi400kb should work fine

( @lskal , that might be your issue, sorry for the ping almost a year later)

@Hermann-SW
Copy link

Hermann-SW commented Oct 13, 2021

Would be good to know whether it is possible to enhance the gadget to not only act as USB keyboard, but as USB mouse in additon?

I added IR led making Pi400 a KVS (Keyboard Video Switch) based on pi400kb and global shortcut CTRL+ALT+Q starrting pi400kb, that controls HDMI switch as remote control:

https://forums.raspberrypi.com/viewtopic.php?f=140&t=295074&p=1924474#p1924059

In case mouse events could be transmitted over "pi400kbms", Pi400 could be KVM (Keyboard Video Mouse)!

@Gadgetoid
Copy link
Author

Gadgetoid commented Oct 13, 2021

Would be good to know whether it is possible to enhance the gadget to not only act as USB keyboard, but as USB mouse in additon?

I don't see any reason why not, using pretty much the same technique of forwarding the raw HID events.

Edit: Have pulled this into a real GitHub repo and started some cleanup/build instructions - https://github.com/Gadgetoid/pi400kb

@Gadgetoid
Copy link
Author

@Hermann-SW I'd love to have your input on the new repo. I've cleaned things up a lot and switched to CMake (because I've been using it a lot lately).

@Hermann-SW
Copy link

Hermann-SW commented Oct 13, 2021

@Gadgetoid
Move to git repo from gist is a good move.
I followed instruction and was able to build, and it works.
Created new issue on two warnings:
Gadgetoid/pi400kb#5

@Gadgetoid
Copy link
Author

Hopefully I'll find the time to address some of the roadmap issues I've set up and make it easier to get other mice/keyboards working. Pretty sure a Pi 4 could also run this?

@desrod
Copy link

desrod commented Oct 24, 2021

I've searched for hours, and haven't yet found a single project that allows me to use the Pi4 400 keyboard as an actual keyboard for any device, including this frequently recommended repository, unfortunately. A very simple use case is plugging the 400 into an actual Raspberry Pi4 and using it as a keyboard for that device. Having to carry two keyboards seems silly, no?

Having to power up the Pi4 400 (eg: "boot" it) is not a workable solution, since it requires a monitor to be attached, then a console, then a login, and running the binary. Yes, you could roll that into the bootup and make it headless, but then... you're still booting the RPi4 OS just to redirect keys. You can't just plug the 400 into a machine and get a working keyboard, that's the catch here, and why this, and dozens of other hacks, bodges and workarounds fall apart.

Why can't the physical hardware itself, be presented to the USB bus of the client system, as a HID device? It's already presented as a HID device to the underlying OS itself, when booting natively on the 400. IOW, USB-A (400 side) to USB-A (client machine), should present itself as a HID device to the receiving machine. No need to power it up, boot it or run an OS on the device itself.

Why is this a hurdle nobody has crossed yet?

@Hermann-SW
Copy link

Because a Pi400 is not intended to be used as a keyboard, additional software is needed.
You outlined the solution already, headless and hook into boutup.
My Pi400 always boots as KVM switch directly into loptop it is connected to:
https://forums.raspberrypi.com/viewtopic.php?f=140&t=321840

Why not use 18$ Raspberry keyboard for your needs?
https://www.raspberrypi.com/products/raspberry-pi-keyboard-and-hub/

@Gadgetoid
Copy link
Author

Why can't the physical hardware itself, be presented to the USB bus of the client system, as a HID device? It's already presented as a HID device to the underlying OS itself, when booting natively on the 400. IOW, USB-A (400 side) to USB-A (client machine), should present itself as a HID device to the receiving machine. No need to power it up, boot it or run an OS on the device itself.

Is this a joke?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment