Skip to content

Instantly share code, notes, and snippets.

@jasonwhite jasonwhite/joystick.c
Last active Oct 20, 2019

Embed
What would you like to do?
Reads joystick/gamepad events on Linux and displays them.
/**
* 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

This comment has been minimized.

Copy link

tomek-szczesny commented Jun 8, 2019

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

@jasonwhite

This comment has been minimized.

Copy link
Owner Author

jasonwhite commented Jun 10, 2019

@tomek-szczesny Thanks! I updated the Gist with your fix. Glad you found this useful.

@Tetrastorm

This comment has been minimized.

Copy link

Tetrastorm commented Aug 8, 2019

Nice code.
You will try to put "{" and "}" at all if and else, although is not needed.

@Hermann-SW

This comment has been minimized.

Copy link

Hermann-SW commented Oct 17, 2019

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)
@jasonwhite

This comment has been minimized.

Copy link
Owner Author

jasonwhite commented Oct 17, 2019

@Hermann-SW Thanks. I've updated the Gist to include unistd.h. 👍

@Hermann-SW

This comment has been minimized.

Copy link

Hermann-SW commented Oct 19, 2019

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
4DoF_robot_arm_in_action_yes_hard_scripted

@jasonwhite

This comment has been minimized.

Copy link
Owner Author

jasonwhite commented Oct 19, 2019

@Hermann-SW Wow! That's awesome! I also added the fflush(stdout); 👍

@Hermann-SW

This comment has been minimized.

Copy link

Hermann-SW commented Oct 20, 2019

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
...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.