Read Joystick from Julia

Hello,

I would like to read a joystick on Linux to control my kite simulator (see: GitHub - aenarete/KiteViewers.jl: 3D viewer for airborne wind energy systems ). The following test on the command line works:

jstest --normal /dev/input/js0
Driver version is 2.1.0.
Joystick (Logitech Extreme 3D pro) has 6 axes (X, Y, Rz, Throttle, Hat0X, Hat0Y)
and 12 buttons (Trigger, ThumbBtn, ThumbBtn2, TopBtn, TopBtn2, PinkieBtn, BaseBtn, BaseBtn2, BaseBtn3, BaseBtn4, BaseBtn5, BaseBtn6).
Testing ... (interrupt to exit)
Axes:  0:     0  1:     0  2:     0  3:     0  4:     0  5:     0 Buttons:  0:off  1:off  2:off  3:off  4:off  5:off  6:off  7:off  8:off  9:off 10:off 11:off 

But how can I access the values from Julia?

This C code looks straightforward to port to Julia: Reads joystick/gamepad events on Linux and displays them. · GitHub

I give it a try…

How would you translate this to Julia:

size_t get_axis_count(int fd)
{
    __u8 axes;

    if (ioctl(fd, JSIOCGAXES, &axes) == -1)
        return 0;

    return axes;
}

I just need a starting point.

Update:
Found this discussion: Ccall() to ioctl - #17 by yuyichao

I don’t know if there is a Julia wrapper for ioctl already, but you can always @ccall it. You can do something like this

function get_axis_count(fd::Cint)::Csize_t
    axes = Ref{UInt8}()
    if @ccall(ioctl(fd::Cint, JSIOCGAXES::Cint; axes::Ref{UInt8})::Cint) == -1
        return 0
    end
    return axes[]
end

(didn’t test it)

3 Likes

Nice. Now I need to find the value of JSIOCGAXES, which is defined as

#define JSIOCGAXES		_IOR('j', 0x11, __u8)				/* get number of axes */

see: joystick.h - include/uapi/linux/joystick.h - Linux source code (v5.17.9) - Bootlin

Any idea which Cint number this refers to?

% cat joystick.c
#include <linux/joystick.h>
#include <stdio.h>

int main(void) {
    printf("JSIOCGAXES = %d\n", JSIOCGAXES);
    return 0;
}
% cc joystick.c -o joystick
% ./joystick               
JSIOCGAXES = -2147390959

This code works:

const JSIOCGAXES=2147576337

function open_joystick(filename="/dev/input/js0")
   file = open(filename,"r+")
   Cint(fd(file))
end

function get_axis_count(fd::Cint)
    axes = Ref{UInt8}()
    if @ccall(ioctl(fd::Cint, JSIOCGAXES::Clong; axes::Ref{UInt8})::Cint) == -1
        return -1
    end
    Int64(axes[])
end
using Pkg
pkg"add https://github.com/ufechner7/Joystick.jl"

using Joystick
fd=open_joystick()
axis_count = get_axis_count(fd)
println(axis_count)

But why do I have a different value for JSIOCGAXES than you have?
And is it correct to assume that the file descriptor is of type Cint, both on 64bit and 32bit systems?

Feel free to fork my code and make a pull request: GitHub - ufechner7/Joystick.jl: Joystick interface for Julia

I will continue tomorrow.

I don’t know why it’s different, I’m not familiar with Linux programming. What’s the output of this command for you?

% echo '#include <linux/joystick.h>\nJSIOCGAXES' | cc -E - | tail -n1          
(((2U) << (((0 +8)+8)+14)) | ((('j')) << (0 +8)) | (((0x11)) << 0) | ((((sizeof(__u8)))) << ((0 +8)+8)))

Wait, it’s the same number: Int32(-2147390959) is the same as UInt32(2147576337)

julia> Int(reinterpret(UInt32, Int32(-2147390959)))
2147576337
1 Like

The correct type of JSIOCGAXES is Clong…

So a signed integer. So you should use -2147390959, not 2147576337 (unless you convert the latter to unsigned). These integer literals are all signed as you’re writing them, so using the reinterpreted unsigned value is wrong.

Thanks! Will give it a try

// gcc test.c -o test
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <linux/joystick.h>

int main(int argc, char *argv[])
{
    printf("JSIOCGAXES: %lu\n", JSIOCGAXES);
    printf("JSIOCGBUTTONS: %lu\n", JSIOCGBUTTONS);
    return 0;
}

Output:

ufechner@TUD277255:~/repos/Joystick.jl/src$ ./test
JSIOCGAXES: 2147576337
JSIOCGBUTTONS: 2147576338

So the positive value is correct.

You’re printing an unsigned value and getting a positive value… Again, the Julia literal 2147576337 is a signed value, which is completely different from the unsigned litearal value 0x80016a11. 2147576337 doesn’t even fit in a Int32.

Did it and summited a PR. I don’t have a Joystick right now to check the implementation, and I didn’t add more test, but hope you find it useful.

1 Like

Thanks a lot! I merged your commit and fixed one typo, but your example does’t work. Something is missing:

julia> using Joystick
[ Info: Precompiling Joystick [82f05805-4863-42dc-b1a7-0852bd62c632]

julia> axes = Joystick.JSAxis()
ERROR: MethodError: no method matching Tuple{Joystick.JSAxisState, Joystick.JSAxisState, Joystick.JSAxisState}()
Closest candidates are:
  (::Type{T})(::Tuple) where T<:Tuple at ~/packages/julias/julia-1.7/share/julia/base/tuple.jl:312
  (::Type{T})(::NamedTuple) where T<:Tuple at ~/packages/julias/julia-1.7/share/julia/base/namedtuple.jl:147
  (::Type{T})(::Any) where T<:Tuple at ~/packages/julias/julia-1.7/share/julia/base/tuple.jl:317
Stacktrace:
 [1] top-level scope
   @ REPL[3]:1

I mean, JSAxis is just a type without constructor:

struct JSAxisState
    x::Int16
    y::Int16
end

const JSAxis = NTuple{3, JSAxisState}

I think a constructor is missing, but I don’t know how to write a constructor for an NTuple. So far I only used structs.

Any idea?

OK, this works:

using Joystick
jsaxes = ntuple(i->JSAxisState(0,0), 3)

So the example has no syntax error any more, but is still not yet working.

Oh, I see. Maybe is the reading of events? Try here to change all=true (that is the default).

Also, what does the example do right now?