2

I bought a Sparkfun USB host shield a while ago and have been using it using my Logitech Extreme 3D Pro Joystick. There is some example code for that one which makes it nice and easy to work with. There is also some example code for a generic USB HID Joystick.

My goal is to be able to use any joystick I want with the shield. All the channels on each of my flight sticks are being detected but they all have issues like they are mapped wrongly and don't have a full range of motion.

I understand I need to use USBHID_desc to get the USB HID Descriptor (unique for each joystick right?) and plug those numbers somewhere into the USBHIDJoystick code, but I don't know where to put this information.

My problem boils down to how should I use the data recovered from the USBHID_desc program.

Any help would be appreciated as I'm at a bit of a loss

Thanks

Report Descriptor for Random Flight Stick

Start
0000: 05 01 09 04 A1 01 09 01 A1 00 05 01 09 30 09 31 
0010: 15 00 26 FF 03 35 00 46 FF 03 65 00 75 0A 95 02 
0020: 81 02 09 35 09 32 15 00 26 FF 01 35 00 46 FF 01 
0030: 65 00 75 09 95 02 81 02 75 01 95 02 81 01 09 39 
0040: 15 01 25 08 35 00 46 3B 01 65 14 75 08 95 01 81 
0050: 02 05 09 19 01 29 06 15 00 25 01 35 00 45 01 75 
0060: 01 95 06 81 02 75 01 95 0A 81 01 C0 C0 
Usage Page Gen Desktop Ctrls(01)
Usage Game Pad
Collection Application
Usage Pointer
Collection Physical
Usage Page Gen Desktop Ctrls(01)
Usage X
Usage Y
Logical Min(00)
Logical Max(FF03)
Physical Min(00)
Physical Max(FF03)
Unit(00)
Report Size(0A)
Report Count(02)
Input(00000010)
Usage Rz
Usage Z
Logical Min(00)
Logical Max(FF01)
Physical Min(00)
Physical Max(FF01)
Unit(00)
Report Size(09)
Report Count(02)
Input(00000010)
Report Size(01)
Report Count(02)
Input(00000001)
Usage Hat Switch
Logical Min(01)
Logical Max(08)
Physical Min(00)
Physical Max(3B01)
Unit(14)
Report Size(08)
Report Count(01)
Input(00000010)
Usage Page Button(09)
Usage Min(01)
Usage Max(06)
Logical Min(00)
Logical Max(01)
Physical Min(00)
Physical Max(01)
Report Size(01)
Report Count(06)
Input(00000010)
Report Size(01)
Report Count(0A)
Input(00000001)
End Collection
End Collection Game Pad Pointer X Y(02)(08)
 Rz Z(00)(E3)
(00)(00)
 Hat Switch(00)
 Btn0001
(00) Btn0002
(01) Btn0003
(00) Btn0004
(00) Btn0005
(00) Btn0006
(00)
(00)(00)(00)(00)(00)(00)(00)(00)(00)(00)

USB HID Joystick .INO

#include <usbhid.h>
#include <hiduniversal.h>
#include <usbhub.h>

// Satisfy IDE, which only needs to see the include statment in the ino.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>

#include "hidjoystickrptparser.h"

USB Usb;
USBHub Hub(&Usb);
HIDUniversal Hid(&Usb);
JoystickEvents JoyEvents;
JoystickReportParser Joy(&JoyEvents);

void setup() {
        Serial.begin(115200);
#if !defined(__MIPSEL__)
        while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection
#endif
        Serial.println("Start");

        if (Usb.Init() == -1)
                Serial.println("OSC did not start.");

        delay(200);

        if (!Hid.SetReportParser(0, &Joy))
                ErrorMessage<uint8_t > (PSTR("SetReportParser"), 1);
}

void loop() {
        Usb.Task();
}

USB HID Joystick .cpp

#include "hidjoystickrptparser.h"

JoystickReportParser::JoystickReportParser(JoystickEvents *evt) :
joyEvents(evt),
oldHat(0xDE),
oldButtons(0) {
        for (uint8_t i = 0; i < RPT_GEMEPAD_LEN; i++)
                oldPad[i] = 0xD;
}

void JoystickReportParser::Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf) {
        bool match = true;

        // Checking if there are changes in report since the method was last called
        for (uint8_t i = 0; i < RPT_GEMEPAD_LEN; i++)
                if (buf[i] != oldPad[i]) {
                        match = false;
                        break;
                }

        // Calling Game Pad event handler
        if (!match && joyEvents) {
                joyEvents->OnGamePadChanged((const GamePadEventData*)buf);

                for (uint8_t i = 0; i < RPT_GEMEPAD_LEN; i++) oldPad[i] = buf[i];
        }

        uint8_t hat = (buf[5] & 0xF);

        // Calling Hat Switch event handler
        if (hat != oldHat && joyEvents) {
                joyEvents->OnHatSwitch(hat);
                oldHat = hat;
        }

        uint16_t buttons = (0x0000 | buf[6]);
        buttons <<= 4;
        buttons |= (buf[5] >> 4);
        uint16_t changes = (buttons ^ oldButtons);

        // Calling Button Event Handler for every button changed
        if (changes) {
                for (uint8_t i = 0; i < 0x0C; i++) {
                        uint16_t mask = (0x0001 << i);

                        if (((mask & changes) > 0) && joyEvents) {
                                if ((buttons & mask) > 0)
                                        joyEvents->OnButtonDn(i + 1);
                                else
                                        joyEvents->OnButtonUp(i + 1);
                        }
                }
                oldButtons = buttons;
        }
}

void JoystickEvents::OnGamePadChanged(const GamePadEventData *evt) {
        Serial.print("X1: ");
        PrintHex<uint8_t > (evt->X, 0x80);
        Serial.print("\tY1: ");
        PrintHex<uint8_t > (evt->Y, 0x80);
        Serial.print("\tX2: ");
        PrintHex<uint8_t > (evt->Z1, 0x80);
        Serial.print("\tY2: ");
        PrintHex<uint8_t > (evt->Z2, 0x80);
        Serial.print("\tRz: ");
        PrintHex<uint8_t > (evt->Rz, 0x80);
        Serial.println("");
}

void JoystickEvents::OnHatSwitch(uint8_t hat) {
        Serial.print("Hat Switch: ");
        PrintHex<uint8_t > (hat, 0x80);
        Serial.println("");
}

void JoystickEvents::OnButtonUp(uint8_t but_id) {
        Serial.print("Up: ");
        Serial.println(but_id, DEC);
}

void JoystickEvents::OnButtonDn(uint8_t but_id) {
        Serial.print("Dn: ");
        Serial.println(but_id, DEC);
}

USB HID Joystick .h

#if !defined(__HIDJOYSTICKRPTPARSER_H__)
#define __HIDJOYSTICKRPTPARSER_H__

#include <usbhid.h>

struct GamePadEventData {
        uint8_t X, Y, Z1, Z2, Rz;
};

class JoystickEvents {
public:
        virtual void OnGamePadChanged(const GamePadEventData *evt);
        virtual void OnHatSwitch(uint8_t hat);
        virtual void OnButtonUp(uint8_t but_id);
        virtual void OnButtonDn(uint8_t but_id);
};

#define RPT_GEMEPAD_LEN     5

class JoystickReportParser : public HIDReportParser {
        JoystickEvents *joyEvents;

        uint8_t oldPad[RPT_GEMEPAD_LEN];
        uint8_t oldHat;
        uint16_t oldButtons;

public:
        JoystickReportParser(JoystickEvents *evt);

        virtual void Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf);
};

#endif // __HIDJOYSTICKRPTPARSER_H__

Edit of the current problem: (This was fixed with a bit of trial and error in the packed struct)

X: 512 Y: 512 Hat Switch: 0 Twist: 768 Slider: 108 Buttons A: 0 Buttons B: 0
X: 512 Y: 512 Hat Switch: 0 Twist: 256 Slider: 108 Buttons A: 0 Buttons B: 0
X: 512 Y: 512 Hat Switch: 0 Twist: 768 Slider: 108 Buttons A: 0 Buttons B: 0
X: 512 Y: 512 Hat Switch: 0 Twist: 256 Slider: 108 Buttons A: 0 Buttons B: 0
X: 512 Y: 512 Hat Switch: 0 Twist: 768 Slider: 108 Buttons A: 0 Buttons B: 0
X: 512 Y: 512 Hat Switch: 0 Twist: 256 Slider: 108 Buttons A: 0 Buttons B: 0
X: 512 Y: 512 Hat Switch: 0 Twist: 768 Slider: 108 Buttons A: 0 Buttons B: 0
X: 512 Y: 512 Hat Switch: 0 Twist: 256 Slider: 108 Buttons A: 0 Buttons B: 0
X: 512 Y: 512 Hat Switch: 0 Twist: 768 Slider: 108 Buttons A: 0 Buttons B: 0
X: 512 Y: 512 Hat Switch: 0 Twist: 256 Slider: 108 Buttons A: 0 Buttons B: 0
X: 512 Y: 512 Hat Switch: 0 Twist: 768 Slider: 108 Buttons A: 0 Buttons B: 0

Edit for if statement issue: Below is a section of the what my issue pretty much goes down to. Now, I am assuming here that structs act in a similar way to dictionaries in python which I think is a massive simplification as they also seem to work as a class? Anyway, I was working on a way to have a first struct with the data for a specific joystick in, then using the data from that first to fill the second that will be used multiple times in the code but I think that is a worse method then what I have below and I couldn't get it to work anyway.

.cpp

#include "le3dp_rptparser.h"
#define __HIDJOYSTICKRPTPARSER_H__

#include <usbhid.h>

struct LogitechGamePro
{
  union { //axes and hut switch
    uint32_t axes;
    struct {
      uint32_t x : 10;
      uint32_t y : 10;
      uint32_t twist : 9;
      uint32_t slider : 10;   
    };
  };
  uint8_t hat;
  uint8_t buttons_a;
  uint8_t buttons_b;
};



struct GamePadEventData
{
  union { //axes and hut switch
    uint32_t axes;
    struct {
      uint32_t x : 10;
      uint32_t y : 10;
      uint32_t twist : 9;
      uint32_t slider : 10;   
    };
  };
  uint8_t hat;
  uint8_t buttons_a;
  uint8_t buttons_b;
};





JoystickReportParser::JoystickReportParser(JoystickEvents *evt) :
    joyEvents(evt)
{}

void JoystickReportParser::Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf)
{

  
    
    bool match = true;

    // Checking if there are changes in report since the method was last called
    for (uint8_t i=0; i<RPT_GAMEPAD_LEN; i++) {
        if( buf[i] != oldPad[i] ) {
            match = false;
            break;
        }
  }
    // Calling Game Pad event handler
    if (!match && joyEvents) {
        joyEvents->OnGamePadChanged((const GamePadEventData*)buf);

        for (uint8_t i=0; i<RPT_GAMEPAD_LEN; i++) oldPad[i] = buf[i];
    }
 
}



void JoystickEvents::OnGamePadChanged(const GamePadEventData *evt)
{
    Serial.print("X: ");
    PrintHex<uint16_t>(evt->x, 0x80);
    Serial.print(" Y: ");
    PrintHex<uint16_t>(evt->y, 0x80);
    Serial.print(" Hat Switch: ");
    PrintHex<uint8_t>(evt->hat, 0x80);
    Serial.print(" Twist: ");
    PrintHex<uint8_t>(evt->twist, 0x80);
    Serial.print(" Slider: ");
    PrintHex<uint8_t>(evt->slider, 0x80);
  Serial.print(" Buttons A: ");
    PrintHex<uint8_t>(evt->buttons_a, 0x80);
    Serial.print(" Buttons B: ");
    PrintHex<uint8_t>(evt->buttons_b, 0x80);
    Serial.println("");
}

.h

#if !defined(__HIDJOYSTICKRPTPARSER_H__)
#define __HIDJOYSTICKRPTPARSER_H__

#include <usbhid.h>

struct GamePadEventData {};


struct ST290 {};
struct LogitechGamePro {};


class JoystickEvents
{
public:
    virtual void OnGamePadChanged(const GamePadEventData *evt);
};

#define RPT_GAMEPAD_LEN sizeof(GamePadEventData)/sizeof(uint8_t)

class JoystickReportParser : public HIDReportParser
{
    JoystickEvents      *joyEvents;

  uint8_t oldPad[RPT_GAMEPAD_LEN];

public:
    JoystickReportParser(JoystickEvents *evt);

    virtual void Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf);
};

#endif // __HIDJOYSTICKRPTPARSER_H__

Error from Example code 2:

sketch\le3dp_rptparser.cpp: In member function 'virtual void JoystickReportParser::Parse(USBHID*, bool, uint8_t, uint8_t*)':
le3dp_rptparser.cpp:36:54: error: no matching function for call to 'JoystickEvents::OnGamePadChanged(GamePadEventData&)'
             joyEvents->OnGamePadChanged(currentValues);
                                                      ^
In file included from sketch\le3dp_rptparser.cpp:1:0:
sketch\le3dp_rptparser.h:43:15: note: candidate: virtual void JoystickEvents::OnGamePadChanged(const GamePadEventData*)
  virtual void OnGamePadChanged(const GamePadEventData *evt);
               ^~~~~~~~~~~~~~~~
sketch\le3dp_rptparser.h:43:15: note:   no known conversion for argument 1 from 'GamePadEventData' to 'const GamePadEventData*'
sketch\le3dp_rptparser.cpp: At global scope:
le3dp_rptparser.cpp:46:6: error: prototype for 'void JoystickEvents::OnGamePadChanged(GamePadEventData*)' does not match any in class 'JoystickEvents'
 void JoystickEvents::OnGamePadChanged(GamePadEventData *evt)
      ^~~~~~~~~~~~~~
In file included from sketch\le3dp_rptparser.cpp:1:0:
le3dp_rptparser.h:43:15: error: candidate is: virtual void JoystickEvents::OnGamePadChanged(const GamePadEventData*)
  virtual void OnGamePadChanged(const GamePadEventData *evt);
               ^~~~~~~~~~~~~~~~
exit status 1
no matching function for call to 'JoystickEvents::OnGamePadChanged(GamePadEventData&)'
Vosem Media
  • 100
  • 7

1 Answers1

3

Reading and understanding a report descriptor can be a bit of a black art at times. They're quite cryptic when you first look at them, but actually they make perfect sense.

If you think of each entry (except "input" or "output") as setting some configuration value, and the "input" and "output" entries as using those configuration values, it makes more sense.

Since you are only working with gamepads and joysticks (essentially the same thing) much of the descriptor can be ignored (however you sill have to parse it to get to the other stuff). So let's take your example descriptor a line (or group of lines) at a time and see what they all mean, and what they mean to you.

Usage Page Gen Desktop Ctrls(01)
Usage Game Pad
Collection Application
Usage Pointer
Collection Physical
Usage Page Gen Desktop Ctrls(01)

You don't much care about these from a data perspective, but they do define what the following data is interpreted as. This tells you that "The following data is for configuring the pointer aspect (direction, not mouse pointer) of a game pad.

Usage X
Usage Y

These are the parameters that we are now configuring (joystick X and Y movement). We group them together because they have common settings and parameters. Those parameters are coming up.

Logical Min(00)
Logical Max(FF03)
Physical Min(00)
Physical Max(FF03)

This is the range of the values that we are going to be reporting, and what that range means. In this case there is a 1:1 mapping between the values the joystick reports (Logical) and what those values map to (Physical). We see a better usage of this later on. The numbers are "little endian", which means that "FF03" is actually "0x03FF", so the joystick reports values between 0 and 1023.

Unit(00)

This defines the physical units of the value being reported. In this case there are no units. Again we see a better usage of this later on.

Report Size(0A)
Report Count(02)

Now we get into the nitty gritty of the data being sent. This tells us that each report value is 0x0A (10) bits in size, and there are 2 of them. That's 1 report for the X value of 10 bits, and 1 report for the Y value of 10 bits (in that order, since that is the order of the "Usage" lines above).

So so far we have 20 bits of data in our report that are laid out in bytes as:

XXXXXXXX
YYYYYYXX
....YYYY

There's 4 bits not filled in our bytes yet, but don't worry, they're coming soon. Next comes the "Input" entry:

Input(00000010)

This is where it actually creates the data in the report according to the settings we have set above. The 00000010 tells us that it's a variable data value that is being sent.

Now we have another similar chunk for the Z (throttle) and Rz (rotational Z) values:

Usage Rz
Usage Z
Logical Min(00)
Logical Max(FF01)
Physical Min(00)
Physical Max(FF01)
Unit(00)
Report Size(09)
Report Count(02)
Input(00000010)

This is almost the same as above, but the numbers are different. We have 2 usages of Rz and Z (in that order), and they report values between 0 and 0x01FF (511). Again there are no units to these numbers. The report size is 9 bits (by the way, 29-1 is 511), and there are two reports, one for Rz and one for Z.

So lets feed these into our bytes, 9 bits of z (I'll use "z" for Rz) and 9 for Z:

XXXXXXXX
YYYYYYXX
zzzzYYYY
ZZZzzzzz
..ZZZZZZ

You see we still have two bits unused in our bytes. HID reports don't like unused bits, so we need to pad those out before moving on to a new area of interest:

Report Size(01)
Report Count(02)
Input(00000001)

Here we're defining a report size of 1 bit and there's two reports. That's 2 bits. These are our padding bits to fill out those extra two bits above. You see the Input entry has a different number with it. 00000001 tells us that this is constant data. It's nothing that will ever change - just padding.

So now our HID report looks like:

XXXXXXXX
YYYYYYXX
zzzzYYYY
ZZZzzzzz
00ZZZZZZ

Now we move on to the "Hat switch", which is somewhat similar, but not quite:

Usage Hat Switch
Logical Min(01)
Logical Max(08)
Physical Min(00)
Physical Max(3B01)
Unit(14)
Report Size(08)
Report Count(01)
Input(00000010)

Here we have 1 report of 8 bits. We're sending a value between 1 and 8 for the 8 directions of the hat switch (you can only have 1 direction at a time), and we're using an entire byte to send that value in. It sounds wasteful, given that we only need 4 bits to represent the number 8, but remember we would have to pad the other 4 bits with a constant value to bring it up to a round byte. So it's simpler to just use the whole byte in this instance and save having to have a padding section in the descriptor.

You notice that the logical and physical min and max differ in this section. That shows you what you would map the logical values to in a "real world" situation. In this case the values 1-8 map to the real values 0-315 (0x01b3). It's up to you if you actually do that mapping or not. What are those numbers though? Well, if you divide 315 by 8 (the number of hat positions) you get 45. And each hat position is a 45° increment. So they must map to degrees. And look, "Unit" hs a value of 0x14 in it. If we look that up we see that 0x14 corresponds to "English Rotational Angular Position", or "degrees" in normal speech.

We can now add that to our report format:

XXXXXXXX
YYYYYYXX
zzzzYYYY
ZZZzzzzz
00ZZZZZZ
HHHHHHHH

And finally we have 6 buttons:

Usage Page Button(09)
Usage Min(01)
Usage Max(06)
Logical Min(00)
Logical Max(01)
Physical Min(00)
Physical Max(01)
Report Size(01)
Report Count(06)
Input(00000010)

There is no "Button 1" etc usage in HID, but you can define a range for the usage, which is what has been done here. Basically since we're in the "buttons" usage page we define a range of 1-6 for the buttons - so there's button 1 to button 6 inclusive.

Each button is either 0 or 1 (not pressed, or pressed), and that's the same "In the real world". Each button has a 1 bit report, since that's all you need, and there are 6 reports, one for each button.

Again we're only using 6 out of the 8 bits of a byte for this, so we pad it out with two more constant bits:

Report Size(01)
Report Count(0A)
Input(00000001)

So if we now add those buttons we get our final report format:

XXXXXXXX
YYYYYYXX
zzzzYYYY
ZZZzzzzz
00ZZZZZZ
HHHHHHHH
00654321

So you have 7 bytes of data that you then have to split up and recombine in the right way with all sorts of bit shifting and masking to extract the real values. And of course this varies from joystick to joystick.

As you can guess writing a parser not only for the HID descriptor itself, but to extract the data in accordance with that descriptor, will be quite a task. Some joysticks will have the values all packed together, some won't. Some will need to have things read in one order, and some in another. Some will have more buttons, or more axes of motion than others.

You can read the HID 1.11 specification here which will tell you all about how the raw numbers map to the different things.

The existing parser code is looking for things in hard coded positions in the data coming in and is doing it in very basic ways. It doesn't honour the report descriptor in any way, and instead just takes each byte as an axis.

You would need to re-write that entire Parse() function (or create a child class that overrides it) to use the information you have parsed from the report descriptor to extract the values from the data bytes in the right way. Given the myriad ways it could be done you will have quite a task ahead of you. It would be easier to just support specific joysticks and hard-code the options for them, maybe make a separate subclass for each joystick each with its own overridden Parse() function. It's up to you to then use the different classes in your code as you need.


To address your other problems:

  • In the header file have the definitions for your structs:
struct LogitechGamePro
{
  union { //axes and hut switch
    uint32_t axes;
    struct {
      uint32_t x : 10;
      uint32_t y : 10;
      uint32_t twist : 9;
      uint32_t slider : 10;   
    };
  };
  uint8_t hat;
  uint8_t buttons_a;
  uint8_t buttons_b;
};



struct GamePadEventData
{
  union { //axes and hut switch
    uint32_t axes;
    struct {
      uint32_t x : 10;
      uint32_t y : 10;
      uint32_t twist : 9;
      uint32_t slider : 10;   
    };
  };
  uint8_t hat;
  uint8_t buttons_a;
  uint8_t buttons_b;
};

In the .cpp file use those for casting and population:



void JoystickReportParser::Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf)
{

    struct GamePadEventData currentValues;
  
    
    bool match = true;

    // Checking if there are changes in report since the method was last called
    for (uint8_t i=0; i<RPT_GAMEPAD_LEN; i++) {
        if( buf[i] != oldPad[i] ) {
            match = false;
            break;
        }
    }
    // Calling Game Pad event handler
    if (!match && joyEvents) {
        // if (joystick is the logitech game pro) {
            // Overlay the struct on top of the data by casting it to
            // a new variable
            struct LogitechGamePro *jsdata = (LogitechGamePro *)buf;
 
            // Copy values from the temporary overlaid struct into
            // a real variable of the right type for the callback
            currentValues.x = jsdata->x;
            currentValues.y = jsdata->y;
            currentValues.z = jsdata->z;
            // etc

            // Call the callback with that real variable
            joyEvents->OnGamePadChanged(&currentValues);
        // } else if (joystick is some other joystick) {
        //     do the same as above with a different joystick struct
        // } ... etc ...

        for (uint8_t i=0; i<RPT_GAMEPAD_LEN; i++) oldPad[i] = buf[i];
    }
 
}
Majenko
  • 103,876
  • 5
  • 75
  • 133
  • thanks so much, this helped me to actually understand the report desc. It's unfortunate that I don't have the coding skills to rewrite the parse function to use the report descriptor. For changing the specific options for each joystick, I assume I would need to change the `struct GamePadEventData`? would I do something like this: (from example) `uint32_t x : 10;` `uint32_t y : 10;` and change the numbers after? I am probably wrong but do those numbers after correspond to the range of each axis? (mins/maxs), Thanks again – Vosem Media Jun 19 '20 at 16:33
  • 2
    Yep, using a packed struct like that is a good way of extracting the data. – Majenko Jun 19 '20 at 17:46
  • Hello, I used the struct and it is almost working perfectly. The only issue is that the twist is flickering between 256 and 768. I have edited a sample in my original question from the serial monitor. It happens whenever the slide isn't at the 0 and happens much more quickly when the slide is in motion. Any advice is appreciated thankyou – Vosem Media Jun 20 '20 at 13:32
  • 1
    Okay, I sorted the problem out. For anyone wondering I had the packed struct a bit wrong. Trial and error for about a minute sorted it for me but you could do it with the report descriptor instead. – Vosem Media Jun 20 '20 at 13:59
  • My issue is now selecting between structs. My original plan was to have an array for each joystick type and then selecting which array to use to fill in the numbers for the struct. Unfortunately, the order of each axis matters so that's a no go. I am not great at c but know the basics. How would you suggest changing between packed structs? Thanks – Vosem Media Jun 20 '20 at 14:14
  • 1
    You would probably want a big if-then-else (or a switch/case) to overlay the struct over the byte array of the incoming data (cast it), then copy the values from the struct into some standard other struct / array to use later in your program. – Majenko Jun 20 '20 at 14:18
  • Ah okay thankyou, that clears it up. – Vosem Media Jun 20 '20 at 19:25
  • Hey, I'm trying to wrap the struct in an if statement but its just giving me `expected unqualified-id before 'if'` Any ideas? Thanks – Vosem Media Jun 25 '20 at 19:20
  • 1
    Not without seeing your code. – Majenko Jun 25 '20 at 19:31
  • good point, I'll edit it into the question with a more details – Vosem Media Jun 25 '20 at 19:32
  • I added a section of a simple version of what I'm trying to do with some details of another way I went about it. – Vosem Media Jun 25 '20 at 19:39
  • @VosemMedia There's nothing wrong with that snippet. It compiles fine. And it's a perfectly valid way of doing it. – Majenko Jun 25 '20 at 19:43
  • It doesn't seem to compile for me, probably because its in a header file? I moved the header/cpp to the top of the ino and it still didn't compile. I added a larger portion of my code in. Thanks – Vosem Media Jun 25 '20 at 20:32
  • Your if must be inside a function. Executable code outside a function is not valid C. – Majenko Jun 25 '20 at 20:40
  • hmm interesting, thankyou. I added in a void function (edited in the original question) and now it's giving me `'GamePadEventData' does not name a type` from the .cpp file. Any further ideas? I don't know why putting it in a function/if statement is bringing these problems. Thanks again – Vosem Media Jun 25 '20 at 21:13
  • Your structure only exists within the scope it is defined in: in this case the function you just wrapped it in. – Majenko Jun 25 '20 at 21:55
  • so I should put everything in that file into a single function? – Vosem Media Jun 25 '20 at 22:06
  • You should put things in the areas where they are needed. You should first define multiple structs with different names. Then in your packet decoder you cast your incoming data to one of the structs depending on which joystick is connected. Then you copy your data from that cast variable struct to your live data struct. It's those last bits that go in the if, not the definition of the struct. – Majenko Jun 25 '20 at 22:28
  • So in my .h file, I should declare multiple structs for each type of joystick, then where and how should I move the data from the selected struct (e.g logitech joystick) into the live struct. Should I do it in the cpp file? if so, should I wrap all of the areas that require the struct in a single function? Thanks – Vosem Media Jun 26 '20 at 10:07
  • Yes, do it in the CPP file. In the parse function. Use casting of the data array: `if (joystick == LOGITECH_GAME_PRO) {struct LogitechGamePro *jsdata = (struct LogitechGamePro *)data; mydata.x = jsdata->x; ... }` for example. – Majenko Jun 26 '20 at 10:20
  • Thanks for the code, I have a couple of questions. If I declare my structs in the CPP file and change the data in the live struct in a CPP function, how will the .h functions get access to it? It's giving me a gamepaddata does not give a type, presumably because it's not defined in the .h file. How can the .h get access to it defined in the cpp? Also, from your code, would I edit to to look something like `struct GamePadEventData (the live one) *jsdata = (struct LogitechGamePro (the one we want to chage the live one to) *)data; mydata.x = jsdata->x; ` and also add more eg mydata.y = ... – Vosem Media Jun 26 '20 at 11:38
  • No. `GamePadEventData` is the struct that would define the variable you want to copy *to* (`mydata` in that example). – Majenko Jun 26 '20 at 12:20
  • Ah okay that makes more sense, so the mydata is an example struct which you copy to. I am still getting `virtual void OnGamePadChanged(const GamePadEventData *evt); exit status 1 'GamePadEventData' does not name a type` from the header. It's because I am declaring the structs in the CPP. What do you reccomend? Thanks – Vosem Media Jun 26 '20 at 13:10
  • Declare the structs in the .h file (that is, just the `struct foo {...};` portion). Use the structs in the CPP file as variable types. – Majenko Jun 26 '20 at 13:19
  • Okay, I removed all the nonsense I had before and am trying to get the bare bones working. I removed the casting of structs because that's a separate problem that ill tackle after this first one. I declared the structs in the .h like you recommended but populated them in the .cpp (as you can see in the code I edited in). Unfortunately, I am getting a redeclaration error (in the .cpp) because I have previously created them in the .h. I would have thought, just removing the `struct` variable type would have sorted it but I don't think you can do that in C. Thanks – Vosem Media Jun 27 '20 at 12:46
  • 1
    Your empty structs in the .h should be the full structs that are currently in the .cpp. The structs in the .cpp should not exist. – Majenko Jun 27 '20 at 13:15
  • 1
    I have edited in some example code to the end of my answer. – Majenko Jun 27 '20 at 13:23
  • Thankyou for all that example code, I think i'm finally wrapping my head around it all. its bringing up one error, `no matching function for call to 'JoystickEvents::OnGamePadChanged(GamePadEventData&)'` from the line `joyEvents->OnGamePadChanged(currentValues);` thanks. I also added in the full error into the bottom of my question – Vosem Media Jun 27 '20 at 14:27
  • Probably needs to be a pointer actually... – Majenko Jun 27 '20 at 15:23
  • Thank you so much, that got it working perfectly. I know the question was super old when I put a new comment and I would have understanded if you just ignored it. Cheers for helping me this has sorted all of my issues :) – Vosem Media Jun 27 '20 at 20:41
  • Glad to be of help. – Majenko Jun 27 '20 at 20:43