summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkartofen <mladenovnasko0@gmail.com>2025-06-08 23:16:33 +0300
committerkartofen <mladenovnasko0@gmail.com>2025-06-08 23:16:33 +0300
commit227f73622b4576bf21719fcc9ea99416056d8a71 (patch)
tree1ab6d7c0e8dab40aa26def1a2ea6098134e82ea8
init, have brushed and brushless sim and gnuplot viz, also some usb hid testingHEADmaster
-rwxr-xr-xbuild.sh6
-rw-r--r--main.c386
-rw-r--r--plot.gnu19
-rw-r--r--plot2.gnu19
-rw-r--r--transpose.h54
-rw-r--r--uhid_example.c465
-rw-r--r--wheel_hid_report_desc.h853
-rw-r--r--wheel_read.c52
-rw-r--r--wheel_uhid.c264
9 files changed, 2118 insertions, 0 deletions
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000..ec5ad91
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,6 @@
+set -xe
+
+gcc -Wall -Wextra -o wheel_read wheel_read.c
+gcc -Wall -Wextra -o wheel_uhid wheel_uhid.c
+
+gcc -Wall -Wextra -lm -mavx -o main main.c
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..5facab5
--- /dev/null
+++ b/main.c
@@ -0,0 +1,386 @@
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <math.h>
+#include "transpose.h"
+
+#define PI 3.1415
+
+// utils
+#define typecheck(type, var) \
+ (_Static_assert(__builtin_types_compatible_p(type, typeof(var)), "Incompatible Types!"))
+#define MAX(a,b) \
+ ({ typeof(a) _a = (a); \
+ typeof(b) _b = (b); \
+ _a > _b ? _a : _b; })
+#define MIN(a,b) \
+ ({ typeof(a) _a = (a); \
+ typeof(b) _b = (b); \
+ _a > _b ? _b : _a; })
+#define ARR_LENGTH(arr) \
+ (sizeof((arr))/sizeof((arr)[0]))
+#define CLOSEST_POW2(n) \
+ (1UL << (32 - __builtin_clz((unsigned int)((n) - 1))))
+#define OVERLOAD_MACRO(_0, _1, _2, _3, _4, NAME, ...) NAME
+
+// types for typedefing
+#define VECTOR(vec, type, nmemb) \
+ type vec##nmemb __attribute__((vector_size(CLOSEST_POW2(nmemb * sizeof(type)))))
+#define MATRIX(mat, vec, n, m) \
+ struct { vec##m c[n]; } mat##n##x##m
+
+// vector operations
+#define VEC(...) {__VA_ARGS__}
+#define vec_element(vec, n) ((vec)[(n)])
+#define vec_promote(vec, type) \
+ ({ __builtin_choose_expr( \
+ __builtin_types_compatible_p(typeof(vec), type), \
+ *(type *)&vec, \
+ ({ type res = {0}; \
+ memcpy(&res, &vec, \
+ MIN(sizeof(res), sizeof(vec))); \
+ res; })); \
+ })
+#define vec_print2(vec, fmt) \
+ ({ printf("{ "); \
+ for(size_t i = 0; \
+ i < ARR_LENGTH(vec); i++) \
+ printf(fmt" ", vec[i]); \
+ printf("}\n"); \
+ })
+#define vec_print3(vec, fmt, nmemb) \
+ ({ printf("{ "); \
+ for(size_t i = 0; \
+ i < nmemb; i++) \
+ printf(fmt" ", vec[i]); \
+ printf("}\n"); \
+ })
+#define vec_print(...) \
+ OVERLOAD_MACRO(_0, ##__VA_ARGS__, _4, vec_print3, \
+ vec_print2, _1, _0) (__VA_ARGS__)
+
+// matrix operations
+#define MAT(...) {{ TRANSPOSE(__VA_ARGS__) }}
+#define mat_element(mat, n, m) ((mat).c[(n)][(m)])
+#define mat_column(mat, n) ((mat).c[(n)])
+#define mat_promote(mat, type) \
+ ({ __builtin_choose_expr( \
+ __builtin_types_compatible_p(typeof(mat), type), \
+ *(type *)&mat, \
+ ({ type res = {0}; \
+ for(size_t i = 0; i < MIN(ARR_LENGTH(mat.c), \
+ ARR_LENGTH(res.c)); i++) \
+ res.c[i] = vec_promote(mat.c[i], typeof(res.c[i])); \
+ res; })); \
+ })
+#define mat_print4(mat, fmt, n, m) \
+ ({ for(int i = 0; i < m; i++) { \
+ printf("{ "); \
+ for(int j = 0; j < n; j++) \
+ printf(fmt" ", mat.c[j][i]); \
+ printf("}\n"); \
+ } \
+ })
+#define mat_print2(mat, fmt) \
+ ({ size_t n = ARR_LENGHT((mat).c); \
+ size_t m = ARR_LENGHT((mat).c[0]); \
+ for(int i = 0; i < m; i++) { \
+ printf("{ "); \
+ for(int j = 0; j < n; j++) \
+ printf(fmt" ", (mat).c[j][i]); \
+ printf("}\n"); \
+ } \
+ })
+#define mat_print(...) \
+ OVERLOAD_MACRO(_0, ##__VA_ARGS__, mat_print4, _3, \
+ mat_print2, _1, _0) (__VA_ARGS__)
+
+// mixed operations
+#define vecmat_mul(_v, _m) \
+ ({ typeof(_m.c[0]) res = {0}; \
+ size_t n = MIN(ARR_LENGTH(_m.c), ARR_LENGTH((_v))); \
+ for(size_t i = 0; i < n; i++) \
+ res += _m.c[i] * _v[i]; \
+ res; })
+
+typedef VECTOR(vec, double, 2);
+typedef VECTOR(vec, double, 3);
+typedef VECTOR(vec, double, 6);
+typedef MATRIX(mat, vec, 2, 2);
+typedef MATRIX(mat, vec, 3, 2);
+
+typedef VECTOR(vec, double, 1);
+typedef VECTOR(vec, double, 5);
+typedef VECTOR(vec, double, 4);
+typedef MATRIX(mat, vec, 3, 3);
+typedef MATRIX(mat, vec, 2, 3);
+typedef MATRIX(mat, vec, 3, 1);
+typedef MATRIX(mat, vec, 4, 4);
+typedef MATRIX(mat, vec, 5, 5);
+typedef MATRIX(mat, vec, 4, 5);
+
+// todo: smart current limit
+// trapezoid pid
+
+struct pid_ctx {
+ double Kp;
+ double Ki;
+ double Kd;
+
+ double prev_e;
+ double Ie;
+};
+
+double pid(struct pid_ctx *ctx, double measurement, double setpoint, double dt)
+{
+ double e = setpoint - measurement;
+ double De = (e - ctx->prev_e) / dt;
+ ctx->Ie += e * dt;
+ ctx->prev_e = e;
+
+ return
+ ctx->Kp * e +
+ ctx->Kd * De +
+ ctx->Ki * ctx->Ie;
+}
+
+int brushed(void)
+{
+ char filename[] = "data1.dat";
+
+ // PID
+ double setpoint = 4;
+ struct pid_ctx pid_velocity = {20.0, 0.0, 0.0, 0.0, 0.0};
+ struct pid_ctx pid_position = {30.0, 0.0, 0.0};
+
+ // times
+ double dt = 0.00001;
+ double tfinal = 0.5;
+
+ // limits
+ double voltage_max = 12;
+ double current_max = 30;
+ double torque_lose_rate = 1;
+
+
+ // motor constants
+ // double J = 300 * 0.1; // rotor inertia [kg*m^2]
+ // double b = 10 * 0.5; // damping coefficient [-]
+ // double Kt = 10 * 1.0; // torque constant [Nm/A]
+ // double L = 100 * 1.0; // inductance [H]
+ // double R = 20 * 5.0; // resistance [Ohm]
+ // double Ke = 40 * 1.0; // back emf constant [V/m s^-1]
+
+ double J = 0.0044; // rotor inertia [kg*m^2]
+ double b = 0.0011; // damping coefficient [Nm*s/rad]
+ double Kt = 0.22; // torque constant [Nm/A]
+ double L = 0.01; // inductance [H]
+ double R = 4; // resistance [Ohm]
+ double Ke = 0.22; // back emf constant [Vs/rad]
+
+ // state space
+ mat3x3 A = MAT((0.0, 1.0, 0.0),
+ (0.0, -b/J, Kt/J),
+ (0.0, -Ke/L, -R/L));
+
+ mat2x3 B = MAT((0.0, 0.0),
+ (0.0, -1.0/J),
+ (1.0/L, 0.0));
+
+ // mat3x1 C = MAT((0.0, 1.0, 0.0));
+
+
+ vec3 x = VEC(0.0, 0.0, 0.0);
+ vec2 u = VEC(0.0, 0.0);
+
+ // Simulation
+ FILE *fp = fopen(filename, "w");
+ if(!fp) return 1;
+
+ for(double i = 0.0f; i < tfinal; i += dt) {
+ // state space and euler integration
+ vec3 xdot = vecmat_mul(x, A) + vecmat_mul(u, B);
+ x += xdot * dt;
+
+ // pid control
+ if((int)i*100000 % 1000 == 0) {
+ double velocity_setpoint =
+ pid(&pid_position, vec_element(x, 0), setpoint, dt);
+ vec_element(u, 0) =
+ pid(&pid_velocity, vec_element(x, 1), velocity_setpoint, dt);
+
+ }
+
+ // limits
+ if(vec_element(u, 0) > voltage_max) vec_element(u, 0) = voltage_max;
+ // if(vec_element(x, 2) > current_max) vec_element(x, 2) = current_max;
+
+ // timed inputs
+#define around(i, v) ((i) > ((v)-0.000005) && (i) < ((v)+0.000005))
+#define torque_surge(t, torque) if(around(i, t)) vec_element(u, 1) = (torque)
+
+ // torque limits
+ if(vec_element(u, 1) < 0.0) vec_element(u, 1) = 0.0;
+ if(vec_element(u, 1) > 0.0) vec_element(u, 1) -= torque_lose_rate;
+
+ // torque_surge(0.025, 0.2);
+ // torque_surge(0.075, 0.2);
+ // torque_surge(0.100, 0.2);
+ // torque_surge(0.250, 0.2);
+ torque_surge(0.300, 20);
+
+ // setpoint movement
+ if(around(i, 2.25)) setpoint = 5;
+
+ fprintf(fp, "%f %f %f %f %f %f\n", i,
+ vec_element(x, 0),
+ vec_element(x, 1),
+ vec_element(x, 2),
+ vec_element(u, 0),
+ vec_element(u, 1));
+ }
+
+ fclose(fp);
+ return 0;
+}
+
+double fa(double t) {return sin(t); }
+double fb(double t) {return fa(t + (2.0/3.0 * PI)); }
+double fc(double t) {return fb(t + (2.0/3.0 * PI)); }
+
+int brushless()
+{
+ // motor constants
+ double R = 1.0;
+ double L = 1.1;
+ double M = 1.0;
+ double J = 1.0;
+ double b = 1.0;
+ double P = 2.0;
+ double l = 1.0;
+
+ // state space
+#define L1 (L-M)
+#define emfa(a) l/J*fa(a)
+#define emfb(a) l/J*fb(a)
+#define emfc(a) l/J*fc(a)
+#define _SQRT3_2 (sqrt(3.0)/2.0)
+
+#define A_MAT(a) \
+ MAT((-R/L1, 0, 0, -emfa(a), 0), \
+ (0, -R/L1, 0, -emfb(a), 0), \
+ (0, 0, -R/L1, -emfc(a), 0), \
+ (emfa(a), emfb(a), emfc(a), -b/J, 0), \
+ (0, 0, 0, P/2.0, 0))
+
+#define emf_VEC(o, a) ((vec3)VEC(fa(a), fb(a), fc(a)) * l * o);
+
+ mat5x5 A = A_MAT(0);
+
+ mat4x5 B = MAT((1.0/L1, 0, 0, 0),
+ (0, 1.0/L1, 0, 0),
+ (0, 0, 1.0/L1, 0),
+ (0, 0, 0, 1.0/L1),
+ (0, 0, 0, 0));
+
+ vec5 x = VEC(0);
+ vec4 u = VEC(0);
+ vec3 emf = emf_VEC(0, 0);
+
+ mat2x3 clarke = MAT((1.0, 0.0),
+ (-0.5, _SQRT3_2),
+ (-0.5, -_SQRT3_2));
+
+ // simulation
+ char filename[] = "data2.dat";
+ double time = 100;
+ double dt = 0.0001;
+
+ double U = 0.001;
+
+ FILE *fp = fopen(filename, "w");
+ if(!fp) return 1;
+
+ double i2 = 0;
+ for(double i = 0; i < time; i += dt) {
+ A = (mat5x5) A_MAT(vec_element(x, 4));
+ emf = (vec3) emf_VEC(vec_element(x, 3), vec_element(x, 4));
+
+ vec5 xdot = vecmat_mul(x, A) + vecmat_mul(u, B) + vec_promote(emf, vec5);
+ x += xdot * dt;
+
+ if((int)i*1000 % 100 == 0) {
+ vec3 voltages = vecmat_mul(
+ U * (vec2)VEC(sin(vec_element(x, 4)),
+ cos(vec_element(x, 4))),
+ clarke) + 0.001;
+
+
+ // u = vec_promote(voltages, vec4);
+ u = (vec4)VEC(0.0, 1.0, -1.0);
+ }
+
+ fprintf(fp, "%f %f %f %f %f %f %f %f %f %f\n", i,
+ vec_element(x, 0),
+ vec_element(x, 1),
+ vec_element(x, 2),
+ vec_element(x, 3),
+ vec_element(x, 4),
+ vec_element(u, 0),
+ vec_element(u, 1),
+ vec_element(u, 2),
+ vec_element(u, 3));
+ }
+
+}
+
+int main(void)
+{
+ // return brushed();
+ return brushless();
+}
+
+void vec_example(void)
+{
+ // vec2 v2 = VEC(1.0, 2.5);
+ // vec3 v3 = vec_promote(v2, typeof(v3));
+ // vec_element(v3, 2) = 8;
+
+ // mat3x2 m = MAT((1.0, 2.0, -1.0),
+ // (0.0, 1.0, 2.5));
+ // mat_element(m, 0, 1) = 6.9;
+
+ // vec2 r = vecmat_mul(v3, m);
+
+ // vec_print(v3, "%.2f", 3);
+ // printf(" times\n");
+ // mat_print(m, "%.2f", 3, 2);
+ // printf(" =\n");
+ // vec_print(r, "%.2f", 2);
+
+ // mat4x6 m2 = mat_promote(m, typeof(m2));
+ // printf("\npromoted bigger\n");
+ // mat_print(m2, "%.2f", 4, 6);
+
+ // mat2x2 m3 = mat_promote(m2, typeof(m3));
+ // printf("\npromoted smaller\n");
+ // mat_print(m3, "%.2f", 2, 2);
+
+ // FILE *fp = fopen("test.dat", "w");
+ // // fprintf(fp, "TIME, NUMBER1, NUMBER2\n");
+ // for(size_t i = 0; i < 1000000; i++) {
+ // // fprintf(fp, "%ld,%ld,%ld\n", i, i % 256, i % 137);
+ // fprintf(fp, "%ld %ld %ld\n", i, i % 256, i % 137);
+ // fflush(fp);
+ // usleep(50000);
+ // }
+ // fclose(fp);
+
+
+ vec2 _tv21 = {0};
+ vec2 _tv22 = {1};
+ vec2 _tv23 = _tv21 + _tv22;
+ vec6 _tv61 = {0};
+ vec6 _tv62 = {1};
+ vec6 _tv63 = _tv61 + _tv62;
+}
diff --git a/plot.gnu b/plot.gnu
new file mode 100644
index 0000000..8d42553
--- /dev/null
+++ b/plot.gnu
@@ -0,0 +1,19 @@
+set title "Some Sample Plots"
+
+set ylabel "rad/s"
+set y2label "I"
+set y2tics
+
+set grid
+
+set style line 1 \
+ linecolor rgb '#0060ad' \
+ linetype 1 linewidth 3
+
+set style line 2 \
+ linecolor rgb '#dd181f' \
+ linetype 2 linewidth 3 \
+
+ plot 'data1.dat' using 1:2 title "Angular Speed" with lines linestyle 1 axis x1y1, \
+ 'data1.dat' using 1:4 title "Current" with lines linestyle 2 axis x1y2
+ pause -1
diff --git a/plot2.gnu b/plot2.gnu
new file mode 100644
index 0000000..c72d5f8
--- /dev/null
+++ b/plot2.gnu
@@ -0,0 +1,19 @@
+set title "Some Sample Plots"
+
+set ylabel "-"
+set y2tics
+set grid
+
+set style line 1 \
+ linecolor rgb '#0060ad' \
+ linetype 1 linewidth 3
+
+set style line 2 \
+ linecolor rgb '#dd181f' \
+ linetype 2 linewidth 3 \
+
+ plot 'data2.dat' using 1:5 title "Angular Speed" with lines linestyle 1 axis x1y1, \
+ 'data2.dat' using 1:7 title "Angular Speed" with lines linestyle 2 axis x1y2, \
+ 'data2.dat' using 1:8 title "Angular Speed" with lines linestyle 2 axis x1y2, \
+ 'data2.dat' using 1:9 title "Angular Speed" with lines linestyle 2 axis x1y2,
+ pause -1
diff --git a/transpose.h b/transpose.h
new file mode 100644
index 0000000..02471ff
--- /dev/null
+++ b/transpose.h
@@ -0,0 +1,54 @@
+#define EMPTY()
+#define EXPAND(...) __VA_ARGS__
+#define DEFER(id) id EMPTY()
+#define OBSTRUCT(...) __VA_ARGS__ DEFER(EMPTY)()
+#define CAR(a, ...) a
+#define CDR(a, ...) __VA_OPT__((__VA_ARGS__))
+
+#define I_A() A
+#define I_B() B
+#define I_C() C
+
+#ifndef DELIM
+ #define DELIM COMMA
+#endif
+#ifndef P
+ #define P(...) { __VA_ARGS__ } COMMA
+#endif
+
+#define COMMA ,
+#define HAS(b, ...) __VA_OPT__(__VA_ARGS__ b)
+
+#define EVAL(...) EVAL1(EVAL1(EVAL1(__VA_ARGS__)))
+#define EVAL1(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__)))
+#define EVAL2(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__)))
+#define EVAL3(...) EVAL4(EVAL4(EVAL4(__VA_ARGS__)))
+#define EVAL4(...) EVAL5(EVAL5(EVAL5(__VA_ARGS__)))
+#define EVAL5(...) EVAL6(EVAL6(EVAL6(__VA_ARGS__)))
+#define EVAL6(...) EVAL7(EVAL7(EVAL7(__VA_ARGS__)))
+#define EVAL7(...) __VA_ARGS__
+
+// #define DEFER0(...) DEFER1(DEFER1(DEFER1(__VA_ARGS__)))
+// #define DEFER1(...) DEFER2(DEFER2(DEFER2(__VA_ARGS__)))
+// #define DEFER2(...) DEFER3(DEFER3(DEFER3(__VA_ARGS__)))
+// #define DEFER3(...) DEFER4(DEFER4(DEFER4(__VA_ARGS__)))
+// #define DEFER4(...) DEFER5(DEFER5(DEFER5(__VA_ARGS__)))
+// #define DEFER5(...) DEFER6(DEFER6(DEFER6(__VA_ARGS__)))
+// #define DEFER6(...) DEFER (DEFER (DEFER (__VA_ARGS__)))
+
+#define A(a, ...) HAS(__VA_OPT__(DELIM OBSTRUCT(I_A)()(__VA_ARGS__)), CAR a)
+#define B(a, ...) HAS(__VA_OPT__(COMMA OBSTRUCT(I_B)()(__VA_ARGS__)), CDR a)
+#define C(...) __VA_OPT__(P(A(__VA_ARGS__)) DEFER(OBSTRUCT(I_C)())DEFER(EXPAND)((B(__VA_ARGS__))))
+
+#define TRANSPOSE(...) EVAL(C(__VA_ARGS__))
+
+// #define TEST
+#ifdef TEST
+ TRANSPOSE((1, 2, 3), (4, 5, 6))
+ TRANSPOSE((1, 2, 3), (4, 5, 6), (4, 5, 6), (4, 5, 6), (4, 5, 6))
+ // EXPAND(EXPAND(EXPAND(EXPAND(EXPAND(EXPAND(B((1, 4), (2, 6))))))))
+#endif
+
+
+
+
diff --git a/uhid_example.c b/uhid_example.c
new file mode 100644
index 0000000..015cb06
--- /dev/null
+++ b/uhid_example.c
@@ -0,0 +1,465 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * UHID Example
+ *
+ * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * The code may be used by anyone for any purpose,
+ * and can serve as a starting point for developing
+ * applications using uhid.
+ */
+
+/*
+ * UHID Example
+ * This example emulates a basic 3 buttons mouse with wheel over UHID. Run this
+ * program as root and then use the following keys to control the mouse:
+ * q: Quit the application
+ * 1: Toggle left button (down, up, ...)
+ * 2: Toggle right button
+ * 3: Toggle middle button
+ * a: Move mouse left
+ * d: Move mouse right
+ * w: Move mouse up
+ * s: Move mouse down
+ * r: Move wheel up
+ * f: Move wheel down
+ *
+ * Additionally to 3 button mouse, 3 keyboard LEDs are also supported (LED_NUML,
+ * LED_CAPSL and LED_SCROLLL). The device doesn't generate any related keyboard
+ * events, though. You need to manually write the EV_LED/LED_XY/1 activation
+ * input event to the evdev device to see it being sent to this device.
+ *
+ * If uhid is not available as /dev/uhid, then you can pass a different path as
+ * first argument.
+ * If <linux/uhid.h> is not installed in /usr, then compile this with:
+ * gcc -o ./uhid_test -Wall -I./include ./samples/uhid/uhid-example.c
+ * And ignore the warning about kernel headers. However, it is recommended to
+ * use the installed uhid.h if available.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <linux/uhid.h>
+
+/*
+ * HID Report Desciptor
+ * We emulate a basic 3 button mouse with wheel and 3 keyboard LEDs. This is
+ * the report-descriptor as the kernel will parse it:
+ *
+ * INPUT(1)[INPUT]
+ * Field(0)
+ * Physical(GenericDesktop.Pointer)
+ * Application(GenericDesktop.Mouse)
+ * Usage(3)
+ * Button.0001
+ * Button.0002
+ * Button.0003
+ * Logical Minimum(0)
+ * Logical Maximum(1)
+ * Report Size(1)
+ * Report Count(3)
+ * Report Offset(0)
+ * Flags( Variable Absolute )
+ * Field(1)
+ * Physical(GenericDesktop.Pointer)
+ * Application(GenericDesktop.Mouse)
+ * Usage(3)
+ * GenericDesktop.X
+ * GenericDesktop.Y
+ * GenericDesktop.Wheel
+ * Logical Minimum(-128)
+ * Logical Maximum(127)
+ * Report Size(8)
+ * Report Count(3)
+ * Report Offset(8)
+ * Flags( Variable Relative )
+ * OUTPUT(2)[OUTPUT]
+ * Field(0)
+ * Application(GenericDesktop.Keyboard)
+ * Usage(3)
+ * LED.NumLock
+ * LED.CapsLock
+ * LED.ScrollLock
+ * Logical Minimum(0)
+ * Logical Maximum(1)
+ * Report Size(1)
+ * Report Count(3)
+ * Report Offset(0)
+ * Flags( Variable Absolute )
+ *
+ * This is the mapping that we expect:
+ * Button.0001 ---> Key.LeftBtn
+ * Button.0002 ---> Key.RightBtn
+ * Button.0003 ---> Key.MiddleBtn
+ * GenericDesktop.X ---> Relative.X
+ * GenericDesktop.Y ---> Relative.Y
+ * GenericDesktop.Wheel ---> Relative.Wheel
+ * LED.NumLock ---> LED.NumLock
+ * LED.CapsLock ---> LED.CapsLock
+ * LED.ScrollLock ---> LED.ScrollLock
+ *
+ * This information can be verified by reading /sys/kernel/debug/hid/<dev>/rdesc
+ * This file should print the same information as showed above.
+ */
+
+static unsigned char rdesc[] = {
+ 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */
+ 0x09, 0x02, /* USAGE (Mouse) */
+ 0xa1, 0x01, /* COLLECTION (Application) */
+ 0x09, 0x01, /* USAGE (Pointer) */
+ 0xa1, 0x00, /* COLLECTION (Physical) */
+ 0x85, 0x01, /* REPORT_ID (1) */
+ 0x05, 0x09, /* USAGE_PAGE (Button) */
+ 0x19, 0x01, /* USAGE_MINIMUM (Button 1) */
+ 0x29, 0x03, /* USAGE_MAXIMUM (Button 3) */
+ 0x15, 0x00, /* LOGICAL_MINIMUM (0) */
+ 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */
+ 0x95, 0x03, /* REPORT_COUNT (3) */
+ 0x75, 0x01, /* REPORT_SIZE (1) */
+ 0x81, 0x02, /* INPUT (Data,Var,Abs) */
+ 0x95, 0x01, /* REPORT_COUNT (1) */
+ 0x75, 0x05, /* REPORT_SIZE (5) */
+ 0x81, 0x01, /* INPUT (Cnst,Var,Abs) */
+ 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */
+ 0x09, 0x30, /* USAGE (X) */
+ 0x09, 0x31, /* USAGE (Y) */
+ 0x09, 0x38, /* USAGE (WHEEL) */
+ 0x15, 0x81, /* LOGICAL_MINIMUM (-127) */
+ 0x25, 0x7f, /* LOGICAL_MAXIMUM (127) */
+ 0x75, 0x08, /* REPORT_SIZE (8) */
+ 0x95, 0x03, /* REPORT_COUNT (3) */
+ 0x81, 0x06, /* INPUT (Data,Var,Rel) */
+ 0xc0, /* END_COLLECTION */
+ 0xc0, /* END_COLLECTION */
+ 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */
+ 0x09, 0x06, /* USAGE (Keyboard) */
+ 0xa1, 0x01, /* COLLECTION (Application) */
+ 0x85, 0x02, /* REPORT_ID (2) */
+ 0x05, 0x08, /* USAGE_PAGE (Led) */
+ 0x19, 0x01, /* USAGE_MINIMUM (1) */
+ 0x29, 0x03, /* USAGE_MAXIMUM (3) */
+ 0x15, 0x00, /* LOGICAL_MINIMUM (0) */
+ 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */
+ 0x95, 0x03, /* REPORT_COUNT (3) */
+ 0x75, 0x01, /* REPORT_SIZE (1) */
+ 0x91, 0x02, /* Output (Data,Var,Abs) */
+ 0x95, 0x01, /* REPORT_COUNT (1) */
+ 0x75, 0x05, /* REPORT_SIZE (5) */
+ 0x91, 0x01, /* Output (Cnst,Var,Abs) */
+ 0xc0, /* END_COLLECTION */
+};
+
+static int uhid_write(int fd, const struct uhid_event *ev)
+{
+ ssize_t ret;
+
+ ret = write(fd, ev, sizeof(*ev));
+ if (ret < 0) {
+ fprintf(stderr, "Cannot write to uhid: %m\n");
+ return -errno;
+ } else if (ret != sizeof(*ev)) {
+ fprintf(stderr, "Wrong size written to uhid: %zd != %zu\n",
+ ret, sizeof(ev));
+ return -EFAULT;
+ } else {
+ return 0;
+ }
+}
+
+static int create(int fd)
+{
+ struct uhid_event ev;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.type = UHID_CREATE;
+ strcpy((char*)ev.u.create.name, "test-uhid-device");
+ ev.u.create.rd_data = rdesc;
+ ev.u.create.rd_size = sizeof(rdesc);
+ ev.u.create.bus = BUS_USB;
+ ev.u.create.vendor = 0x15d9;
+ ev.u.create.product = 0x0a37;
+ ev.u.create.version = 0;
+ ev.u.create.country = 0;
+
+ return uhid_write(fd, &ev);
+}
+
+static void destroy(int fd)
+{
+ struct uhid_event ev;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.type = UHID_DESTROY;
+
+ uhid_write(fd, &ev);
+}
+
+/* This parses raw output reports sent by the kernel to the device. A normal
+ * uhid program shouldn't do this but instead just forward the raw report.
+ * However, for ducomentational purposes, we try to detect LED events here and
+ * print debug messages for it. */
+static void handle_output(struct uhid_event *ev)
+{
+ /* LED messages are adverised via OUTPUT reports; ignore the rest */
+ if (ev->u.output.rtype != UHID_OUTPUT_REPORT)
+ return;
+ /* LED reports have length 2 bytes */
+ if (ev->u.output.size != 2)
+ return;
+ /* first byte is report-id which is 0x02 for LEDs in our rdesc */
+ if (ev->u.output.data[0] != 0x2)
+ return;
+
+ /* print flags payload */
+ fprintf(stderr, "LED output report received with flags %x\n",
+ ev->u.output.data[1]);
+}
+
+static int event(int fd)
+{
+ struct uhid_event ev;
+ ssize_t ret;
+
+ memset(&ev, 0, sizeof(ev));
+ ret = read(fd, &ev, sizeof(ev));
+ if (ret == 0) {
+ fprintf(stderr, "Read HUP on uhid-cdev\n");
+ return -EFAULT;
+ } else if (ret < 0) {
+ fprintf(stderr, "Cannot read uhid-cdev: %m\n");
+ return -errno;
+ } else if (ret != sizeof(ev)) {
+ fprintf(stderr, "Invalid size read from uhid-dev: %zd != %zu\n",
+ ret, sizeof(ev));
+ return -EFAULT;
+ }
+
+ switch (ev.type) {
+ case UHID_START:
+ fprintf(stderr, "UHID_START from uhid-dev\n");
+ break;
+ case UHID_STOP:
+ fprintf(stderr, "UHID_STOP from uhid-dev\n");
+ break;
+ case UHID_OPEN:
+ fprintf(stderr, "UHID_OPEN from uhid-dev\n");
+ break;
+ case UHID_CLOSE:
+ fprintf(stderr, "UHID_CLOSE from uhid-dev\n");
+ break;
+ case UHID_OUTPUT:
+ fprintf(stderr, "UHID_OUTPUT from uhid-dev\n");
+ handle_output(&ev);
+ break;
+ case UHID_OUTPUT_EV:
+ fprintf(stderr, "UHID_OUTPUT_EV from uhid-dev\n");
+ break;
+ default:
+ fprintf(stderr, "Invalid event from uhid-dev: %u\n", ev.type);
+ }
+
+ return 0;
+}
+
+static bool btn1_down;
+static bool btn2_down;
+static bool btn3_down;
+static signed char abs_hor;
+static signed char abs_ver;
+static signed char wheel;
+
+static int send_event(int fd)
+{
+ struct uhid_event ev;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.type = UHID_INPUT;
+ ev.u.input.size = 5;
+
+ ev.u.input.data[0] = 0x1;
+ if (btn1_down)
+ ev.u.input.data[1] |= 0x1;
+ if (btn2_down)
+ ev.u.input.data[1] |= 0x2;
+ if (btn3_down)
+ ev.u.input.data[1] |= 0x4;
+
+ ev.u.input.data[2] = abs_hor;
+ ev.u.input.data[3] = abs_ver;
+ ev.u.input.data[4] = wheel;
+
+ return uhid_write(fd, &ev);
+}
+
+static int keyboard(int fd)
+{
+ char buf[128];
+ ssize_t ret, i;
+
+ ret = read(STDIN_FILENO, buf, sizeof(buf));
+ if (ret == 0) {
+ fprintf(stderr, "Read HUP on stdin\n");
+ return -EFAULT;
+ } else if (ret < 0) {
+ fprintf(stderr, "Cannot read stdin: %m\n");
+ return -errno;
+ }
+
+ for (i = 0; i < ret; ++i) {
+ switch (buf[i]) {
+ case '1':
+ btn1_down = !btn1_down;
+ ret = send_event(fd);
+ if (ret)
+ return ret;
+ break;
+ case '2':
+ btn2_down = !btn2_down;
+ ret = send_event(fd);
+ if (ret)
+ return ret;
+ break;
+ case '3':
+ btn3_down = !btn3_down;
+ ret = send_event(fd);
+ if (ret)
+ return ret;
+ break;
+ case 'a':
+ abs_hor = -20;
+ ret = send_event(fd);
+ abs_hor = 0;
+ if (ret)
+ return ret;
+ break;
+ case 'd':
+ abs_hor = 20;
+ ret = send_event(fd);
+ abs_hor = 0;
+ if (ret)
+ return ret;
+ break;
+ case 'w':
+ abs_ver = -20;
+ ret = send_event(fd);
+ abs_ver = 0;
+ if (ret)
+ return ret;
+ break;
+ case 's':
+ abs_ver = 20;
+ ret = send_event(fd);
+ abs_ver = 0;
+ if (ret)
+ return ret;
+ break;
+ case 'r':
+ wheel = 1;
+ ret = send_event(fd);
+ wheel = 0;
+ if (ret)
+ return ret;
+ break;
+ case 'f':
+ wheel = -1;
+ ret = send_event(fd);
+ wheel = 0;
+ if (ret)
+ return ret;
+ break;
+ case 'q':
+ return -ECANCELED;
+ default:
+ fprintf(stderr, "Invalid input: %c\n", buf[i]);
+ }
+ }
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ int fd;
+ const char *path = "/dev/uhid";
+ struct pollfd pfds[2];
+ int ret;
+ struct termios state;
+
+ ret = tcgetattr(STDIN_FILENO, &state);
+ if (ret) {
+ fprintf(stderr, "Cannot get tty state\n");
+ } else {
+ state.c_lflag &= ~ICANON;
+ state.c_cc[VMIN] = 1;
+ ret = tcsetattr(STDIN_FILENO, TCSANOW, &state);
+ if (ret)
+ fprintf(stderr, "Cannot set tty state\n");
+ }
+
+ if (argc >= 2) {
+ if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
+ fprintf(stderr, "Usage: %s [%s]\n", argv[0], path);
+ return EXIT_SUCCESS;
+ } else {
+ path = argv[1];
+ }
+ }
+
+ fprintf(stderr, "Open uhid-cdev %s\n", path);
+ fd = open(path, O_RDWR | O_CLOEXEC);
+ if (fd < 0) {
+ fprintf(stderr, "Cannot open uhid-cdev %s: %m\n", path);
+ return EXIT_FAILURE;
+ }
+
+ fprintf(stderr, "Create uhid device\n");
+ ret = create(fd);
+ if (ret) {
+ close(fd);
+ return EXIT_FAILURE;
+ }
+
+ pfds[0].fd = STDIN_FILENO;
+ pfds[0].events = POLLIN;
+ pfds[1].fd = fd;
+ pfds[1].events = POLLIN;
+
+ fprintf(stderr, "Press 'q' to quit...\n");
+ while (1) {
+ ret = poll(pfds, 2, -1);
+ if (ret < 0) {
+ fprintf(stderr, "Cannot poll for fds: %m\n");
+ break;
+ }
+ if (pfds[0].revents & POLLHUP) {
+ fprintf(stderr, "Received HUP on stdin\n");
+ break;
+ }
+ if (pfds[1].revents & POLLHUP) {
+ fprintf(stderr, "Received HUP on uhid-cdev\n");
+ break;
+ }
+
+ if (pfds[0].revents & POLLIN) {
+ ret = keyboard(fd);
+ if (ret)
+ break;
+ }
+ if (pfds[1].revents & POLLIN) {
+ ret = event(fd);
+ if (ret)
+ break;
+ }
+ }
+
+ fprintf(stderr, "Destroy uhid device\n");
+ destroy(fd);
+ return EXIT_SUCCESS;
+}
diff --git a/wheel_hid_report_desc.h b/wheel_hid_report_desc.h
new file mode 100644
index 0000000..738da43
--- /dev/null
+++ b/wheel_hid_report_desc.h
@@ -0,0 +1,853 @@
+// ---------------------------------------------------
+// HID Report Descriptor for Force Feedback Steering Wheel
+// ---------------------------------------------------
+0x05, 0x01, // Usage Page (Generic Desktop)
+0x09, 0x04, // Usage (Joystick)
+0xA1, 0x01, // Collection (Application)
+
+// ---------------------------------------------------
+// INPUT REPORT (ID = 1)
+// ---------------------------------------------------
+0x85, 0x01, // Report ID (1)
+
+ // --- Steering wheel
+0x05, 0x02, // Usage Page (Simulation Controls)
+0x09, 0xC8, // Usage (Steering)
+0x15, 0x81, // Logical Min (-127)
+0x25, 0x7F, // Logical Max (+127)
+0x75, 0x08, // Report Size (8)
+0x95, 0x01, // Report Count (1)
+0x81, 0x02, // Input (Data, Var, Abs)
+
+// --- Pedals + Clutch
+0x05, 0x02, // Usage Page (Simulation Controls)
+// Usage order: Accelerator, Brake, Clutch
+0x09, 0xC4, // Usage (Accelerator)
+0x09, 0xC5, // Usage (Brake)
+0x09, 0xC6, // Usage (Clutch)
+0x15, 0x00, // Logical Min (0)
+0x26, 0xFF, 0x00, // Logical Max (255)
+0x75, 0x08, // Report Size (8)
+0x95, 0x03, // Report Count (3)
+0x81, 0x02, // Input (Data, Var, Abs)
+
+// --- Shifter (radio-button style)
+0x09, 0xC7, // Usage (Shifter)
+0x15, 0xFF, // Logical Min (-1) ← allows reverse
+0x25, 0x07, // Logical Max (+7) ← up to 7 forward gears
+0x35, 0xFF, // Physical Min (-1)
+0x46, 0x07, 0x00, // Physical Max (7)
+0x75, 0x04, // Report Size (4)
+0x95, 0x01, // Report Count (1)
+0x81, 0x42, // Input (Data, Var, Abs, Null)
+
+// --- Padding to align to byte
+0x75, 0x04,
+0x95, 0x01,
+0x81, 0x01,
+
+// --- Shifter buttons (e.g., 8 manual buttons or paddle shifters)
+0x05, 0x09, // Usage Page (Button)
+0x19, 0x01, // Usage Min (Button 1)
+0x29, 0x08, // Usage Max (Button 8)
+0x15, 0x00, // Logical Min (0)
+0x25, 0x01, // Logical Max (1)
+0x75, 0x01, // Size (1)
+0x95, 0x08, // Count (8)
+0x81, 0x02, // Input (Data, Var, Abs)
+
+// // --- Wheel (Steering)
+// 0x05, 0x02, // Usage Page (Simulation Controls)
+// 0x09, 0xC8, // Usage (Steering)
+// 0x15, 0x81, // Logical Minimum (-127)
+// 0x25, 0x7F, // Logical Maximum (+127)
+// 0x75, 0x08, // Report Size (8)
+// 0x95, 0x01, // Report Count (1)
+// 0x81, 0x02, // Input (Data, Variable, Absolute)
+
+// // --- Pedals: Throttle, Brake, Clutch
+// 0x09, 0xBB, // Usage (Throttle)
+// 0x09, 0xC5, // Usage (Brake)
+// 0x09, 0xC4, // Usage (Accelerator)
+// 0x75, 0x08, // Report Size (8)
+// 0x95, 0x03, // Report Count (3)
+// 0x15, 0x00, // Logical Minimum (0)
+// 0x26, 0xFF, 0x00, // Logical Maximum (255)
+// 0x81, 0x02, // Input (Data, Variable, Absolute)
+
+// // --- Hat switch (shifter)
+// 0x05, 0x01, // Usage Page (Generic Desktop)
+// 0x09, 0x39, // Usage (Hat switch)
+// 0x15, 0x00, // Logical Minimum (0)
+// 0x25, 0x07, // Logical Maximum (7)
+// 0x35, 0x00, // Physical Minimum (0)
+// 0x46, 0x3B, 0x01, // Physical Maximum (315)
+// 0x65, 0x14, // Unit (Eng Rot: Degrees)
+// 0x75, 0x04, // Report Size (4)
+// 0x95, 0x01, // Report Count (1)
+// 0x81, 0x42, // Input (Data, Variable, Absolute, Null)
+
+// // --- Padding
+// 0x75, 0x04, // Report Size (4)
+// 0x95, 0x01, // Report Count (1)
+// 0x81, 0x01, // Input (Constant)
+
+// // --- Shifter buttons (e.g., 8 buttons)
+// 0x05, 0x09, // Usage Page (Button)
+// 0x19, 0x01, // Usage Minimum (Button 1)
+// 0x29, 0x08, // Usage Maximum (Button 8)
+// 0x15, 0x00, // Logical Minimum (0)
+// 0x25, 0x01, // Logical Maximum (1)
+// 0x75, 0x01, // Report Size (1)
+// 0x95, 0x08, // Report Count (8)
+// 0x81, 0x02, // Input (Data, Variable, Absolute)
+
+// ---------------------------------------------------
+// Remaining: Feature and Output Reports for PID
+// (same as previously described)
+// ---------------------------------------------------
+
+// (Add PID Feature/Output reports as per earlier example)
+
+// ---------------------------------------------------
+// End Collection
+// ---------------------------------------------------
+0xC0
+
+// // NASKOO
+// // 0x05, 0x01, // Usage Page (Generic)
+// // 0x09, 0x04, // Usage (Joystick)
+// 0x05, 0x02, // Usage Page (Simulation)
+// 0x09, 0x02, // Usage (Automotive)
+// // 0xA1, 0x01, // Collection (Application)
+// 0xA1, 0x01, // Collection (Application)
+// 0x85, 0x01, // Report ID (1)
+// 0x09, 0xC6, // Usage (Z)
+// 0x09, 0xC7, // Usage (Rz)
+// 0x09, 0xC4, // Usage (X)
+// 0x09, 0xC5, // Usage (Y)
+// 0x16, 0x00, 0x80, // Logical Minimum (0)
+// 0x26, 0xFF, 0x7F, // Logical Maximum (255)
+// 0x75, 0x10, // Report Size (8)
+// 0x95, 0x04, // Report Count (4)
+// 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+// // 0xC0, // End Collection
+// 0xC0, // End Collection
+// // END NASKOO
+
+
+// 0x05, 0x01, // USAGE_PAGE (Generic Desktop)
+// 0x09, 0x04, // USAGE (Joystick)
+// 0xA1, 0x01, // COLLECTION (Application)
+// //================================Input Report======================================//
+// 0x09, 0x01, // USAGE (Pointer)
+// // WheelReport
+// 0x85, 0x01, // REPORT_ID (1)
+// 0xA1, 0x00, // COLLECTION (Physical)
+
+// //6 Axis for steering wheel, accelerator, brake, clutch, handbrake and spare
+// 0x05, 0x01, // USAGE_PAGE (Generic Desktop)
+// 0xa1, 0x00, // COLLECTION (Physical)
+// 0x09, 0x30, // USAGE (X)
+// 0x09, 0x31, // USAGE (Y)
+// 0x09, 0x32, // USAGE (Z)
+// 0x09, 0x33, // USAGE (Rx)
+// 0x09, 0x34, // USAGE (Ry)
+// 0x09, 0x35, // USAGE (Rz)
+// 0x09, 0x36, // USAGE (Slider)
+// 0x09, 0x37, // USAGE (Dial)
+// 0x16, 0x00, 0x80, // LOGICAL_MINIMUM (-32768)
+// 0x26, 0xFF, 0x7F, // LOGICAL_MAXIMUM (32767)
+// 0x75, 0x10, // REPORT_SIZE (16)
+// 0x95, 0x08, // REPORT_COUNT (8)
+// 0x81, 0x02, // INPUT (Data,Var,Abs)
+// 0xc0, // END_COLLECTION
+
+// //32 buttons
+// 0x05, 0x09, // USAGE_PAGE (Button)
+// 0x19, 0x01, // Usage Minimum (1),
+// 0x29, 0x20, // Usage Maximum (32)
+// 0x15, 0x00, // LOGICAL_MINIMUM (0)
+// 0x25, 0x01, // Logical Maximum (1),
+// 0x35, 0x00, // PHYSICAL_MINIMUM (0)
+// 0x45, 0x01, // Physical Maximum (1),
+// 0x95, 0x20, // Report Count (32 fields),
+// 0x75, 0x01, // Report Size (1 bit),
+// 0x81, 0x02, // INPUT (Data,Var,Abs)
+
+// #ifdef HATSWITCH
+// 0x05, 0x01, // Usage Page (Desktop),
+// 0x95, 0x01, // Report Count (1 field),
+// 0x75, 0x04, // Report Size (4 bit),
+// 0x25, 0x07, // Logical Maximum (7),
+// 0x46, 0x3B, 0x01, // Physical Maximum (315),
+// //0x65, 0x14, // Unit (Degrees),
+// 0x09, 0x39, // Usage (Hat Switch),
+// 0x81, 0x42, // >>>> Input (Variable)
+
+// //padding 4bits
+// 0x75, 0x04, // REPORT_SIZE (06)
+// 0x95, 0x01, // REPORT_COUNT (01)
+// 0x81, 0x03, // Input (Constant, Variable)
+// #endif
+
+// 0xc0, // END_COLLECTION
+
+
+// // PIDStateReport
+// 0x05, 0x0F, // USAGE_PAGE (Physical Interface)
+// 0x09, 0x92, // USAGE (PID State Report)
+// 0xA1, 0x02, // COLLECTION (Logical)
+// 0x85, 0x02, // REPORT_ID (02)
+// 0x09, 0x9F, // USAGE (Device Paused)
+// 0x09, 0xA0, // USAGE (Actuators Enabled)
+// 0x09, 0xA4, // USAGE (Safety Switch)
+// 0x09, 0xA5, // USAGE (Actuator Override Switch)
+// 0x09, 0xA6, // USAGE (Actuator Power)
+// 0x15, 0x00, // LOGICAL_MINIMUM (00)
+// 0x25, 0x01, // Logical Maximum (1)
+// 0x35, 0x00, // Physical Minimum (0)
+// 0x45, 0x01, // Physical Maximum (1)
+// 0x75, 0x01, // Report Size (1)
+// 0x95, 0x05, // Report Count (5)
+// 0x81, 0x02, // Input (variable,absolute)
+// 0x95, 0x03, // Report Count (3)
+// 0x81, 0x03, // Input (Constant, Variable)
+// 0x09, 0x94, // Usage (Effect Playing)
+// 0x15, 0x00, // Logical Minimum (0)
+// 0x25, 0x01, // Logical Maximum (1)
+// 0x35, 0x00, // Physical Minimum (0)
+// 0x45, 0x01, // Physical Maximum (1)
+// 0x75, 0x01, // Report Size (1)
+// 0x95, 0x01, // Report Count (1)
+// 0x81, 0x02, // Input (variable,absolute)
+// 0x09, 0x22, // Usage (Effect Block Index)
+// 0x15, 0x01, // Logical Minimum (1)
+// 0x25, 0x28, // Logical Maximum (40)
+// 0x35, 0x01, // Physical Minimum (1)
+// 0x45, 0x28, // Physical Maximum (40)
+// 0x75, 0x07, // Report Size (7)
+// 0x95, 0x01, // Report Count (1)
+// 0x81, 0x02, // Input (variable,absolute)
+// 0xC0, //End Collection Datalink (Logical) (OK)
+
+// //================================OutputReport======================================//
+
+// // SetEffectReport
+// 0x09, 0x21, //Usage (Set Effect Report)
+// 0xA1, 0x02, //Collection Datalink (Logical)
+// 0x85, 0x01, //Report ID 1
+// 0x09, 0x22, // Usage (Effect Block Index)
+// 0x15, 0x01, // Logical Minimum (1)
+// 0x25, 0x28, // Logical Maximum (40)
+// 0x35, 0x01, // Physical Minimum (1)
+// 0x45, 0x28, // Physical Maximum (40)
+// 0x75, 0x08, // Report Size (8)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0x09, 0x25, // Usage (Effect Type)
+// 0xA1, 0x02, // Collection Datalink (Logical)
+// 0x09, 0x26, // USAGE (26) Constant
+// 0x09, 0x27, // USAGE (27) Ramp
+// 0x09, 0x30, // USAGE (30) Square
+// 0x09, 0x31, // USAGE (31) Sine
+// 0x09, 0x32, // USAGE (32) Triangle
+// 0x09, 0x33, // USAGE (33) Sawtooth Up
+// 0x09, 0x34, // USAGE (34) Sawtooth Down
+// 0x09, 0x40, // USAGE (40) Spring
+// 0x09, 0x41, // USAGE (41) Damper
+// 0x09, 0x42, // USAGE (42) Inertia
+// 0x09, 0x43, // USAGE (43) Friction
+// // 0x09, 0x28, // Usage (28) (ET Custom Force Data)
+// 0x15, 0x01, // Logical Minimum (1)
+// //0x25, 0x0C, // Logical Maximum (12)
+// 0x25, 0x0B, // Logical Maximum (11)
+// 0x35, 0x01, // Physical Minimum (1)
+// //0x45, 0x0C, // Physical Maximum (12)
+// 0x45, 0x0B, // Physical Maximum (11)
+// 0x75, 0x08, // Report Size (8)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x00, // Output (Data)
+
+// 0xC0, // End Collection Datalink (Logical)
+// 0x09, 0x50, // Usage (Duration)
+// 0x09, 0x54, // Usage (Trigger Repeat Interval)
+// 0x09, 0x51, // Usage (Sample Period)
+// 0x15, 0x00, // Logical Minimum (0)
+// 0x26, 0xFF, 0x7F, // Logical Maximum (32767)
+// 0x35, 0x00, // Physical Minimum (0)
+// 0x46, 0xFF, 0x7F, // Physical Maximum (32767)
+// 0x66, 0x03, 0x10, // Unit (4099)
+// 0x55, 0xFD, // Unit Exponent (253)
+// 0x75, 0x10, // Report Size (16)
+// 0x95, 0x03, // Report Count (3)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0x55, 0x00, // Unit Exponent (0)
+// 0x66, 0x00, 0x00, // Unit (0)
+// 0x09, 0x52, // Usage (Gain)
+// 0x15, 0x00, // Logical Minimum (0)
+// 0x26, 0xFF, 0x00, // Logical Maximum (255)
+// 0x35, 0x00, // Physical Minimum (1)
+// 0x46, 0x10, 0x27, // Physical Maximum (10000)
+// 0x75, 0x08, // Report Size (8)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0x09, 0x53, // Usage (Trigger Button)
+// 0x15, 0x01, // Logical Minimum (1)
+// 0x25, 0x08, // Logical Maximum (8)
+// 0x35, 0x01, // Physical Minimum (1)
+// 0x45, 0x08, // Physical Maximum (8)
+// 0x75, 0x08, // Report Size (8)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0x09, 0x55, // Usage (Axes Enable)
+// 0xA1, 0x02, // Collection Datalink (Logical)
+// 0x05, 0x01, // Usage Page (Generic Desktop)
+// 0x09, 0x30, // Usage (X)
+// 0x09, 0x31, // Usage (Y)
+// 0x15, 0x00, // Logical Minimum (0)
+// 0x25, 0x01, // Logical Maximum (1)
+// 0x75, 0x01, // Report Size (1)
+// 0x95, 0x02, // Report Count (2)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0xC0, // End Collection Datalink (Logical)
+
+// 0x05, 0x0F, // Usage Page (Physical Interface)
+// 0x09, 0x56, // Usage (Direction Enable)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0x95, 0x05, // Report Count (5)
+// 0x91, 0x03, // Output (Constant, Variable)
+// 0x09, 0x57, // Usage (Direction)
+// 0xA1, 0x02, // Collection Datalink (Logical)
+// 0x0B, 0x01, 0, 0x0A, 0, // Usage (Ordinals: Instance 1)
+// 0x0B, 0x02, 0, 0x0A, 0, // Usage (Ordinals: Instance 2)
+// 0x66, 0x14, 0x00, // Unit (20)
+// 0x55, 0xFE, // Unit Exponent (254)
+// 0x15, 0x00, // Logical Minimum (0)
+// 0x26, 0xFF, 0x00, // Logical Maximum (255)
+// 0x35, 0x00, // Physical Minimum (1)
+// 0x47, 0xA0, 0x8C, 0, 0, // Physical Maximum (36000)
+// 0x66, 0x00, 0x00, // Unit (0)
+// 0x75, 0x08, // Report Size (8)
+// 0x95, 0x02, // Report Count (2)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0x55, 0x00, // Unit Exponent (0)
+// 0x66, 0x00, 0x00, // Unit (0)
+// 0xC0, // End Collection Datalink (Logical)
+
+
+// 0x05, 0x0F, // Usage Page (Physical Interface)
+// 0x09, 0x58, // Usage (Type Specific Block Offset)
+// 0xA1, 0x02, // Collection (Logical)
+// 0x0B, 0x01, 0, 0x0A, 0, // Usage (Ordinals: Instance 1)
+// 0x0B, 0x02, 0, 0x0A, 0, // Usage (Ordinals: Instance 2)
+// 0x26, 0xFD, 0x7F, // Logical Maximum (32765); 32K RAM or ROM max.
+// 0x75, 0x10, // Report Size (16)
+// 0x95, 0x02, // Report Count (2)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0xC0, // End Collection (Logical)
+
+// 0xC0, //End Collection Datalink (Logical) (OK)
+
+// // SetEnvelopeReport
+// 0x09, 0x5A, //Usage (Set Envelope Report)
+// 0xA1, 0x02, //Collection Datalink (Logical)
+// 0x85, 0x02, //Report ID 2
+
+// 0x09, 0x22, // Usage (Effect Block Index)
+// 0x15, 0x01, // Logical Minimum (1)
+// 0x25, 0x28, // Logical Maximum (40)
+// 0x35, 0x01, // Physical Minimum (1)
+// 0x45, 0x28, // Physical Maximum (40)
+// 0x75, 0x08, // Report Size (8)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+
+// 0x09, 0x5B, // Usage (Attack Level)
+// 0x09, 0x5D, // Usage (Fade Level)
+// 0x16, 0x00, 0x00, // Logical Minimum (0)
+// 0x26, 0x00, 0x40, // Logical Maximum (16384)
+// 0x36, 0x00, 0x00, // Physical Minimum (0)
+// 0x46, 0x00, 0x40, // Physical Maximum (16384)
+// 0x75, 0x10, // Report Size (16)
+// 0x95, 0x02, // Report Count (2)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+
+// 0x09, 0x5C, // Usage (Attack Time)
+// 0x09, 0x5E, // Usage (Fade Time)
+// 0x66, 0x03, 0x10, // Unit (1003h) English Linear, Seconds
+// 0x55, 0xFD, // Unit Exponent (FDh) (X10^-3 ==> Milisecond)
+// 0x26, 0xFF, 0x7F, // Logical Maximum (32767)
+// 0x46, 0xFF, 0x7F, // Physical Maximum (32767)
+// 0x75, 0x10, // Report Size (16)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0x45, 0x00, // Physical Maximum (0)
+// 0x66, 0x00, 0x00, // Unit (0)
+// 0x55, 0x00, // Unit Exponent (0)
+// 0xC0, //End Collection Datalink (Logical) (OK)
+
+// // SetConditionReport
+// 0x09, 0x5F, //Usage (Set Condition Report)
+// 0xA1, 0x02, //Collection Datalink (Logical)
+// 0x85, 0x03, //Report ID 3
+// 0x09, 0x22, // Usage (Effect Block Index)
+// 0x15, 0x01, // Logical Minimum (1)
+// 0x25, 0x28, // Logical Maximum (40)
+// 0x35, 0x01, // Physical Minimum (1)
+// 0x45, 0x28, // Physical Maximum (40)
+// 0x75, 0x08, // Report Size (8)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+
+// 0x09, 0x23, // Usage (Parameter Block Offset)
+// 0x15, 0x00, // Logical Minimum (0)
+// 0x25, 0x03, // Logical Maximum (3)
+// 0x35, 0x00, // Physical Minimum (0)
+// 0x45, 0x03, // Physical Maximum (3)
+// 0x75, 0x04, // Report Size (4)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+
+// 0x09, 0x58, // Usage (Type Specific Block Off...)
+// 0xA1, 0x02, // Collection Datalink (Logical)
+// 0x0B, 0x01, 0, 0x0A, 0, // Usage (Ordinals: Instance 1)
+// 0x0B, 0x02, 0, 0x0A, 0, // Usage (Ordinals: Instance 2)
+// 0x75, 0x02, // Report Size (2)
+// 0x95, 0x02, // Report Count (2)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+
+// 0xC0, // End Collection Datalink (Logical)
+
+// 0x16, 0x00, 0xC0, // Logical Minimum (-16384)
+// 0x26, 0x00, 0x40, // Logical Maximum (16384)
+// 0x36, 0x00, 0xC0, // Physical Minimum (-16384)
+// 0x46, 0x00, 0x40, // Physical Maximum (16384)
+
+// 0x09, 0x60, // Usage (CP Offset)
+// 0x75, 0x10, // Report Size (16)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+
+// 0x16, 0x00, 0xC0, // Logical Minimum (-16384)
+// 0x26, 0x00, 0x40, // Logical Maximum (16384)
+// 0x36, 0x00, 0xC0, // Physical Minimum (-16384)
+// 0x46, 0x00, 0x40, // Physical Maximum (16384)
+
+// 0x09, 0x61, // Usage (Positive Coefficient)
+// 0x09, 0x62, // Usage (Negative Coefficient)
+// 0x95, 0x02, // Report Count (2)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+
+// 0x16, 0x00, 0x00, // Logical Minimum (0)
+// 0x26, 0x00, 0x40, // Logical Maximum (16384)
+// 0x36, 0x00, 0x00, // Physical Minimum (0)
+// 0x46, 0x00, 0x40, // Physical Maximum (16384)
+// 0x09, 0x63, // Usage (Positive Saturation)
+// 0x09, 0x64, // Usage (Negative Saturation)
+// 0x75, 0x10, // Report Size (16)
+// 0x95, 0x02, // Report Count (2)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+
+// 0x09, 0x65, // Usage (Dead Band)
+// 0x46, 0x00, 0x40, // Physical Maximum (16384)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+
+// 0xC0, //End Collection Datalink (Logical) (OK)
+
+
+// // SetPeriodicReport
+
+// 0x09, 0x6E, //Usage (Set Periodic Report)
+// 0xA1, 0x02, //Collection Datalink (Logical)
+// 0x85, 0x04, //Report ID 4
+// 0x09, 0x22, // Usage (Effect Block Index)
+// 0x15, 0x01, // Logical Minimum (1)
+// 0x25, 0x28, // Logical Maximum (40)
+// 0x35, 0x01, // Physical Minimum (1)
+// 0x45, 0x28, // Physical Maximum (40)
+// 0x75, 0x08, // Report Size (8)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0x09, 0x70, // Usage (Magnitude)
+// 0x16, 0x00, 0x00, // Logical Minimum (0)
+// 0x26, 0x00, 0x40, // Logical Maximum (16384)
+// 0x36, 0x00, 0x00, // Physical Minimum (0)
+// 0x46, 0x00, 0x40, // Physical Maximum (16384)
+// 0x75, 0x10, // Report Size (16)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0x09, 0x6F, // Usage (Offset)
+// 0x16, 0x00, 0xC0, // Logical Minimum (-16384)
+// 0x26, 0x00, 0x40, // Logical Maximum (16384)
+// 0x36, 0x00, 0xC0, // Physical Minimum (-16384)
+// 0x46, 0x00, 0x40, // Physical Maximum (16384)
+
+// 0x95, 0x01, // Report Count (1)
+// 0x75, 0x10, // Report Size (16)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0x09, 0x71, // Usage (Phase)
+// 0x66, 0x14, 0x00, // Unit (14h) (Eng Rotation, Degrees)
+// 0x55, 0xFE, // Unit Exponent (FEh) (X10^-2)
+// 0x15, 0x00, // Logical Minimum (0)
+// 0x27, 0x9F, 0x8C, 0, 0, // Logical Maximum (35999)
+// 0x35, 0x00, // Physical Minimum (0)
+// 0x47, 0x9F, 0x8C, 0, 0, // Physical Maximum (35999)
+// 0x75, 0x10, // Report Size (16)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0x09, 0x72, // Usage (Period)
+// 0x15, 0x00, // Logical Minimum (0)
+// 0x27, 0xFF, 0x7F, 0, 0, // Logical Maximum (32K)
+// 0x35, 0x00, // Physical Minimum (0)
+// 0x47, 0xFF, 0x7F, 0, 0, // Physical Maximum (32K)
+// 0x66, 0x03, 0x10, // Unit (1003h) (English Linear, Seconds)
+// 0x55, 0xFD, // Unit Exponent (FDh) (X10^-3 ==> Milisecond)
+// 0x75, 0x20, // Report Size (32)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0x66, 0x00, 0x00, // Unit (0)
+// 0x55, 0x00, // Unit Exponent (0)
+// 0xC0, //End Collection Datalink (Logical) (OK)
+
+// // SetConstantForceReport
+// 0x09, 0x73, //Usage (Set Constant Force Report)
+// 0xA1, 0x02, //Collection Datalink (Logical)
+// 0x85, 0x05, // Report ID 5
+// 0x09, 0x22, // Usage (Effect Block Index)
+// 0x15, 0x01, // Logical Minimum (1)
+// 0x25, 0x28, // Logical Maximum (40)
+// 0x35, 0x01, // Physical Minimum (1)
+// 0x45, 0x28, // Physical Maximum (40)
+// 0x75, 0x08, // Report Size (8)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0x09, 0x70, // Usage (Magnitude)
+// 0x16, 0x00, 0xC0, // Logical Minimum (-16384)
+// 0x26, 0x00, 0x40, // Logical Maximum (16384)
+// 0x36, 0x00, 0xC0, // Physical Minimum (-16384)
+// 0x46, 0x00, 0x40, // Physical Maximum (16384)
+// 0x75, 0x10, // Report Size (16)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0xC0, //End Collection Datalink (Logical) (OK)
+
+// // SetRampForceReport
+// 0x09, 0x74, //Usage (Set Ramp Force Report)
+// 0xA1, 0x02, //Collection Datalink (Logical)
+// 0x85, 0x06, // Report ID 6
+// 0x09, 0x22, // Usage (Effect Block Index)
+// 0x15, 0x01, // Logical Minimum (1)
+// 0x25, 0x28, // Logical Maximum (40)
+// 0x35, 0x01, // Physical Minimum (1)
+// 0x45, 0x28, // Physical Maximum (40)
+// 0x75, 0x08, // Report Size (8)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0x09, 0x75, // Usage (Ramp Start)
+// 0x09, 0x76, // Usage (Ramp End)
+// 0x16, 0x00, 0xC0, // Logical Minimum (-16384)
+// 0x26, 0x00, 0x40, // Logical Maximum (16384)
+// 0x36, 0x00, 0xC0, // Physical Minimum (-16384)
+// 0x46, 0x00, 0x40, // Physical Maximum (16384)
+// 0x75, 0x10, // Report Size (16)
+// 0x95, 0x02, // Report Count (2)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0xC0, //End Collection Datalink (Logical) (OK)
+
+// /*
+// // CustomForceDataReport
+// 0x09, 0x68, //Usage (Custom Force Data Report)
+// 0xA1, 0x02, //Collection Datalink (Logical)
+// 0x85, 0x07, // Report ID 7
+// 0x09, 0x22, // Usage (Effect Block Index)
+// 0x15, 0x01, // Logical Minimum (1)
+// 0x25, 0x28, // Logical Maximum (40)
+// 0x35, 0x01, // Physical Minimum (1)
+// 0x45, 0x28, // Physical Maximum (40)
+// 0x75, 0x08, // Report Size (8)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0x09, 0x6C, // Usage (Custom Force Data Offset)
+// 0x15, 0x00, // Logical Minimum (0)
+// 0x26, 0x10, 0x27, // Logical Maximum (10000)
+// 0x35, 0x00, // Physical Minimum (0)
+// 0x46, 0x10, 0x27, // Physical Maximum (10000)
+// 0x75, 0x10, // Report Size (16)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0x09, 0x69, // Usage (Custom Force Data)
+// 0x15, 0x81, // Logical Minimum (-127)
+// 0x25, 0x7F, // Logical Maximum (127)
+// 0x35, 0x00, // Physical Minimum (0)
+// 0x46, 0xFF, 0x00, // Physical Maximum (255)
+// 0x75, 0x08, // Report Size (8)
+// 0x95, 0x0C, // Report Count (12)
+// 0x92, 0x02, 0x01, // Output (Variable, Buffered)
+// 0xC0, //End Collection Datalink (Logical) (OK)
+
+// // DownloadForceSample
+
+// 0x09, 0x66, //Usage (Download Force Sample)
+// 0xA1, 0x02, //Collection Datalink (Logical)
+// 0x85, 0x08, //Report ID 8
+// 0x05, 0x01, // Usage Page (Generic Desktop)
+// 0x09, 0x30, // Usage (X)
+// // 0x09, 0x31, // Usage (Y)
+// 0x15, 0x81, // Logical Minimum (-127)
+// 0x25, 0x7F, // Logical Maximum (127)
+// 0x35, 0x00, // Physical Minimum (0)
+// 0x46, 0xFF, 0x00, // Physical Maximum (255)
+// 0x75, 0x08, // Report Size (8)
+// // 0x95, 0x02, // Report Count (2)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0xC0, //End Collection Datalink (Logical) (OK)
+// */
+// // EffectOperationReport
+// 0x05, 0x0F, //Usage Page (Physical Interface)
+// 0x09, 0x77, //Usage (Effect Operation Report)
+// 0xA1, 0x02, //Collection Datalink (Logical)
+// 0x85, 0x0A, //Report ID 10
+// 0x09, 0x22, // Usage (Effect Block Index)
+// 0x15, 0x01, // Logical Minimum (1)
+// 0x25, 0x28, // Logical Maximum (40)
+// 0x35, 0x01, // Physical Minimum (1)
+// 0x45, 0x28, // Physical Maximum (40)
+// 0x75, 0x08, // Report Size (8)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0x09, 0x78, // Usage (Effect Operation)
+// 0xA1, 0x02, // Collection Datalink (Logical)
+// 0x09, 0x79, // Usage (Op Effect Start)
+// 0x09, 0x7A, // Usage (Op Effect Start Solo)
+// 0x09, 0x7B, // Usage (Op Effect Stop)
+// 0x15, 0x01, // Logical Minimum (1)
+// 0x25, 0x03, // Logical Maximum (3)
+// 0x75, 0x08, // Report Size (8)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x00, // Output (Data)
+// 0xC0, // End Collection Datalink (Logical)
+// 0x09, 0x7C, // Usage (Loop Count)
+// 0x15, 0x00, // Logical Minimum (0)
+// 0x26, 0xFF, 0x00, // Logical Maximum (255)
+// 0x35, 0x00, // Physical Minimum (0)
+// 0x46, 0xFF, 0x00, // Physical Maximum (255)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0xC0, //End Collection Datalink (Logical) (OK)
+
+// // PIDBlockFreeReport
+// 0x09, 0x90, //Usage (PID Block Free Report)
+// 0xA1, 0x02, //Collection Datalink (Logical)
+// 0x85, 0x0B, // Report ID 11
+// 0x09, 0x22, // Usage (Effect Block Index)
+// 0x15, 0x01, // Logical Minimum (1)
+// 0x25, 0x28, // Logical Maximum (40)
+// 0x35, 0x01, // Physical Minimum (1)
+// 0x45, 0x28, // Physical Maximum (40)
+// 0x75, 0x08, // Report Size (8)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0xC0, //End Collection Datalink (Logical) (OK)
+// //PIDDeviceControl
+// 0x09, 0x96, //Usage (PID Device Control)
+// 0xA1, 0x02, //Collection Datalink (Logical)
+// 0x85, 0x0C, // Report ID 12
+// 0x09, 0x97, // Usage (DC Enable Actuators)
+// 0x09, 0x98, // Usage (DC Disable Actuators)
+// 0x09, 0x99, // Usage (DC Stop All Effects)
+// 0x09, 0x9A, // Usage (DC Device Reset)
+// 0x09, 0x9B, // Usage (DC Device Pause)
+// 0x09, 0x9C, // Usage (DC Device Continue)
+// 0x15, 0x01, // Logical Minimum (1)
+// 0x25, 0x06, // Logical Maximum (6)
+// 0x75, 0x08, // Report Size (8)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x00, // Output (Data)
+// 0xC0, //End Collection Datalink (Logical) (OK)
+
+// // DeviceGainReport
+// 0x09, 0x7D, //Usage (Device Gain Report)
+// 0xA1, 0x02, //Collection Datalink (Logical)
+// 0x85, 0x0D, //Report ID 13
+// 0x09, 0x7E, // Usage (Device Gain)
+// 0x15, 0x00, // Logical Minimum (0)
+// 0x26, 0xFF, 0x00, // Logical Maximum (255)
+// 0x35, 0x00, // Physical Minimum (0)
+// 0x46, 0x10, 0x27, // Physical Maximum (10000)
+// 0x75, 0x08, // Report Size (8)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0xC0, //End Collection Datalink (Logical) (OK)
+// /*
+// //SetCustomForceReport
+// 0x09, 0x6B, //Usage (Set Custom Force Report)
+// 0xA1, 0x02, //Collection Datalink (Logical)
+// 0x85, 0x0E, // Report ID 14
+// 0x09, 0x22, // Usage (Effect Block Index)
+// 0x15, 0x01, // Logical Minimum (1)
+// 0x25, 0x28, // Logical Maximum (40)
+// 0x35, 0x01, // Physical Minimum (1)
+// 0x45, 0x28, // Physical Maximum (40)
+// 0x75, 0x08, // Report Size (8)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0x09, 0x6D, // Usage (Sample Count)
+// 0x15, 0x00, // Logical Minimum (0)
+// 0x26, 0xFF, 0x00, // Logical Maximum (255)
+// 0x35, 0x00, // Physical Minimum (0)
+// 0x46, 0xFF, 0x00, // Physical Maximum (255)
+// 0x75, 0x08, // Report Size (8)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0x09, 0x51, // Usage (Sample Period)
+// 0x66, 0x03, 0x10, // Unit 4099
+// 0x55, 0xFD, // Unit (Exponent 253)
+// 0x15, 0x00, // Logical Minimum (0)
+// 0x26, 0xFF, 0x7F, // Logical Maximum (32767)
+// 0x35, 0x00, // Physical Minimum (0)
+// 0x46, 0xFF, 0x7F, // Physical Maximum (32767)
+// 0x75, 0x10, // Report Size (16)
+// 0x95, 0x01, // Report Count (1)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// 0x55, 0x00, // Unit (Exponent 0)
+// 0x66, 0x00, 0x00, // Unit 0
+// 0xC0, //End Collection Datalink (Logical) (OK)
+// //=========================================FeatureReport======================================//
+// */
+// //CreateNewEffectReport
+// 0x09, 0xAB, // USAGE (Create New Effect Report)
+// 0xA1, 0x02, // COLLECTION (Logical)
+// 0x85, 0x05, // REPORT_ID (05)
+// 0x09, 0x25, // USAGE (Effect Type)
+// 0xA1, 0x02, // COLLECTION (Logical)
+// 0x09, 0x26, // USAGE (26)
+// 0x09, 0x27, // USAGE (27)
+// 0x09, 0x30, // USAGE (30)
+// 0x09, 0x31, // USAGE (31)
+// 0x09, 0x32, // USAGE (32)
+// 0x09, 0x33, // USAGE (33)
+// 0x09, 0x34, // USAGE (34)
+// 0x09, 0x40, // USAGE (40)
+// 0x09, 0x41, // USAGE (41)
+// 0x09, 0x42, // USAGE (42)
+// 0x09, 0x43, // USAGE (43)
+// //0x09, 0x28, // USAGE (28)
+// //0x25, 0x0C, // LOGICAL_MAXIMUM (0C)
+// 0x25, 0x0B, // LOGICAL_MAXIMUM (0B)
+// 0x15, 0x01, // LOGICAL_MINIMUM (01)
+// 0x35, 0x01, // PHYSICAL_MINIMUM (01)
+// //0x45, 0x0C, // PHYSICAL_MAXIMUM (0C)
+// 0x45, 0x0B, // PHYSICAL_MAXIMUM (0B)
+// 0x75, 0x08, // REPORT_SIZE (08)
+// 0x95, 0x01, // REPORT_COUNT (01)
+// 0xB1, 0x00, // FEATURE (Data)
+// 0xC0, // END COLLECTION ()
+// 0x05, 0x01, // USAGE_PAGE (Generic Desktop)
+// 0x09, 0x3B, // USAGE (Byte Count)
+// 0x15, 0x00, // LOGICAL_MINIMUM (00)
+// 0x26, 0xFF, 0x01, // LOGICAL_MAXIMUM (511)
+// 0x35, 0x00, // PHYSICAL_MINIMUM (00)
+// 0x46, 0xFF, 0x01, // PHYSICAL_MAXIMUM (511)
+// 0x75, 0x0A, // REPORT_SIZE (0A)
+// 0x95, 0x01, // REPORT_COUNT (01)
+// 0xB1, 0x02, // FEATURE (Data,Var,Abs)
+// 0x75, 0x06, // REPORT_SIZE (06)
+// 0xB1, 0x01, // FEATURE (Constant,Ary,Abs)
+// 0xC0, // END COLLECTION ()
+
+// // PIDBlockLoadReport
+// 0x05, 0x0F, // USAGE_PAGE (Physical Interface)
+// 0x09, 0x89, // USAGE (PID Block Load Report)
+// 0xA1, 0x02, // COLLECTION (Logical)
+// 0x85, 0x06, // REPORT_ID (06)
+// 0x09, 0x22, // USAGE (Effect Block Index)
+// 0x25, 0x28, // LOGICAL_MAXIMUM (28)
+// 0x15, 0x01, // LOGICAL_MINIMUM (01)
+// 0x35, 0x01, // PHYSICAL_MINIMUM (01)
+// 0x45, 0x28, // PHYSICAL_MAXIMUM (28)
+// 0x75, 0x08, // REPORT_SIZE (08)
+// 0x95, 0x01, // REPORT_COUNT (01)
+// 0xB1, 0x02, // FEATURE (Data,Var,Abs)
+// 0x09, 0x8B, // USAGE (Block Load Status)
+// 0xA1, 0x02, // COLLECTION (Logical)
+// 0x09, 0x8C, // USAGE (Block Load Success)
+// 0x09, 0x8D, // USAGE (Block Load Full)
+// 0x09, 0x8E, // USAGE (Block Load Error)
+// 0x25, 0x03, // LOGICAL_MAXIMUM (03)
+// 0x15, 0x01, // LOGICAL_MINIMUM (01)
+// 0x35, 0x01, // PHYSICAL_MINIMUM (01)
+// 0x45, 0x03, // PHYSICAL_MAXIMUM (03)
+// 0x75, 0x08, // REPORT_SIZE (08)
+// 0x95, 0x01, // REPORT_COUNT (01)
+// 0xB1, 0x00, // FEATURE (Data)
+// 0xC0, // END COLLECTION ()
+// 0x09, 0xAC, // USAGE (RAM Pool Available)
+// 0x15, 0x00, // LOGICAL_MINIMUM (00)
+// 0x27, 0xFF, 0xFF, 0x00, 0x00, // LOGICAL_MAXIMUM (00 00 FF FF)
+// 0x35, 0x00, // PHYSICAL_MINIMUM (00)
+// 0x47, 0xFF, 0xFF, 0x00, 0x00, // PHYSICAL_MAXIMUM (00 00 FF FF)
+// 0x75, 0x10, // REPORT_SIZE (10)
+// 0x95, 0x01, // REPORT_COUNT (01)
+// 0xB1, 0x00, // FEATURE (Data)
+// 0xC0, // END COLLECTION ()
+
+// // PIDPoolReport
+// 0x09, 0x7F, // USAGE (PID Pool Report)
+// 0xA1, 0x02, // COLLECTION (Logical)
+// 0x85, 0x07, // REPORT_ID (07)
+// 0x09, 0x80, // USAGE (RAM Pool Size)
+// 0x75, 0x10, // REPORT_SIZE (10)
+// 0x95, 0x01, // REPORT_COUNT (01)
+// 0x15, 0x00, // LOGICAL_MINIMUM (00)
+// 0x35, 0x00, // PHYSICAL_MINIMUM (00)
+// 0x27, 0xFF, 0xFF, 0x00, 0x00, // LOGICAL_MAXIMUM (00 00 FF FF)
+// 0x47, 0xFF, 0xFF, 0x00, 0x00, // PHYSICAL_MAXIMUM (00 00 FF FF)
+// 0xB1, 0x02, // FEATURE (Data,Var,Abs)
+// 0x09, 0x83, // USAGE (Simultaneous Effects Max)
+// 0x26, 0xFF, 0x00, // LOGICAL_MAXIMUM (00 FF)
+// 0x46, 0xFF, 0x00, // PHYSICAL_MAXIMUM (00 FF)
+// 0x75, 0x08, // REPORT_SIZE (08)
+// 0x95, 0x01, // REPORT_COUNT (01)
+// 0xB1, 0x02, // FEATURE (Data,Var,Abs)
+// 0x09, 0xA9, // USAGE (Device Managed Pool)
+// 0x09, 0xAA, // USAGE (Shared Parameter Blocks)
+// 0x75, 0x01, // REPORT_SIZE (01)
+// 0x95, 0x02, // REPORT_COUNT (02)
+// 0x15, 0x00, // LOGICAL_MINIMUM (00)
+// 0x25, 0x01, // LOGICAL_MAXIMUM (01)
+// 0x35, 0x00, // PHYSICAL_MINIMUM (00)
+// 0x45, 0x01, // PHYSICAL_MAXIMUM (01)
+// 0xB1, 0x02, // FEATURE (Data,Var,Abs)
+
+// 0x75, 0x06, // REPORT_SIZE (06)
+// 0x95, 0x01, // REPORT_COUNT (01)
+// 0xB1, 0x03, // FEATURE ( Cnst,Var,Abs)
+// 0xC0, // END COLLECTION ()
+
+
+// 0x85, 0x0F, // REPORT_ID (15)
+// 0x09, 0x03, // USAGE (Vendor Usage 3)
+// 0x15, 0x00, // LOGICAL_MINIMUM (0)
+// 0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
+// 0x75, 0x08, // REPORT_SIZE (8)
+// 0x95, 0x01, // REPORT_COUNT (1)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// //0xb1, 0x82, // FEATURE (Data,Var,Abs,Vol) //command
+// 0x09, 0x03, // USAGE (Vendor Usage 3)
+// 0x15, 0x00, // LOGICAL_MINIMUM (0)
+// 0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
+// 0x75, 0x10, // REPORT_SIZE (16)
+// 0x95, 0x03, // REPORT_COUNT (3)
+// 0x91, 0x02, // Output (Data,Var,Abs)
+// //0xb1, 0x82, // FEATURE (Data,Var,Abs,Vol) //3 args
+
+// 0x85, 0x10, // REPORT_ID (16)
+// 0x09, 0x04, // USAGE (Vendor Usage 4)
+// 0x75, 0x08, // REPORT_SIZE (8)
+// 0x95, 0x1f, // REPORT_COUNT (31)
+// //0x81, 0x82, // INPUT (Data,Var,Abs,Vol)
+// 0x81, 0x02, // INPUT (Data,Var,Abs)
+
+// 0xC0, // END COLLECTION ()
diff --git a/wheel_read.c b/wheel_read.c
new file mode 100644
index 0000000..ae6b4d9
--- /dev/null
+++ b/wheel_read.c
@@ -0,0 +1,52 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <linux/input.h>
+
+void usage(const char *progname) {
+ fprintf(stderr, "Usage: %s /dev/input/eventX\n", progname);
+ exit(EXIT_FAILURE);
+}
+
+int main(int argc, char *argv[]) {
+ int fd;
+ struct input_event ev;
+ ssize_t n;
+
+ if (argc != 2) {
+ usage(argv[0]);
+ }
+
+ // Open the evdev device provided in the command line argument
+ fd = open(argv[1], O_RDONLY);
+ if (fd < 0) {
+ perror("Error opening device");
+ exit(EXIT_FAILURE);
+ }
+
+ printf("Listening for events on %s. Press Ctrl+C to exit.\n", argv[1]);
+
+ // Event reading loop
+ while (1) {
+ n = read(fd, &ev, sizeof(ev));
+ if (n == (ssize_t)-1) {
+ if (errno == EINTR) {printf("EINTR\n");
+ continue;}
+ perror("Error reading event");
+ break;
+ } else if (n != sizeof(ev)) {
+ fprintf(stderr, "Short read: %zd\n", n);
+ break;
+ }
+
+ // Print the event information
+ printf("Event: time %ld.%06ld, type %d, code %d, value %d\n",
+ ev.time.tv_sec, ev.time.tv_usec, ev.type, ev.code, ev.value);
+ }
+
+ close(fd);
+ return 0;
+}
diff --git a/wheel_uhid.c b/wheel_uhid.c
new file mode 100644
index 0000000..563125b
--- /dev/null
+++ b/wheel_uhid.c
@@ -0,0 +1,264 @@
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <linux/uhid.h>
+#include <stdint.h>
+
+static unsigned char rdesc[] = {
+ #include "wheel_hid_report_desc.h"
+};
+
+static int uhid_write(int fd, const struct uhid_event *ev)
+{
+ ssize_t ret;
+
+ ret = write(fd, ev, sizeof(*ev));
+ if (ret < 0) {
+ fprintf(stderr, "Cannot write to uhid: %m\n");
+ return -errno;
+ } else if (ret != sizeof(*ev)) {
+ fprintf(stderr, "Wrong size written to uhid: %zd != %zu\n",
+ ret, sizeof(ev));
+ return -EFAULT;
+ } else {
+ return 0;
+ }
+}
+
+static int create(int fd)
+{
+ struct uhid_event ev;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.type = UHID_CREATE;
+ strcpy((char*)ev.u.create.name, "test-uhid-device");
+ ev.u.create.rd_data = rdesc;
+ ev.u.create.rd_size = sizeof(rdesc);
+ ev.u.create.bus = BUS_USB;
+ ev.u.create.vendor = 0x15d9;
+ ev.u.create.product = 0x0a37;
+ ev.u.create.version = 0;
+ ev.u.create.country = 0;
+
+ return uhid_write(fd, &ev);
+}
+
+static void destroy(int fd)
+{
+ struct uhid_event ev;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.type = UHID_DESTROY;
+
+ uhid_write(fd, &ev);
+}
+
+/* This parses raw output reports sent by the kernel to the device. A normal
+ * uhid program shouldn't do this but instead just forward the raw report.
+ * However, for ducomentational purposes, we try to detect LED events here and
+ * print debug messages for it. */
+static void handle_output(struct uhid_event *ev)
+{
+ /* LED messages are adverised via OUTPUT reports; ignore the rest */
+ if (ev->u.output.rtype != UHID_OUTPUT_REPORT)
+ return;
+ /* LED reports have length 2 bytes */
+ if (ev->u.output.size != 2)
+ return;
+ /* first byte is report-id which is 0x02 for LEDs in our rdesc */
+ if (ev->u.output.data[0] != 0x2)
+ return;
+
+ /* print flags payload */
+ fprintf(stderr, "LED output report received with flags %x\n",
+ ev->u.output.data[1]);
+}
+
+static int event(int fd)
+{
+ struct uhid_event ev;
+ ssize_t ret;
+
+ memset(&ev, 0, sizeof(ev));
+ ret = read(fd, &ev, sizeof(ev));
+ if (ret == 0) {
+ fprintf(stderr, "Read HUP on uhid-cdev\n");
+ return -EFAULT;
+ } else if (ret < 0) {
+ fprintf(stderr, "Cannot read uhid-cdev: %m\n");
+ return -errno;
+ } else if (ret != sizeof(ev)) {
+ fprintf(stderr, "Invalid size read from uhid-dev: %zd != %zu\n",
+ ret, sizeof(ev));
+ return -EFAULT;
+ }
+
+ switch (ev.type) {
+ case UHID_START:
+ fprintf(stderr, "UHID_START from uhid-dev\n");
+ break;
+ case UHID_STOP:
+ fprintf(stderr, "UHID_STOP from uhid-dev\n");
+ break;
+ case UHID_OPEN:
+ fprintf(stderr, "UHID_OPEN from uhid-dev\n");
+ break;
+ case UHID_CLOSE:
+ fprintf(stderr, "UHID_CLOSE from uhid-dev\n");
+ break;
+ case UHID_OUTPUT:
+ fprintf(stderr, "UHID_OUTPUT from uhid-dev\n");
+ handle_output(&ev);
+ break;
+ case UHID_OUTPUT_EV:
+ fprintf(stderr, "UHID_OUTPUT_EV from uhid-dev\n");
+ break;
+ default:
+ fprintf(stderr, "Invalid event from uhid-dev: %u\n", ev.type);
+ }
+
+ return 0;
+}
+
+struct my_report {
+ uint8_t reportid;
+ int8_t a[4];
+ uint16_t rest;
+} __attribute__((packed)) r = {1};
+
+static int send_event(int fd)
+{
+ struct uhid_event ev;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.type = UHID_INPUT;
+ ev.u.input.size = sizeof(r);
+ memcpy(ev.u.input.data, &r, sizeof(r));
+
+ printf("\n");
+ for(size_t i = 0; i < sizeof(r); i++) {
+ printf("0x%x ", ev.u.input.data[i]);
+ }
+ printf("\n");
+
+ printf("Sending event\n");
+ return uhid_write(fd, &ev);
+}
+
+static int keyboard(int fd)
+{
+ static int axisidx = 0;
+ char buf[128];
+
+ ssize_t ret = read(STDIN_FILENO, buf, sizeof(buf));
+ if (ret == 0) {
+ fprintf(stderr, "Read HUP on stdin\n");
+ return -EFAULT;
+ } else if (ret < 0) {
+ fprintf(stderr, "Cannot read stdin: %m\n");
+ return -errno;
+ }
+
+ for(size_t i = 0; i < ret; ++i) {
+ if(buf[i] >= '1' && buf[i] <= '8') {
+ axisidx = buf[i] - '0' - 1;
+ continue;
+ }
+
+ switch(buf[i]) {
+ case 'u': r.a[axisidx] += 2; send_event(fd); break;
+ case 'd': r.a[axisidx] -= 2; send_event(fd); break;
+ case 'q': return -ECANCELED;
+ default:
+ fprintf(stderr, "Unknown command %c\n", buf[i]);
+ }
+ }
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ int fd;
+ const char *path = "/dev/uhid";
+ struct pollfd pfds[2];
+ int ret;
+ struct termios state;
+
+ ret = tcgetattr(STDIN_FILENO, &state);
+ if (ret) {
+ fprintf(stderr, "Cannot get tty state\n");
+ } else {
+ state.c_lflag &= ~ICANON;
+ state.c_cc[VMIN] = 1;
+ ret = tcsetattr(STDIN_FILENO, TCSANOW, &state);
+ if (ret)
+ fprintf(stderr, "Cannot set tty state\n");
+ }
+
+ if (argc >= 2) {
+ if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
+ fprintf(stderr, "Usage: %s [%s]\n", argv[0], path);
+ return EXIT_SUCCESS;
+ } else {
+ path = argv[1];
+ }
+ }
+
+ fprintf(stderr, "Open uhid-cdev %s\n", path);
+ fd = open(path, O_RDWR | O_CLOEXEC);
+ if (fd < 0) {
+ fprintf(stderr, "Cannot open uhid-cdev %s: %m\n", path);
+ return EXIT_FAILURE;
+ }
+
+ fprintf(stderr, "Create uhid device\n");
+ ret = create(fd);
+ if (ret) {
+ close(fd);
+ return EXIT_FAILURE;
+ }
+
+ pfds[0].fd = STDIN_FILENO;
+ pfds[0].events = POLLIN;
+ pfds[1].fd = fd;
+ pfds[1].events = POLLIN;
+
+ fprintf(stderr, "Press 'q' to quit...\n");
+ while (1) {
+ ret = poll(pfds, 2, -1);
+ if (ret < 0) {
+ fprintf(stderr, "Cannot poll for fds: %m\n");
+ break;
+ }
+ if (pfds[0].revents & POLLHUP) {
+ fprintf(stderr, "Received HUP on stdin\n");
+ break;
+ }
+ if (pfds[1].revents & POLLHUP) {
+ fprintf(stderr, "Received HUP on uhid-cdev\n");
+ break;
+ }
+
+ if (pfds[0].revents & POLLIN) {
+ ret = keyboard(fd);
+ if (ret)
+ break;
+ }
+ if (pfds[1].revents & POLLIN) {
+ ret = event(fd);
+ if (ret)
+ break;
+ }
+ }
+
+ fprintf(stderr, "Destroy uhid device\n");
+ destroy(fd);
+ return EXIT_SUCCESS;
+}