Author Topic: Help! Combining two graphics tablets into a single joystick device.  (Read 24684 times)

triplesmile

  • Member
  • ***
  • Posts: 7
Hello.
I have two separate drawing tablets like these with USB interface: https://www.gaomon.net/Pen_Tablet/S620.html
What I want to do with them is to connect them to a PC so it recognizes them as a single joystick. I want to try this for a drone racing simulator.
Basically 2 axes for 2 tablets each, 4 joystick axes in total, for example one tablet controls "X" and "Y" axes and second tablet controls "Z axis" and "X rotation" of the same joystick device. I don't mind buying additional components or partly disassembling the tablets, I just need this to work...
What are my options to accomplish this task?

Renate

  • Frequent Contributor
  • ****
  • Posts: 97
Re: Help! Combining two graphics tablets into a single joystick device.
« Reply #1 on: December 12, 2020, 03:15:53 pm »
Hmm, interesting.

You might reverse engineering the 2nd tablet to configure it to be the secondary axes.
This is not as difficult as it may seem because the USB HID descriptors are pretty conspicuous in a dump.
It wouldn't affect any of the data out bits.

The other way would be to interpose a µP, but it would have to have both USB host & device support.

Or you just might build something with 4 axes.

Jan Axelson

  • Administrator
  • Frequent Contributor
  • *****
  • Posts: 3033
    • Lakeview Research
Re: Help! Combining two graphics tablets into a single joystick device.
« Reply #2 on: December 12, 2020, 03:27:29 pm »
Yes, would require reprcramming, not just hardware.

triplesmile

  • Member
  • ***
  • Posts: 7
Re: Help! Combining two graphics tablets into a single joystick device.
« Reply #3 on: December 12, 2020, 03:39:54 pm »
Thank you very much for your replies, don't know where to start to be honest. May you also recommend a path of literature or topics I should cover and go through before doing this? I am quite new to engineering in general and probably would waste a lot of time reading what I don't really need without any guidance...

Renate

  • Frequent Contributor
  • ****
  • Posts: 97
Re: Help! Combining two graphics tablets into a single joystick device.
« Reply #4 on: December 13, 2020, 01:00:13 pm »
The problem is that digitizers are basically absolute (vs relative) mouses.

Under Windows they are system devices and you run into the same problem as multiple keyboards or mice.
They all get merged into the same stream.
You'd have to write a driver under Windows. I've never done that.

In Linux you can write a udev rule to defeat usage of the tablet as a tablet and have it just appear as hidraw,
then have a user daemon read that and pipe whatever joysticky thing you want to uinput.

triplesmile

  • Member
  • ***
  • Posts: 7
Re: Help! Combining two graphics tablets into a single joystick device.
« Reply #5 on: December 14, 2020, 04:17:31 pm »
I am thinking about using CH559L chip (hope I could read drawing tablets with it, video of what I'm talking about: https://youtu.be/Th88RiSmj2w) with a simple PCB board (i guess it will be simple to design one, never done that before) and connect it via UART to teensy LC microcontroller and then program it and connect to pc as a joystick. Do you think this should work? Any things I should be aware of or be careful with?

Renate

  • Frequent Contributor
  • ****
  • Posts: 97
Re: Help! Combining two graphics tablets into a single joystick device.
« Reply #6 on: December 14, 2020, 07:12:09 pm »
So I take it that this is for Windows?

I see another one of those CH559 adapters: https://www.tindie.com/products/matzelectronics/ch559-usb-host-to-uart-bridge-module/
Yes, one of these could probably work, but you would be programming both the CH559 and the Android Leonardo or Teensy or whatever.

The best would be to get it all done on the CH559.
The CH559 is inexpensive but unless the code is already built I'm not sure that I'd want to mix it up with another architecture (it is an 8051).
I'm not sure if it's possible to have one port on the CH559 host and one port peripheral or if anybody has that written already.
Of course you'd need a small hub for the two (or more) tablets.

Of course a Raspberry Pi has 4 USB peripheral jacks and full software support too.

triplesmile

  • Member
  • ***
  • Posts: 7
Re: Help! Combining two graphics tablets into a single joystick device.
« Reply #7 on: December 15, 2020, 06:34:55 pm »
Well I do have RPI 4B, trying to read the data from the tablets with it. Don't know if and how it's possible to make it work as a joystick gadget/device though.

Renate

  • Frequent Contributor
  • ****
  • Posts: 97
Re: Help! Combining two graphics tablets into a single joystick device.
« Reply #8 on: December 16, 2020, 10:50:30 am »
You'll still need something that can act as a USB peripheral.
A RPi Zero can act as a peripheral, the bigger RPi can't because they use a hub internally.

When I plug in a Wacom tablet, it shows up as /dev/hidraw0 and also as /dev/input/event0 & event1
hidraw0 is just what it sounds like.
The event0 and event1 are the processed reports turned into Linux events.
event0 is the tablet stuff, event1 is the auxillary buttons.

You can grab exclusive access to the events by:
Code: [Select]
fd=open("/dev/input/event0", O_RDONLY);
n=ioctl(fd, EVIOCGRAB, 1);
n=read(fd, data, sizeof(data));

It might be easier to just deal with hidraw0
Still, in my 5 minutes of experimenting I haven't found how to disable the events properly.
I can delete /dev/input/event0 but I'm not sure if something is still trying to read hidraw0

FWIW, here is the HID report descriptor off my Wacom:
Code: [Select]
Tablet PTZ-630
USB, VID: 056a, PID: 00b1

05 01    Usage page (Desktop)
09 02    Usage (Mouse)
a1 01    Collection (Application)

85 01    Report id (1)
09 01    Usage (Pointer)
a1 00    Collection (Physical)

05 09    Usage page (Buttons)
19 01    Usage minimum (Button 1)
29 03    Usage maximum (Button 3)
15 00    Logical minimum (0)
25 01    Logical maximum (1)
95 03    Report count (3)
75 01    Report size (1)
81 02    Input (Variable, Absolute)

95 05    Report count (5)
81 03    Input (Constant, Variable)

05 01    Usage page (Desktop)
09 30    Usage (X)
09 31    Usage (Y)
09 38    Usage (Wheel)
15 81    Logical minimum (-127)
25 7f    Logical maximum (127)
75 08    Report size (8)
95 03    Report count (3)
81 06    Input (Variable, Relative)

c0       End collection
c0       End collection

05 0d    Usage page (Digitizer)
09 01    Usage (Digitizer)
a1 01    Collection (Application)

85 02    Report id (2)
09 00    Usage (0)
75 08    Report size (8) <- REDUNDANT
95 09    Report count (9)
15 00    Logical minimum (0)
26 ff 00 Logical maximum (255)
81 02    Input (Variable, Absolute)

09 3a    Usage (Program change keys)
25 02    Logical maximum (2)
95 01    Report count (1)
b1 02    Feature (Variable, Absolute)

85 03    Report id (3)
09 00    Usage (0)
26 ff 00 Logical maximum (255)
b1 02    Feature (Variable, Absolute)

85 04    Report id (4)
09 3a    Usage (Program change keys)
25 01    Logical maximum (1)
b1 02    Feature (Variable, Absolute)

85 05    Report id (5)
09 00    Usage (0)
26 ff 00 Logical maximum (255)
95 08    Report count (8)
b1 02    Feature (Variable, Absolute)

85 06    Report id (6)
09 00    Usage (0)
b1 02    Feature (Variable, Absolute)

85 07    Report id (7)
09 00    Usage (0)
95 04    Report count (4)
b1 02    Feature (Variable, Absolute)

85 08    Report id (8)
09 00    Usage (0)
b1 02    Feature (Variable, Absolute)

85 09    Report id (9)
09 00    Usage (0)
95 01    Report count (1)
b1 02    Feature (Variable, Absolute)

85 0a    Report id (10)
09 00    Usage (0)
95 02    Report count (2)
b1 02    Feature (Variable, Absolute)

85 0b    Report id (11)
09 00    Usage (0)
95 01    Report count (1)
b1 02    Feature (Variable, Absolute)

85 0c    Report id (12)
09 00    Usage (0)
95 09    Report count (9)
81 02    Input (Variable, Absolute)

85 0d    Report id (13)
09 00    Usage (0)
95 01    Report count (1)
b1 02    Feature (Variable, Absolute)

c0       End collection

You

Renate

  • Frequent Contributor
  • ****
  • Posts: 97
Re: Help! Combining two graphics tablets into a single joystick device.
« Reply #9 on: December 17, 2020, 08:55:10 am »
A few wrinkles you'll have to deal with in any case.

In Linux drivers will be loaded. You probably don't want that.
The sophisticated way would be to unbind the drivers in some udev rule.
The brute force way would be to blacklist the modules in /etc/modprobe.d/blacklist-wacom.conf
Code: [Select]
blacklist evdev
blacklist wacom
The "wacom" will prevent the more advanced Wacom driver from loading.
The "evdev" will prevent the standard keyboard/mouse driver from loading for any device.
This has the side effect of preventing a USB keyboard or mouse from working.

And you probably want to throw in a udev rule for an alias in /etc/udev/rules.d/70-wacom.rules (or whatever).
Code: [Select]
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="056a", ATTRS{idProduct}=="00b1", MODE="0666", GROUP="plugdev", SYMLINK+="wacom"I hope that your tablet has an ID, that makes life easier.

The next problem is that when my Wacom tablet is plugged in it looks like a standard relative mouse.
You need to send it a command to switch it from sending report ID #1 to report ID #2.
This would be sent by the wacom driver during initialization, but we're not using that.
It's a set feature report #2
Code: [Select]
data[0]=0x02;
data[1]=0x02;
n=ioctl(fd, HIDIOCSFEATURE(2), data);

Report ID #2 is a 10 byte absolute coordinates report.
Code: [Select]
0  0x02  report ID
1  0x80=no coords, 0xa0=hover, 0xe0=touch
2  X hi byte
3  X lo byte
4  Y hi byte
5  Y lo byte
6  pressure
7  ?
8  ?
9  ?
The resolution is nice, 20320 x 15240
We can read from /dev/wacom and get our 10 byte report #2.
If we hit a button we get our 10 byte report #12.

triplesmile

  • Member
  • ***
  • Posts: 7
Re: Help! Combining two graphics tablets into a single joystick device.
« Reply #10 on: December 19, 2020, 10:43:10 am »
Mhmm, it looks to me that you can actually use RPI4 as a usb device via usb C, which is kinda OTG port i guess, but I might be totally wrong as I have almost no clue what I'm talking about...:
https://mtlynch.io/key-mime-pi/
https://www.kernel.org/doc/html/latest/usb/gadget_hid.html

Didn't understand everything you've written previously, might come back time to time to read it again. I was successful to read pen coordinate data from tablets with evdev library, but need to make some modifications foresure...
Oh I feel like this is going to be a long road... I will keep asking questions here when I hit some sort of a wall, thanks for the help so far! :)
« Last Edit: December 19, 2020, 12:11:11 pm by triplesmile »

Renate

  • Frequent Contributor
  • ****
  • Posts: 97
Re: Help! Combining two graphics tablets into a single joystick device.
« Reply #11 on: December 19, 2020, 03:00:51 pm »
Yes, I just learned that the RPi 4 supports USB gadget on the Type C connector.
What you want could be done just using an RPi4.
I only have RPi 2, RPi 3, RPi Zero, RPi ZeroW, no RPI4.

Still, what you want admits of two ways of doing:

1) Read Linux events (with or without a library). This has the potential to work with random tablets.
2) Read hidraw reports. This makes it easier to integrate button pushes on a tablet but is model-specific to the tablet.

For multiple tablets the 2nd one would make it easier to differentiate the tablets.
A udev rule would work on either ATTRS{serial}== or KERNEL== depending on whether it had a unique serial number.

Using /dev/input it would probably initialize the same way each time, but maybe not.
You could go into /sys and figure out which tablet is which.

triplesmile

  • Member
  • ***
  • Posts: 7
Re: Help! Combining two graphics tablets into a single joystick device.
« Reply #12 on: December 20, 2020, 06:29:48 pm »
So far I've managed to get my RPI4 recognized as a gamepad(joystick basically) on pc, now I am trying to make a proper HID descriptor to use 4 axes for coordinates (additionally will try to make it for everything tablet has, so additional 2 axes for pen pressure and all the buttons (14 of them), just in case I'll want to use something in the future). Found that evdev library has a function: "dev.grab()  # become the sole recipient of all incoming input events". So I suppose it will make an easy way to read data but avoid tablets acting as mouse. A bit confused now how I'll generate reports for joystick movement, but emhmm, yeah, baby steps, maybe I'll figure it out somehow.. :D

Renate

  • Frequent Contributor
  • ****
  • Posts: 97
Re: Help! Combining two graphics tablets into a single joystick device.
« Reply #13 on: December 21, 2020, 06:44:38 am »
Found that evdev library has a function: "dev.grab()  # become the sole recipient of all incoming input events".

I've not used libevdev, but grab() must just be the single ioctl();

You can grab exclusive access to the events by:
Code: [Select]
fd=open("/dev/input/event0", O_RDONLY);
n=ioctl(fd, EVIOCGRAB, 1);
n=read(fd, data, sizeof(data));

Yeah, you can go down this path but FWIW you'll have to read 4 event streams instead of 2 hidraw streams.

triplesmile

  • Member
  • ***
  • Posts: 7
Re: Help! Combining two graphics tablets into a single joystick device.
« Reply #14 on: December 23, 2020, 03:18:14 pm »
Here is my current scrip code, which works great if I only use 1 tablet, but if I use both simultaneously I get some lagg, skips, basically works not well so to say...
Any ideas how to fix that or how to improve it?

Code: [Select]
import asyncio, evdev

def write_report(report):
    with open('/dev/hidg0', 'rb+') as fd:
        fd.write(report)
   
def bv(byv,bir): #byte value, bit number (1-8). Returns bit state (1/0)
    return(byv%(2**(bir))//(2**(bir-1)))
   
def changeReport(rar,LRv,Lpen,Lpad,ecode,evalue):
    #print(ecode, evalue, sep=' ')
   
    if (LRv == Lpad or LRv == Lpen):
        i=0 #left tablet
    else:
        i=1 #right tablet
    if (ecode==0):
        [rar[3+4*i],rar[2+4*i]]=divmod(round(evalue*65535/33020),256)
        #print(rar[3+4*i],rar[2+4*i])
        #print(evalue)
    elif (ecode==1):
        [rar[5+4*i],rar[4+4*i]]=divmod(round(evalue*65535/20320),256)
    elif (ecode==256): #1
        if(evalue==1 and bv(rar[i],1)==0):
            rar[i]+=1
        elif(evalue==0 and bv(rar[i],1)==1):
            rar[i]-=1
    elif (ecode==257): #2
        if(evalue==1 and bv(rar[i],2)==0):
            rar[i]+=2
        elif(evalue==0 and bv(rar[i],2)==1):
            rar[i]-=2
    elif (ecode==258): #3
        if(evalue==1 and bv(rar[i],3)==0):
            rar[i]+=4
        elif(evalue==0 and bv(rar[i],3)==1):
            rar[i]-=4
    elif (ecode==259): #4
        if(evalue==1 and bv(rar[i],4)==0):
            rar[i]+=8
        elif(evalue==0 and bv(rar[i],4)==1):
            rar[i]-=8
    elif (ecode==331): #5
        if(evalue==1 and bv(rar[i],5)==0):
            rar[i]+=16
        elif(evalue==0 and bv(rar[i],5)==1):
            rar[i]-=16
    elif (ecode==332): #6
        if(evalue==1 and bv(rar[i],6)==0):
            rar[i]+=32
        elif(evalue==0 and bv(rar[i],6)==1):
            rar[i]-=32
    elif (ecode==320): #7
        if(evalue==1 and bv(rar[i],7)==0):
            rar[i]+=64
        elif(evalue==0 and bv(rar[i],7)==1):
            rar[i]-=64
    elif (ecode==330): #8
        if(evalue==1 and bv(rar[i],8)==0):
            rar[i]+=128
        elif(evalue==0 and bv(rar[i],8)==1):
            rar[i]-=128
    return rar

#[0][1][2][3][4][5][6][7][8][9][10][11][12][13]
# B  B  X  X  Y  Y  Z  Z  Rx Rx Ry  Ry  Rz  Rz
def main():
    #list devices
    devices = [evdev.InputDevice(path) for path in evdev.list_devices()]
    for device in devices:
        if device.phys == "usb-0000:01:00.0-1.1/input0" and device.name == "GAOMON Gaomon Tablet Pen":
            pen1 = evdev.InputDevice(device.path)
            pen1.grab()
        elif device.phys == "usb-0000:01:00.0-1.2/input0" and device.name == "GAOMON Gaomon Tablet Pen":
            pen2 = evdev.InputDevice(device.path)
            pen2.grab()
        elif device.phys == "usb-0000:01:00.0-1.1/input0" and device.name == "GAOMON Gaomon Tablet Pad":
            pad1 = evdev.InputDevice(device.path)
            pad1.grab()
        elif device.phys == "usb-0000:01:00.0-1.2/input0" and device.name == "GAOMON Gaomon Tablet Pad":
            pad2 = evdev.InputDevice(device.path)
            pad2.grab()
           
    ReportArr=bytearray(14)
    async def print_events(device,ReportArr):
       
        async for event in device.async_read_loop():
            if event.type == evdev.ecodes.EV_KEY or event.type == evdev.ecodes.EV_ABS:
                ReportArr=changeReport(ReportArr,device.fd,pen1.fd,pad1.fd,event.code,event.value)
                write_report(ReportArr)
                #print(ReportArr[0],ReportArr[1])
    for device in pen1, pad1, pen2, pad2:
        asyncio.ensure_future(print_events(device,ReportArr))
    loop = asyncio.get_event_loop()
    loop.run_forever()
   

if __name__ == "__main__":
    main()