diff options
author | kartofen <mladenovnasko0@gmail.com> | 2025-06-08 23:16:33 +0300 |
---|---|---|
committer | kartofen <mladenovnasko0@gmail.com> | 2025-06-08 23:16:33 +0300 |
commit | 227f73622b4576bf21719fcc9ea99416056d8a71 (patch) | |
tree | 1ab6d7c0e8dab40aa26def1a2ea6098134e82ea8 |
-rwxr-xr-x | build.sh | 6 | ||||
-rw-r--r-- | main.c | 386 | ||||
-rw-r--r-- | plot.gnu | 19 | ||||
-rw-r--r-- | plot2.gnu | 19 | ||||
-rw-r--r-- | transpose.h | 54 | ||||
-rw-r--r-- | uhid_example.c | 465 | ||||
-rw-r--r-- | wheel_hid_report_desc.h | 853 | ||||
-rw-r--r-- | wheel_read.c | 52 | ||||
-rw-r--r-- | wheel_uhid.c | 264 |
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 @@ -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; +} |