A Linux C++ joystick object

An OpenGL rendering of our joystick object

In this post we will implement an object in C++ for accessing the state of an attached joystick. Below is a rendering using SDL and OpenGL of a joystick state.

An OpenGL rendering of our joystick object with mapped and unmapped coordinates
An OpenGL rendering of our joystick object with mapped and unmapped coordinates

In "/usr/include/linux/joystick.h" we find the following event structure:

struct js_event {
	__u32 time;	/* event timestamp in milliseconds */
	__s16 value;	/* value */
	__u8 type;	/* event type */
	__u8 number;	/* axis/button number */
};

Once we have opened a device node for reading we will populate this event structure with the data read from the device. We define a structure for holding the state of the joystick.

struct joystick_state {
	std::vector<signed short> button;
	std::vector<signed short> axis;
};

As we parse the event data, the joystick state structure is updated to reflect the most current state of the joystick. Two methods will be added to our object to query the current state of the buttons and the axes. A button will be either on or off, but for the axes we define another structure for returning the coordinates of an analog stick in addition to the coordinates in polar form.

struct joystick_position {
	float theta, r, x, y;
};

During testing, a PlayStation 3 DualShock controller was used. The left analog stick has two axes, the horizontal (axis 0) and the vertical (axis 1), each mapped to the interval [-32767, 32767] (see below).

The axes for an analog stick
The axes for an analog stick

We will first scale each axis to the interval [-1.0, 1.0] and then map these coordinates to the unit ball using the equations below. Going one step further we will convert these coordinates into polar form.

Scaling to the interval [-1.0, 1.0], we have,
\begin{align}
x' &= \frac{x}{32767}\\
y' &= \frac{-y}{32767}
\end{align}

Mapping to the unit ball, we have,
\begin{align}
x'' &= x'\sqrt{1-\frac{y'^2}{2}}\\
y'' &= y'\sqrt{1-\frac{x'^2}{2}}
\end{align}

Finally, converting to polar form,
\begin{align}
\theta &= atan2(y'',x'')\\
r &= \sqrt{{x''}^2+{y''}^2}
\end{align}

We have the declaration of our joystick object below.

class cJoystick {
  private:
	pthread_t thread;
	bool active;
	int joystick_fd;
	js_event *joystick_ev;
	joystick_state *joystick_st;
	__u32 version;
	__u8 axes;
	__u8 buttons;
	char name[256];

  protected:
  public:
	cJoystick();
	~cJoystick();
	static void* loop(void* obj);
	void readEv();
	joystick_position joystickPosition(int n);
        bool buttonPressed(int n);
};

When a joystick is connected to a USB port, udev, the device manager for the Linux kernel, presents device nodes at "/dev/input/js*". Our constructor attempts to access a device node, query the device for the number of buttons and axes, reserve space in our state structure for the buttons and axes, and, finally, create a thread to populate the state structure.

cJoystick::cJoystick() {
	active = false;
	joystick_fd = 0;
	joystick_ev = new js_event();
	joystick_st = new joystick_state();
	joystick_fd = open(JOYSTICK_DEV, O_RDONLY | O_NONBLOCK);
	if (joystick_fd > 0) {
		ioctl(joystick_fd, JSIOCGNAME(256), name);
		ioctl(joystick_fd, JSIOCGVERSION, &version);
		ioctl(joystick_fd, JSIOCGAXES, &axes);
		ioctl(joystick_fd, JSIOCGBUTTONS, &buttons);
		std::cout << "   Name: " << name << std::endl;
		std::cout << "Version: " << version << std::endl;
		std::cout << "   Axes: " << (int)axes << std::endl;
		std::cout << "Buttons: " << (int)buttons << std::endl;
		joystick_st->axis.reserve(axes);
		joystick_st->button.reserve(buttons);
		active = true;
		pthread_create(&thread, 0, &cJoystick::loop, this);
	}
}

Our loop attempts to read an event from the device, and, if an event is waiting to be processed, update our state structure based on the event type.

void* cJoystick::loop(void *obj) {
	while (reinterpret_cast<cJoystick *>(obj)->active) reinterpret_cast<cJoystick *>(obj)->readEv();
}

void cJoystick::readEv() {
	int bytes = read(joystick_fd, joystick_ev, sizeof(*joystick_ev));
	if (bytes > 0) {
		joystick_ev->type &= ~JS_EVENT_INIT;
		if (joystick_ev->type & JS_EVENT_BUTTON) {
			joystick_st->button[joystick_ev->number] = joystick_ev->value;
		}
		if (joystick_ev->type & JS_EVENT_AXIS) {
			joystick_st->axis[joystick_ev->number] = joystick_ev->value;
		}
	}
}

We have a method to query the state of a button below.

bool cJoystick::buttonPressed(int n) {
	return n > -1 && n < buttons ? joystick_st->button[n] : 0;
}

The method to query the state of an analog stick based on our equations above is below.

joystick_position cJoystick::joystickPosition(int n) {
	joystick_position pos;

	if (n > -1 && n < buttons) {
		int i0 = n*2, i1 = n*2+1;
		float x0 = joystick_st->axis[i0]/32767.0f, y0 = -joystick_st->axis[i1]/32767.0f;
		float x  = x0 * sqrt(1 - pow(y0, 2)/2.0f), y  = y0 * sqrt(1 - pow(x0, 2)/2.0f);

		pos.x = x0;
		pos.y = y0;
		
		pos.theta = atan2(y, x);
		pos.r = sqrt(pow(y, 2) + pow(x, 2));
	} else {
		pos.theta = pos.r = pos.x = pos.y = 0.0f;
	}
	return pos;
}

Finally, we have the destructor.

cJoystick::~cJoystick() {
	if (joystick_fd > 0) {
		active = false;
		pthread_join(thread, 0);
		close(joystick_fd);
	}
	delete joystick_st;
	delete joystick_ev;
	joystick_fd = 0;
}

In my next post we will attempt to create an object to query the state of the keyboard in a similar fashion. \(t\)

Download this project: joystick_fix.tar.bz2

Comments

  1. nariman

    hi
    when I want to make this project some errors occure :
    src/joystick.cc:29:20: error: ‘close’ was not declared in this scope
    src/joystick.cc: In member function ‘void cJoystick::readEv()’:
    src/joystick.cc:41:65: error: ‘read’ was not declared in this scope
    what can I do now ?
    help me please... tnx

    1. Post
      Author
      1. Post
        Author
        keith

        I've updated the download. There was also a bug on line 56 in src/joystick.cc. That line should reference axes not buttons. I've also cleaned up the Makefile a bit.

  2. cmakeshift

    I'd suggest adding an isActive() method of sorts. Otherwise if we just assume it has opened a valid device and try to get a position it simply crashes spectacularly here on my end...
    It is as simple as:

    bool isActive() const {return active;}

    right before the closing bracket of cJoystick. Then check the return value it before trying to use the object.

Leave a Reply

Your email address will not be published.