-
-
Save jasonwhite/c5b2048c15993d285130 to your computer and use it in GitHub Desktop.
/** | |
* Author: Jason White | |
* | |
* Description: | |
* Reads joystick/gamepad events and displays them. | |
* | |
* Compile: | |
* gcc joystick.c -o joystick | |
* | |
* Run: | |
* ./joystick [/dev/input/jsX] | |
* | |
* See also: | |
* https://www.kernel.org/doc/Documentation/input/joystick-api.txt | |
*/ | |
#include <fcntl.h> | |
#include <stdio.h> | |
#include <unistd.h> | |
#include <linux/joystick.h> | |
/** | |
* Reads a joystick event from the joystick device. | |
* | |
* Returns 0 on success. Otherwise -1 is returned. | |
*/ | |
int read_event(int fd, struct js_event *event) | |
{ | |
ssize_t bytes; | |
bytes = read(fd, event, sizeof(*event)); | |
if (bytes == sizeof(*event)) | |
return 0; | |
/* Error, could not read full event. */ | |
return -1; | |
} | |
/** | |
* Returns the number of axes on the controller or 0 if an error occurs. | |
*/ | |
size_t get_axis_count(int fd) | |
{ | |
__u8 axes; | |
if (ioctl(fd, JSIOCGAXES, &axes) == -1) | |
return 0; | |
return axes; | |
} | |
/** | |
* Returns the number of buttons on the controller or 0 if an error occurs. | |
*/ | |
size_t get_button_count(int fd) | |
{ | |
__u8 buttons; | |
if (ioctl(fd, JSIOCGBUTTONS, &buttons) == -1) | |
return 0; | |
return buttons; | |
} | |
/** | |
* Current state of an axis. | |
*/ | |
struct axis_state { | |
short x, y; | |
}; | |
/** | |
* Keeps track of the current axis state. | |
* | |
* NOTE: This function assumes that axes are numbered starting from 0, and that | |
* the X axis is an even number, and the Y axis is an odd number. However, this | |
* is usually a safe assumption. | |
* | |
* Returns the axis that the event indicated. | |
*/ | |
size_t get_axis_state(struct js_event *event, struct axis_state axes[3]) | |
{ | |
size_t axis = event->number / 2; | |
if (axis < 3) | |
{ | |
if (event->number % 2 == 0) | |
axes[axis].x = event->value; | |
else | |
axes[axis].y = event->value; | |
} | |
return axis; | |
} | |
int main(int argc, char *argv[]) | |
{ | |
const char *device; | |
int js; | |
struct js_event event; | |
struct axis_state axes[3] = {0}; | |
size_t axis; | |
if (argc > 1) | |
device = argv[1]; | |
else | |
device = "/dev/input/js0"; | |
js = open(device, O_RDONLY); | |
if (js == -1) | |
perror("Could not open joystick"); | |
/* This loop will exit if the controller is unplugged. */ | |
while (read_event(js, &event) == 0) | |
{ | |
switch (event.type) | |
{ | |
case JS_EVENT_BUTTON: | |
printf("Button %u %s\n", event.number, event.value ? "pressed" : "released"); | |
break; | |
case JS_EVENT_AXIS: | |
axis = get_axis_state(&event, axes); | |
if (axis < 3) | |
printf("Axis %zu at (%6d, %6d)\n", axis, axes[axis].x, axes[axis].y); | |
break; | |
default: | |
/* Ignore init events. */ | |
break; | |
} | |
fflush(stdout); | |
} | |
close(js); | |
return 0; | |
} |
@tomek-szczesny Thanks! I updated the Gist with your fix. Glad you found this useful.
Nice code.
You will try to put "{" and "}" at all if and else, although is not needed.
Nice!
Had to add #include <unistd.h>
to compile.
Knows all keys of SNES:
Button 0 pressed
Button 0 released
Button 1 pressed
Button 1 released
Button 2 pressed
Button 2 released
Button 3 pressed
Button 3 released
Button 4 pressed
Button 4 released
Button 5 pressed
Button 5 released
Button 8 pressed
Button 8 released
Button 9 pressed
Button 9 released
Axis 0 at ( 0, -32767)
Axis 0 at ( 0, 0)
Axis 0 at ( 32767, 0)
Axis 0 at ( 0, 0)
Axis 0 at ( 0, 32767)
Axis 0 at ( 0, 0)
Axis 0 at (-32767, 0)
Axis 0 at ( 0, 0)
@Hermann-SW Thanks. I've updated the Gist to include unistd.h
. 👍
Thank you!
I do use your joystick.c in a bash script that controls 4DoF robot arm with SNES gamepad and it works wonderful.
...
while IFS= read -r line; do
# echo "Text read from file: $line"
case $line in
"Axis 0 at (-32767, 0)")
let p=p-d
;;
"Axis 0 at ( 32767, 0)")
let p=p+d
;;
...
pigs s 8 $g; pigs s 9 $u; pigs s 10 $l; pigs s 11 $p
done < <(./joystick)
There is only a last minimal change needed for that to work, and that is flushing the output.
I would like to use your joystick.c unchanged so that I can simply point to your gist.
Can you add this one statement?
$ diff -c3 joystick.c.orig joystick.c
*** joystick.c.orig 2019-10-19 07:11:31.762769671 +0200
--- joystick.c 2019-10-19 07:12:00.047928028 +0200
***************
*** 128,133 ****
--- 128,134 ----
/* Ignore init events. */
break;
}
+ fflush(stdout);
}
close(js);
$
This is hardcoded robot arm movement sample, I can do this with SNES gamepad now as well:
https://www.raspberrypi.org/forums/viewtopic.php?f=37&t=252272&p=1553313#p1553313
@Hermann-SW Wow! That's awesome! I also added the fflush(stdout);
👍
Thanks Jason!
I created initial new github repo for 4DoF robot arm.
tools/gamepad is a bash script mixed with your current version of joystick.c, to control the robot arm.
The exact version is referenced at top of tool.
I really like the 1-liner that does compile your joystick.c in case it is not already available in /tmp/joystick:
#!/bin/bash
# contains and uses this version of Jason White's joystick.c at bottom
# https://gist.github.com/jasonwhite/c5b2048c15993d285130/eb3bcd6e1cd88002d21764b9965d075eceaf4b83
#
# requires SNES joystick connected to Raspberry USB, pigpio library installed
#
js=/tmp/joystick
if [ ! -f $js ]; then sed -n "/^\/\*\*$/,\$p" $0 | gcc -x c - -o $js; fi
g=1500
...
Note that this doesn't seem to detect the D-Pad on the Xbox One controller connected using https://github.com/atar-axis/xpadneo
For me this didn't work. The call to read
at line 31 returned -1
and perror
reported Invalid argument
. To get this to work I had to use input_event
instead of js_event
, but of course then the rest of the code breaks (for instance input_event
doesn't have a number
field).
EDIT: Nevermind! It seems I was using the wrong /dev
file. Reading from
``/dev/input/by-id/usb-Logitech_Logitech_RumblePad_2_USB-event-joystick````
gets me a stream of input_event
s (which therefore doesn't work with this code), whereas reading from
/dev/input/by-id/usb-Logitech_Logitech_RumblePad_2_USB-joystick
gets me a stream of js_event
s as expected by this code.
(Ubuntu 19.10 64 bit with a Logitech gamepad)
My dpad doesn't get detected.
My dpad doesn't get detected as well. I'm on opensuse tumbleweed, controller is a wired xbox 360 controller. When I press any dpad button I only see some axis updates, but they don't correspond at all to my actions.
@berton7 I got the dpad working. get_axis_state
only checks axis < 3, but the dpad is axis 3. You only need to make small changes to make it work. I'm using a 8bitdo SN30 controller. It should be something like this:
if (event->type == JS_EVENT_AXIS) {
if (event->number == 6 ) {
if (event->value == -32767)
{
// left
}
else if (event->value == 32767)
{
// right
}
} else if (event->number == 7) {
if (event->value == -32767)
{
// up
}
else if (event->value == 32767)
{
// down
}
}
}
Yeah I figured it out just after sending the comment, like it always happens. Thanks anyways, hopefully this will save up time to somebody else. Also it would be nice to at least leave a comment in the code about this @jasonwhite
Definitely axis 6 on my system. Axis 6 is the the "roll" of from the accelerometer. and Axis 7 is the Pitch from the accelerometer.
Thank you so much for creating this code.
In less than 30 seconds I got it working for a Thrustmaster joystick.
https://shop.thrustmaster.com/en_gb/products-joysticks/usb-joystick.html
All four buttons work
The Throttle works and reports as Axis 1
, where only the X value is updated (as expected)
The main joystick works as Axis 0
(except it seems the values stop before the end of the travel, I have looked at the CAT output directly and this would appear to be an issue with the Joystick and NOT your code)
Even the Thumb Joystick button array works well, there is a small hiccup with its results. (again, probably an issue with the joystick)
If I select "North" / "South", Axis 2 reports
Axis 2 at (-32767, 0)
Axis 2 at ( 0, 0)
Axis 2 at ( 32767, 0)
Axis 2 at ( 0, 0)
But if I select "East" / "West", axis 1 reports
Axis 1 at ( 32767, 32767)
Axis 1 at ( 32767, 0)
Axis 1 at ( 32767, -32767)
Axis 1 at ( 32767, 0)
Right up until typing this, I thought it was an issue, but of-course it isn't, I just need to accommodate Axis 2 X and Axis 1 Y as my controls.
Thanks again.
If anyone is interested, I'm trying to create a auto selecting PTZ camera controller in Node-RED using Visca over IP
for camera control and a BlackMagicDesign Atem desk as the vision mixer, so that whichever camera is in Preview will be the one to be controlled by the Joystick, unless I elect to control a LIVE camera (probably by pulling the Trigger / Button 0
(again, now I type this up, I could use any of the FOUR buttons to override the automation to select a camera.)
I'll be using the Throttle control to set the Zoom speed
(Unless I think of something else while I'm building the Node-RED flow)
Thanks again.
Stuart
Purely to make it easier in Node-RED, I have tweaked this section, to remove spaces and add :
as delimiters.
case JS_EVENT_BUTTON:
printf("Button:%u:%s:\n", event.number, event.value ? "pressed" : "released");
break;
case JS_EVENT_AXIS:
axis = get_axis_state(&event, axes);
if (axis < 3)
printf("Axis:%zu:%6d:%6d:\n", axis, axes[axis].x, axes[axis].y);
break;
Here is the start of my Node-RED flow (or at least the USB joystick part)
Which gives outputs like
5/16/2022, 10:22:03 AM[node: b5349a609d15da2e])
msg.payload : array[4]
array[4]
0: "Axis"
1: 0
2: 8445
3: 0
4: string
5/16/2022, 10:25:15 AM[node: b5349a609d15da2e])
msg.payload : array[3]
array[3]
0: "Button"
1: 2
2: "pressed"
3: "↵"
5/16/2022, 10:25:15 AM[node: b5349a609d15da2e])
msg.payload : array[3]
array[3]
0: "Button"
1: 2
2: "released"
3: "↵"
[
{
"id": "cb77dfef0d6416f5",
"type": "debug",
"z": "e66541cd9d074485",
"name": "",
"active": true,
"tosidebar": false,
"console": false,
"tostatus": true,
"complete": "true",
"targetType": "full",
"statusVal": "payload",
"statusType": "msg",
"x": 950,
"y": 440,
"wires": []
},
{
"id": "d500de06a0c0486b",
"type": "exec",
"z": "e66541cd9d074485",
"command": "./opt/joystick/joystick /dev/input/js0",
"addpay": "",
"append": "",
"useSpawn": "true",
"timer": "",
"winHide": false,
"oldrc": false,
"name": "",
"x": 671,
"y": 461,
"wires": [
[
"cb77dfef0d6416f5",
"80278a50dea8e506"
],
[],
[]
]
},
{
"id": "d87d614be93c7a79",
"type": "inject",
"z": "e66541cd9d074485",
"name": "Start Joystick app",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payloadType": "date",
"x": 311,
"y": 461,
"wires": [
[
"d500de06a0c0486b"
]
]
},
{
"id": "5644b13cd10ddaee",
"type": "inject",
"z": "e66541cd9d074485",
"name": "Stop Joystick app",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "Now",
"payloadType": "str",
"x": 300,
"y": 640,
"wires": [
[
"977ad04d20a3cd2f"
]
]
},
{
"id": "977ad04d20a3cd2f",
"type": "exec",
"z": "e66541cd9d074485",
"command": "sudo pidof joystick",
"addpay": false,
"append": "",
"useSpawn": "true",
"timer": "",
"winHide": false,
"oldrc": false,
"name": "",
"x": 630,
"y": 640,
"wires": [
[
"5f350b2f32c8643d"
],
[],
[]
]
},
{
"id": "5f350b2f32c8643d",
"type": "exec",
"z": "e66541cd9d074485",
"command": "sudo kill",
"addpay": true,
"append": "",
"useSpawn": "true",
"timer": "",
"oldrc": false,
"name": "",
"x": 940,
"y": 620,
"wires": [
[],
[],
[]
]
},
{
"id": "80278a50dea8e506",
"type": "function",
"z": "e66541cd9d074485",
"name": "",
"func": "var data = msg.payload.split(\":\");\nmsg.payload = data;\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1150,
"y": 460,
"wires": [
[
"b5349a609d15da2e"
]
]
},
{
"id": "b5349a609d15da2e",
"type": "debug",
"z": "e66541cd9d074485",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 1370,
"y": 460,
"wires": []
}
]
I'm happy to share this Node-RED flow if anyone is looking for a Visca over IP control solution, now with a joystick :-)
@MDAR That's awesome! I'm glad this old gist is still helping people out. :)
@MDAR That's awesome! I'm glad this old gist is still helping people out. :)
In case you're curious to see where you work is being taken
I've created this topic and credited you.
https://discourse.nodered.org/t/hid-joystick-gamepad-inputs/62748
@MDAR Very cool. In case you are wondering how to send rumble effects to the gamepad, it's done with https://www.kernel.org/doc/html/latest/input/ff.html
In case you are wondering how to send rumble effects to the gamepad
I wasn't.... but I think I know a chap that might
Hi Jason
I found an old set of PlayStation Buzz controllers in a box of bits, your code works with them too !!!
I can't think of a use for it right now, but I'm sure I will.
This is a big ask and I will totally respect a "no, not interested" reply.
Would you be interested in exploring options to get your code working with a Contour MultiMedia Express mouse?
https://contour-design.co.uk/products/multimedia-controller-xpress
I was hoping it might resolve as a Joystick in Linux, but alas it loads as a mouse. ( /dev/input/mouse0
in Debian Bullseye on an Odroid C4 to be exact )
@MDAR If the Linux kernel doesn't see it as a joystick device, then code to support it probably doesn't belong in this gist. The main purpose of this gist was just to show how to read joystick events. There are plenty of existing examples that show how to read events from /dev/input/mouseX
.
Also note that /dev/input/mouseX
usually requires user to be root
or in the input
group to be able to read events from it. I believe things like games that need mouse input get it from X11 or Wayland rather than directly from the device.
For more modern version and not requiring you to be root for reading gamepad input in linux using evdev
generic input event interface with liburing
and libevdev
libraries, go to my repo https://github.com/e2dk4r/gamepad
In line 124, I had to change "%u" into "%zu" to compile this code. Otherwise I got a warning:
joystick.c: In function ‘main’: joystick.c:124:21: warning: format ‘%u’ expects argument of type ‘unsigned int’, but argument 2 has type ‘size_t’ [-Wformat=] printf("Axis %u at (%6d, %6d)\n", axis, axes[axis].x, axes[axis].y); ^
Perhaps casting "axis" into int would be the safest solution, but I suck at programming, so...
Anyway, lovely piece of code. I'm repairing a joystick. :D