espressif / arduino-esp32

Arduino core for the ESP32
GNU Lesser General Public License v2.1
13.42k stars 7.37k forks source link

Can't instanciate multiple HID devices #9973

Open tankyx opened 3 months ago

tankyx commented 3 months ago

Board

ESP32S3

Device Description

DevkitM1, plain board, both USB ports connected to host

Hardware Configuration

Optical sensor connected to GPIO21, GPIO38, GPIO47, GPIO48, GND and 3v3

Version

latest master (checkout manually)

IDE Name

Arduino IDE 2.3.2

Operating System

Windows 10

Flash frequency

80MHz

PSRAM enabled

no

Upload speed

921600

Description

I am implementing an optical sensor using the USBHID.h library. It works well and I am able to send movements to the host. As soon as I instantiate a Custom HID device, the host won't receive the movements (I don't know if it is the board not sending or the host not receiving).

Sketch

#pragma once
#include <USB.h>
#include <USBHID.h>

#define TUD_HID_REPORT_DESC_MOUSE16(...) \
  HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP      )                   ,\
  HID_USAGE      ( HID_USAGE_DESKTOP_MOUSE     )                   ,\
  HID_COLLECTION ( HID_COLLECTION_APPLICATION  )                   ,\
    /* Report ID if any */\
    __VA_ARGS__ \
    HID_USAGE      ( HID_USAGE_DESKTOP_POINTER )                   ,\
    HID_COLLECTION ( HID_COLLECTION_PHYSICAL   )                   ,\
      HID_USAGE_PAGE  ( HID_USAGE_PAGE_BUTTON  )                   ,\
        HID_USAGE_MIN   ( 1                                      ) ,\
        HID_USAGE_MAX   ( 5                                      ) ,\
        HID_LOGICAL_MIN ( 0                                      ) ,\
        HID_LOGICAL_MAX ( 1                                      ) ,\
        /* Left, Right, Middle, Backward, Forward buttons */ \
        HID_REPORT_COUNT( 5                                      ) ,\
        HID_REPORT_SIZE ( 1                                      ) ,\
        HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\
        /* 3 bit padding */ \
        HID_REPORT_COUNT( 1                                      ) ,\
        HID_REPORT_SIZE ( 3                                      ) ,\
        HID_INPUT       ( HID_CONSTANT                           ) ,\
      HID_USAGE_PAGE  ( HID_USAGE_PAGE_DESKTOP )                   ,\
        /* X, Y position [-32767, 32767] */ \
        HID_USAGE       ( HID_USAGE_DESKTOP_X                    ) ,\
        HID_USAGE       ( HID_USAGE_DESKTOP_Y                    ) ,\
        HID_LOGICAL_MIN_N ( 0x8001, 2                            ) ,\
        HID_LOGICAL_MAX_N ( 0x7fff, 2                            ) ,\
        HID_REPORT_COUNT( 2                                      ) ,\
        HID_REPORT_SIZE ( 16                                     ) ,\
        HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_RELATIVE ) ,\
        /* Verital wheel scroll [-127, 127] */ \
        HID_USAGE       ( HID_USAGE_DESKTOP_WHEEL                )  ,\
        HID_LOGICAL_MIN ( 0x81                                   )  ,\
        HID_LOGICAL_MAX ( 0x7f                                   )  ,\
        HID_REPORT_COUNT( 1                                      )  ,\
        HID_REPORT_SIZE ( 8                                      )  ,\
        HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_RELATIVE )  ,\
      HID_USAGE_PAGE  ( HID_USAGE_PAGE_CONSUMER ), \
       /* Horizontal wheel scroll [-127, 127] */ \
        HID_USAGE_N     ( HID_USAGE_CONSUMER_AC_PAN, 2           ), \
        HID_LOGICAL_MIN ( 0x81                                   ), \
        HID_LOGICAL_MAX ( 0x7f                                   ), \
        HID_REPORT_COUNT( 1                                      ), \
        HID_REPORT_SIZE ( 8                                      ), \
        HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_RELATIVE ), \
    HID_COLLECTION_END                                            , \
  HID_COLLECTION_END

static const uint8_t rel16_mouse_report_descriptor[] = {
    TUD_HID_REPORT_DESC_MOUSE16(HID_REPORT_ID(HID_REPORT_ID_MOUSE))
};

enum MousePositioning_t {
  HID_MOUSE_RELATIVE,
  HID_MOUSE_ABSOLUTE
};

typedef struct TU_ATTR_PACKED
{
    uint8_t buttons;
    int16_t  x;
    int16_t  y;
    int8_t  wheel;
    int8_t  pan;
} hid_mouse_report_16_t;

struct HIDMouseType_t {
  MousePositioning_t positioning;
  const uint8_t *report_descriptor;
  size_t descriptor_size;
  size_t report_size;
};

HIDMouseType_t HIDMouseRel16 = { HID_MOUSE_RELATIVE, rel16_mouse_report_descriptor, sizeof(rel16_mouse_report_descriptor), sizeof(hid_mouse_report_16_t) };

class USBHIDRelativeMouse16 : public USBHIDDevice {
private:
  USBHID &_hid;
  uint8_t _buttons;

public:
    USBHIDRelativeMouse16(USBHID& hid) : _hid(hid) {
      _hid = hid;
      static bool initialized = false;
      if (!initialized) {
        initialized = true;
        _hid.addDevice(this, HIDMouseRel16.descriptor_size);
      }
    }

    uint16_t _onGetDescriptor(uint8_t *dst) {
      memcpy(dst, HIDMouseRel16.report_descriptor, HIDMouseRel16.descriptor_size);
      return HIDMouseRel16.descriptor_size;
    }

    void move(int16_t x, int16_t y, int8_t wheel = 0, int8_t pan = 0) {
        hid_mouse_report_16_t report = {
            .buttons = _buttons,
            .x       = x,
            .y       = y,
            .wheel   = wheel,
            .pan     = pan
        };
        _hid.SendReport(HID_REPORT_ID_MOUSE, &report, sizeof(hid_mouse_report_16_t));
    }

    void click(uint8_t b) {
        _buttons = b;
        move(0,0);
        _buttons = 0;
        move(0,0);
    }

    void buttons(uint8_t b) {
        if (b != _buttons) {
            _buttons = b;
            move(0,0);
        }
    }
};

#pragma once
#include <USB.h>
#include <USBHID.h>

#define TUD_HID_REPORT_DESC_CUSTOM(...) \
  HID_USAGE_PAGE ( HID_USAGE_PAGE_VENDOR     )                   ,\
  HID_USAGE      ( 0x01                                   )       ,\
  HID_COLLECTION ( HID_COLLECTION_APPLICATION )                    ,\
    /* Report ID if any */\
    __VA_ARGS__ \
    HID_USAGE      ( 0x02                                   )       ,\
    HID_LOGICAL_MIN ( 0                                      )       ,\
    HID_LOGICAL_MAX ( 0xFF                                   )       ,\
    HID_REPORT_SIZE ( 8                                      )       ,\
    HID_REPORT_COUNT( 64                                     )       ,\
    HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE )       ,\
    HID_OUTPUT      ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE )       ,\
  HID_COLLECTION_END

static const uint8_t custom_hid_report_descriptor[] = {
    TUD_HID_REPORT_DESC_CUSTOM(HID_REPORT_ID(69))
};

typedef struct TU_ATTR_PACKED
{
    uint8_t data[64];
} custom_hid_report_t;

struct HIDCustomType_t {
  const uint8_t *report_descriptor;
  size_t descriptor_size;
  size_t report_size;
};

HIDCustomType_t HIDCustom = { custom_hid_report_descriptor, sizeof(custom_hid_report_descriptor), sizeof(custom_hid_report_t) };

class USBHIDCustomDevice : public USBHIDDevice {
private:
  USBHID &_hid;
  custom_hid_report_t _report;

public:
    USBHIDCustomDevice(USBHID& hid) : _hid(hid) { 
      _hid = hid;
      static bool initialized = false;
      if (!initialized) {
        initialized = true;
        _hid.addDevice(this, HIDCustom.descriptor_size);
      }
    }

    uint16_t _onGetDescriptor(uint8_t *dst) {
      memcpy(dst, HIDCustom.report_descriptor, HIDCustom.descriptor_size);
      return HIDCustom.descriptor_size;
    }

    void sendReport(const uint8_t* data, size_t length) {
        if (length > sizeof(_report.data)) length = sizeof(_report.data);
        memcpy(_report.data, data, length);
        _hid.SendReport(69, &_report, sizeof(custom_hid_report_t));
    }

    void receiveReport(uint8_t* data, size_t length) {
        if (length > sizeof(_report.data)) length = sizeof(_report.data);
        memcpy(data, _report.data, length);
    }

    void _onOutput(uint8_t *report, size_t len) {
      // Handle the received report
      receiveReport(report, len);
    }
};

#include "Mouse16.h"
#include "PAW3370.h"
#include "CustomHIDDevice.h"

USBHID hid;

USBHIDRelativeMouse16 Mouse16(hid);
USBHIDCustomDevice customHID(hid);
PAW3370 sensor;

void setup() {
    Serial.begin(115200);
    SPI.begin(PIN_CLK, PIN_MISO, PIN_MOSI, PIN_CS);

    if (!sensor.init()) {
        Serial.println("Failed to initialize PAW3370");
        while (1);
    }
    hid.begin();
    USB.begin();
}

void loop() {
    int16_t delta_x, delta_y;

    if (!sensor.read_motion(&delta_x, &delta_y)) {
        Serial.println("Failed to read motion data");
    }

    uint8_t receivedData[64];
    //customHID.receiveReport(receivedData, sizeof(receivedData));
    Mouse16.move(delta_x, delta_y, 0, 0);
}

Debug Message

Board Info:
------------------------------------------
  Arduino Board     : ESP32S3_DEV
  Arduino Variant   : esp32s3
  Arduino FQBN      : esp32:esp32:esp32s3:UploadSpeed=921600,USBMode=hwcdc,CDCOnBoot=default,MSCOnBoot=default,DFUOnBoot=default,UploadMode=default,CPUFreq=240,FlashMode=qio,FlashSize=4M,PartitionScheme=default,DebugLevel=verbose,PSRAM=disabled,LoopCore=1,EventsCore=1,EraseFlash=none,JTAGAdapter=default,ZigbeeMode=default
============ Before Setup End ============
[   608][V][esp32-hal-uart.c:408] uartBegin(): UART0 baud(115200) Mode(800001c) rxPin(44) txPin(43)
[   617][V][esp32-hal-uart.c:497] uartBegin(): UART0 not installed. Starting installation
[   625][V][esp32-hal-uart.c:544] uartBegin(): UART0 initialization done.
[   632][V][esp32-hal-periman.c:235] perimanSetBusDeinit(): Deinit function for type SPI_MASTER_SCK (34) successfully set to 0x42006440
[   644][V][esp32-hal-periman.c:235] perimanSetBusDeinit(): Deinit function for type SPI_MASTER_MISO (35) successfully set to 0x42006368
[   656][V][esp32-hal-periman.c:235] perimanSetBusDeinit(): Deinit function for type SPI_MASTER_MOSI (36) successfully set to 0x42006290
[   668][V][esp32-hal-periman.c:235] perimanSetBusDeinit(): Deinit function for type SPI_MASTER_SS (37) successfully set to 0x4200617c
[   680][V][esp32-hal-periman.c:235] perimanSetBusDeinit(): Deinit function for type GPIO (1) successfully set to 0x420322a8
[   691][V][esp32-hal-periman.c:160] perimanSetPinBus(): Pin 48 successfully set to type GPIO (1) with bus 0x31
[   701][V][esp32-hal-periman.c:160] perimanSetPinBus(): Pin 48 successfully set to type SPI_MASTER_SCK (34) with bus 0x1
[   712][V][esp32-hal-periman.c:235] perimanSetBusDeinit(): Deinit function for type GPIO (1) successfully set to 0x420322a8
[   723][V][esp32-hal-periman.c:160] perimanSetPinBus(): Pin 47 successfully set to type GPIO (1) with bus 0x30
[   732][V][esp32-hal-periman.c:160] perimanSetPinBus(): Pin 47 successfully set to type SPI_MASTER_MISO (35) with bus 0x1
[   743][V][esp32-hal-periman.c:235] perimanSetBusDeinit(): Deinit function for type GPIO (1) successfully set to 0x420322a8
[   754][V][esp32-hal-periman.c:160] perimanSetPinBus(): Pin 38 successfully set to type GPIO (1) with bus 0x27
[   764][V][esp32-hal-periman.c:160] perimanSetPinBus(): Pin 38 successfully set to type SPI_MASTER_MOSI (36) with bus 0x1
[   875][V][esp32-hal-periman.c:235] perimanSetBusDeinit(): Deinit function for type GPIO (1) successfully set to 0x420322a8
[   886][V][esp32-hal-periman.c:160] perimanSetPinBus(): Pin 21 successfully set to type GPIO (1) with bus 0x16
INIT : Wrote to Power Up
........
[  1610][D][esp32-hal-tinyusb.c:655] tinyusb_load_enabled_interfaces(): Load Done: if_num: 1, descr_len: 41, if_mask: 0x4
=========== After Setup Start ============
INTERNAL Memory Info:
------------------------------------------
  Total Size        :   386140 B ( 377.1 KB)
  Free Bytes        :   344736 B ( 336.7 KB)
  Allocated Bytes   :    35988 B (  35.1 KB)
  Minimum Free Bytes:   339688 B ( 331.7 KB)
  Largest Free Block:   319476 B ( 312.0 KB)
------------------------------------------
GPIO Info:
------------------------------------------
  GPIO : BUS_TYPE[bus/unit][chan]
  --------------------------------------  
    21 : GPIO
    38 : SPI_MASTER_MOSI[0]
    43 : UART_TX[0]
    44 : UART_RX[0]
    47 : SPI_MASTER_MISO[0]
    48 : SPI_MASTER_SCK[0]
[  1944][V][USBHID.cpp:240] tud_hid_set_idle_cb(): instance: 0, idle_rate:0
[  1952][V][USBHID.cpp:219] tud_hid_descriptor_report_cb(): instance: 0
[  1960][D][USBHID.cpp:176] tinyusb_load_enabled_hid_devices(): Loaded HID Descriptor with the following reports:
[  1970][D][USBHID.cpp:179] tinyusb_load_enabled_hid_devices():   ID:   2, Type:   INPUT, Size:  7, Usage:    MOUSE
[  1980][D][USBHID.cpp:179] tinyusb_load_enabled_hid_devices():   ID:  69, Type:   INPUT, Size: 64, Usage:  GENERIC
[  1990][D][USBHID.cpp:179] tinyusb_load_enabled_hid_devices():   ID:  69, Type:  OUTPUT, Size: 64, Usage:  GENERIC
[  2042][E][USBHID.cpp:360] SendReport(): report 2 wait failed
[  2047][E][USBHID.cpp:347] SendReport(): not ready

Other Steps to Reproduce

I can see the following error in USBLogView Device Description

Unknown USB Device (Device Descriptor Request Failed)

I have checked existing issues, online documentation and the Troubleshooting Guide

me-no-dev commented 3 months ago

I tried your exact code, with the exception that I do not have PAW3370, so instead my main sketch looks like:

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  hid.begin();
  USB.begin();
  pinMode(0, INPUT_PULLUP);
}

void loop() {
  static bool btn = digitalRead(0);
  bool btn_now = digitalRead(0);
  if(btn != btn_now){
    btn = btn_now;
    if(!btn_now){
      Mouse16.click(1);
    }
  }
  delay(10);
}

Mouse works fine on my computer and I can see both devices in the USB tool I have. Main difference is that I am on a Mac and have no idea what things look like on Windows. Code looks fine and works fine, except you should not call customHID.receiveReport yourself. That will be called once the host sends data to the device.

tankyx commented 3 months ago

Indeed, I tried on both MBP M1 and MBP M3 and it was working without any issues, with both devices showing up. Probably some windows shenanigans happening. Could you please investigate ?

me-no-dev commented 3 months ago

I can't. I do not have a windows machine and the colleague that does just went out on a month long leave... One suggestion for you is to set the USB mode in the board menu to TinyUSB (I saw in the logs it was the default USB/JTAG selected)

tankyx commented 3 months ago

Yeah I have done that :

USB Mode to USB-OTG (TinyUSB) Upload mode UART0/Hardware CDC

I dont really understand why Windows is not allowing the Arduino to work as intended

me-no-dev commented 3 months ago

Maybe it does not like something in the vendor descriptor? Can you try with a standard TinyUSB one? https://github.com/espressif/arduino-esp32/blob/master/libraries/USB/examples/HIDVendor/HIDVendor.ino

tankyx commented 3 months ago

On Windows I have the following output :

[   890][E][USBHID.cpp:347] SendReport(): not ready
[   894][E][USBHID.cpp:347] SendReport(): not ready
[   899][E][USBHID.cpp:347] SendReport(): not ready
[   904][E][USBHID.cpp:347] SendReport(): not ready
[   908][E][USBHID.cpp:347] SendReport(): not ready
[   913][E][USBHID.cpp:347] SendReport(): not ready
[   918][E][USBHID.cpp:347] SendReport(): not ready
[   922][E][USBHID.cpp:347] SendReport(): not ready
[   927][E][USBHID.cpp:347] SendReport(): not ready
[   931][E][USBHID.cpp:347] SendReport(): not ready
[   936][E][USBHID.cpp:347] SendReport(): not ready
[   941][E][USBHID.cpp:347] SendReport(): not ready
[   945][E][USBHID.cpp:347] SendReport(): not ready
[   950][E][USBHID.cpp:347] SendReport(): not ready
[   955][E][USBHID.cpp:347] SendReport(): not ready
[   959][E][USBHID.cpp:347] SendReport(): not ready
[   964][E][USBHID.cpp:347] SendReport(): not ready
[   969][E][USBHID.cpp:347] SendReport(): not ready
[   973][E][USBHID.cpp:347] SendReport(): not ready
[   978][V][USBHID.cpp:240] tud_hid_set_idle_cb(): instance: 0, idle_rate:0
[   987][V][USBHID.cpp:219] tud_hid_descriptor_report_cb(): instance: 0
[   993][D][USBHID.cpp:176] tinyusb_load_enabled_hid_devices(): Loaded HID Descriptor with the following reports:
[  1004][D][USBHID.cpp:179] tinyusb_load_enabled_hid_devices():   ID:   6, Type:   INPUT, Size: 63, Usage:   VENDOR
[  1014][D][USBHID.cpp:179] tinyusb_load_enabled_hid_devices():   ID:   6, Type:  OUTPUT, Size: 63, Usage:   VENDOR
[  1024][D][USBHID.cpp:179] tinyusb_load_enabled_hid_devices():   ID:   6, Type: FEATURE, Size: 63, Usage:   VENDOR

And I can see it being plugged in as a USB Input Device with device type HID (Human Interface Device). Should I use that standard HIDVendor in my code ?

tankyx commented 3 months ago

I added the standard HIDVendor device to my code, I still get the issue on Windows. Not on MacOS though

#include "Mouse16.h"
#include "CustomHIDDevice.h"
#include "PAW3370.h"
#include "USBHIDVendor.h"

USBHIDRelativeMouse16 Mouse16;
//USBHIDCustomDevice customHID;
USBHIDVendor Vendor;
PAW3370 sensor;

static void vendorEventCallback(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) {
  if (event_base == ARDUINO_USB_HID_VENDOR_EVENTS) {
    arduino_usb_hid_vendor_event_data_t *data = (arduino_usb_hid_vendor_event_data_t *)event_data;
    switch (event_id) {
      case ARDUINO_USB_HID_VENDOR_GET_FEATURE_EVENT: Serial.printf("HID VENDOR GET FEATURE: len:%u\n", data->len); break;
      case ARDUINO_USB_HID_VENDOR_SET_FEATURE_EVENT:
        Serial.printf("HID VENDOR SET FEATURE: len:%u\n", data->len);
        for (uint16_t i = 0; i < data->len; i++) {
          Serial.printf("0x%02X ", *(data->buffer));
        }
        Serial.println();
        break;
      case ARDUINO_USB_HID_VENDOR_OUTPUT_EVENT:
        Serial.printf("HID VENDOR OUTPUT: len:%u\n", data->len);
        for (uint16_t i = 0; i < data->len; i++) {
          Serial.printf("0x%02X ", *(data->buffer));
        }
        break;

      default: break;
    }
  }
}

void setup() {
    Serial.begin(115200);
    SPI.begin(PIN_CLK, PIN_MISO, PIN_MOSI, PIN_CS);

    if (!sensor.init()) {
        Serial.println("Failed to initialize PAW3370");
        while (1);
    }
    Vendor.onEvent(vendorEventCallback);
    Mouse16.begin();
    Vendor.begin();
    USB.begin();
}

void loop() {
    int16_t delta_x, delta_y;

    if (!sensor.read_motion(&delta_x, &delta_y)) {
        Serial.println("Failed to read motion data");
    }

    if (delta_x != 0 || delta_y != 0) {
      Mouse16.move(delta_x, delta_y, 0, 0);
    }

    if (Vendor.available()) {
      Vendor.read();
    }
}
tankyx commented 3 months ago

@me-no-dev Couple of updates on the matter :

me-no-dev commented 2 months ago

That means that Windows does not like something in your vendor descriptor. I wonder what... what is different from the default one?

tankyx commented 2 months ago

I don't really know to be fair. Now I have the custom device showing up, but I can't communicate with it.

#pragma once
#include <USB.h>
#include <USBHID.h>
#include "Mouse16.h"

#define HID_VENDOR_REPORT_SIZE 64

static const uint8_t report_descriptor[] = {
    0x06, 0x00, 0xff,              // Usage Page (Vendor Defined 0xFF00)
    0x09, 0x01,                    // Usage (Vendor Usage 0x01)
    0xa1, 0x01,                    // Collection (Application)
    0x85, 0x06,                    //   Report ID (6)
    0x15, 0x00,                    //   Logical Minimum (0)
    0x26, 0xff, 0x00,              //   Logical Maximum (255)
    0x75, 0x08,                    //   Report Size (8 bits)
    0x95, 0x01,                    //   Report Count (1)
    0x09, 0x01,                    //   Usage (Vendor Usage 0x01)
    0x81, 0x02,                    //   Input (Data, Var, Abs)
    0x15, 0x00,                    //   Logical Minimum (0)
    0x26, 0xff, 0x7f,              //   Logical Maximum (32767)
    0x75, 0x10,                    //   Report Size (16 bits)
    0x95, 0x02,                    //   Report Count (2)
    0x09, 0x02,                    //   Usage (Vendor Usage 0x02)
    0x09, 0x03,                    //   Usage (Vendor Usage 0x03)
    0x81, 0x02,                    //   Input (Data, Var, Abs)
    0x75, 0x08,                    //   Report Size (8 bits)
    0x95, 0x39,                    //   Report Count (57)
    0x09, 0x04,                    //   Usage (Vendor Usage 0x04)
    0x81, 0x02,                    //   Input (Data, Var, Abs)
    0xc0                           // End Collection
};

struct HIDReport {
    uint8_t reportId;
    int16_t dx;
    int16_t dy;
    uint8_t padding[57];
};

class USBHIDCustomDevice : public USBHIDDevice {
private:
    USBHID _hid;
    USBHIDRelativeMouse16 _mouse;

public:
    USBHIDCustomDevice(USBHIDRelativeMouse16& mouse) : _hid() {
        static bool initialized = false;
        if (!initialized) {
            initialized = true;
            _hid.addDevice(this, sizeof(report_descriptor));
        }
        _mouse = mouse;
    }

    void begin() {
        _hid.begin();
    }

    uint16_t _onGetDescriptor(uint8_t *dst) {
        memcpy(dst, report_descriptor, sizeof(report_descriptor));
        return sizeof(report_descriptor);
    }

    void receiveReport(const uint8_t* data, size_t length) {
        int16_t dx = (int16_t)(data[1] | (data[2] << 8));
        int16_t dy = (int16_t)(data[3] | (data[4] << 8));
        _mouse.move(dx, dy, 0, 0);
    }

    void _onSetFeature(uint8_t report_id, const uint8_t *buffer, uint16_t len) {
      receiveReport(buffer, len);
    }

    void _onOutput(uint8_t *report, size_t len) {
        receiveReport(report, len);
    }
};

#pragma once
#include <USB.h>
#include <USBHID.h>

#define TUD_HID_REPORT_DESC_MOUSE16(...) \
  HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP      )                   ,\
  HID_USAGE      ( HID_USAGE_DESKTOP_MOUSE     )                   ,\
  HID_COLLECTION ( HID_COLLECTION_APPLICATION  )                   ,\
    /* Report ID if any */\
    __VA_ARGS__ \
    HID_USAGE      ( HID_USAGE_DESKTOP_POINTER )                   ,\
    HID_COLLECTION ( HID_COLLECTION_PHYSICAL   )                   ,\
      HID_USAGE_PAGE  ( HID_USAGE_PAGE_BUTTON  )                   ,\
        HID_USAGE_MIN   ( 1                                      ) ,\
        HID_USAGE_MAX   ( 5                                      ) ,\
        HID_LOGICAL_MIN ( 0                                      ) ,\
        HID_LOGICAL_MAX ( 1                                      ) ,\
        /* Left, Right, Middle, Backward, Forward buttons */ \
        HID_REPORT_COUNT( 5                                      ) ,\
        HID_REPORT_SIZE ( 1                                      ) ,\
        HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\
        /* 3 bit padding */ \
        HID_REPORT_COUNT( 1                                      ) ,\
        HID_REPORT_SIZE ( 3                                      ) ,\
        HID_INPUT       ( HID_CONSTANT                           ) ,\
      HID_USAGE_PAGE  ( HID_USAGE_PAGE_DESKTOP )                   ,\
        /* X, Y position [-32767, 32767] */ \
        HID_USAGE       ( HID_USAGE_DESKTOP_X                    ) ,\
        HID_USAGE       ( HID_USAGE_DESKTOP_Y                    ) ,\
        HID_LOGICAL_MIN_N ( 0x8001, 2                            ) ,\
        HID_LOGICAL_MAX_N ( 0x7fff, 2                            ) ,\
        HID_REPORT_COUNT( 2                                      ) ,\
        HID_REPORT_SIZE ( 16                                     ) ,\
        HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_RELATIVE ) ,\
        /* Verital wheel scroll [-127, 127] */ \
        HID_USAGE       ( HID_USAGE_DESKTOP_WHEEL                )  ,\
        HID_LOGICAL_MIN ( 0x81                                   )  ,\
        HID_LOGICAL_MAX ( 0x7f                                   )  ,\
        HID_REPORT_COUNT( 1                                      )  ,\
        HID_REPORT_SIZE ( 8                                      )  ,\
        HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_RELATIVE )  ,\
      HID_USAGE_PAGE  ( HID_USAGE_PAGE_CONSUMER ), \
       /* Horizontal wheel scroll [-127, 127] */ \
        HID_USAGE_N     ( HID_USAGE_CONSUMER_AC_PAN, 2           ), \
        HID_LOGICAL_MIN ( 0x81                                   ), \
        HID_LOGICAL_MAX ( 0x7f                                   ), \
        HID_REPORT_COUNT( 1                                      ), \
        HID_REPORT_SIZE ( 8                                      ), \
        HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_RELATIVE ), \
    HID_COLLECTION_END                                            , \
  HID_COLLECTION_END

static const uint8_t rel16_mouse_report_descriptor[] = {
    TUD_HID_REPORT_DESC_MOUSE16(HID_REPORT_ID(HID_REPORT_ID_MOUSE))
};

enum MousePositioning_t {
  HID_MOUSE_RELATIVE,
  HID_MOUSE_ABSOLUTE
};

typedef struct TU_ATTR_PACKED
{
    uint8_t buttons;
    int16_t  x;
    int16_t  y;
    int8_t  wheel;
    int8_t  pan;
} hid_mouse_report_16_t;

struct HIDMouseType_t {
  MousePositioning_t positioning;
  const uint8_t *report_descriptor;
  size_t descriptor_size;
  size_t report_size;
};

HIDMouseType_t HIDMouseRel16 = { HID_MOUSE_RELATIVE, rel16_mouse_report_descriptor, sizeof(rel16_mouse_report_descriptor), sizeof(hid_mouse_report_16_t) };

class USBHIDRelativeMouse16 : public USBHIDDevice {
private:
  USBHID _hid;
  uint8_t _buttons;

public:
    USBHIDRelativeMouse16() : _hid() {
      static bool initialized = false;
      if (!initialized) {
        initialized = true;
        _hid.addDevice(this, HIDMouseRel16.descriptor_size);
      }
    }

    void begin() {
      _hid.begin();
    }

    uint16_t _onGetDescriptor(uint8_t *dst) {
      memcpy(dst, HIDMouseRel16.report_descriptor, HIDMouseRel16.descriptor_size);
      return HIDMouseRel16.descriptor_size;
    }

    void move(int16_t x, int16_t y, int8_t wheel = 0, int8_t pan = 0) {
        hid_mouse_report_16_t report = {
            .buttons = _buttons,
            .x       = x,
            .y       = y,
            .wheel   = wheel,
            .pan     = pan
        };
        Serial.printf("Sending x = %d | y = %d\n", x, y);
        _hid.SendReport(HID_REPORT_ID_MOUSE, &report, sizeof(hid_mouse_report_16_t));
    }

    void click(uint8_t b) {
        _buttons = b;
        move(0,0);
        _buttons = 0;
        move(0,0);
    }

    void buttons(uint8_t b) {
        if (b != _buttons) {
            _buttons = b;
            move(0,0);
        }
    }
};

This is the definition of my two devices.

Following is my host code :

void MouseController::sendHIDReport(int16_t dx, int16_t dy) {
    HIDReport report;

    std::cout << "Sending HID report: " << dx << " " << dy << std::endl;

    report.reportId = 0x06; // Use HID_REPORT_ID_VENDOR = 0x06
    report.dx = dx;
    report.dy = dy;
    memset(report.padding, 0, sizeof(report.padding));

    DWORD bytesWritten;
    std::cout << "Size of report: " << sizeof(report) << std::endl;
    //BOOL res = WriteFile(hidDevice, &report, sizeof(report), &bytesWritten, NULL);
    BOOL res = HidD_SetOutputReport(hidDevice, &report, sizeof(report));
    if (!res) {
        int err = GetLastError();
        std::cerr << "Failed to send HID report: " << err << std::endl;
        if (err == 995 || err == 1167) {
            std::cerr << "Device disconnected" << std::endl;
            CloseHandle(hidDevice);
            hidDevice = nullptr;
            Sleep(2000); // Wait for device to reconnect
            ConnectToDevice();
        } else {
            exit(1);
        }
    } else {
        std::cout << "HID report sent successfully: " << dx << " " << dy << std::endl;
    }
}

It seems also that my device reboots when it receives too much data for too long

ChrGri commented 1 month ago

Not sure if this is related, but I do have trouble using CDC and HID simultaneously. After some time, one or the other seems to stall, see https://github.com/espressif/arduino-esp32/issues/9582#issuecomment-2096691001.

BR