2929
3030#ifdef SDL_JOYSTICK_HIDAPI_ZUIKI
3131
32+ #define GYRO_SCALE (1024.0f / 32768.0f * SDL_PI_F / 180.0f) // Calculate scaling factor based on gyroscope data range and radians
33+ #define ACCEL_SCALE (8.0f / 32768.0f * SDL_STANDARD_GRAVITY) // Calculate acceleration scaling factor based on gyroscope data range and standard gravity
34+ #define FILTER_SIZE 11 // Must be an odd number
35+ #define MAX_RETRY_COUNT 10 // zuiki device initialization retry count
36+
3237// Define this if you want to log all packets from the controller
3338#if 0
3439#define DEBUG_ZUIKI_PROTOCOL
3540#endif
3641
42+ typedef struct {
43+ float buffer [FILTER_SIZE ];
44+ uint8_t index ;
45+ uint8_t count ;
46+ } MedianFilter_t ;
47+
3748typedef struct
3849{
3950 Uint8 last_state [USB_PACKET_LENGTH ];
51+ bool sensors_supported ; // Sensor enabled status flag
52+ Uint64 sensor_timestamp_ns ; // Sensor timestamp (nanoseconds, cumulative update)
53+ float sensor_rate ;
54+ MedianFilter_t filter_gyro_x ;
55+ MedianFilter_t filter_gyro_y ;
56+ MedianFilter_t filter_gyro_z ;
4057} SDL_DriverZUIKI_Context ;
4158
59+ static float median_filter_update (MedianFilter_t * mf , float input ) {
60+ mf -> buffer [mf -> index ] = input ;
61+ mf -> index = (mf -> index + 1 ) % FILTER_SIZE ;
62+ if (mf -> count < FILTER_SIZE ) mf -> count ++ ;
63+ float temp [FILTER_SIZE ];
64+ SDL_memcpy (temp , mf -> buffer , sizeof (temp ));
65+ for (int i = 0 ; i < mf -> count - 1 ; i ++ ) {
66+ for (int j = i + 1 ; j < mf -> count ; j ++ ) {
67+ if (temp [i ] > temp [j ]) {
68+ float t = temp [i ];
69+ temp [i ] = temp [j ];
70+ temp [j ] = t ;
71+ }
72+ }
73+ }
74+ return temp [mf -> count / 2 ];
75+ }
76+
77+
4278static void HIDAPI_DriverZUIKI_RegisterHints (SDL_HintCallback callback , void * userdata )
4379{
4480 SDL_AddHintCallback (SDL_HINT_JOYSTICK_HIDAPI_ZUIKI , callback , userdata );
@@ -59,6 +95,9 @@ static bool HIDAPI_DriverZUIKI_IsSupportedDevice(SDL_HIDAPI_Device *device, cons
5995 if (vendor_id == USB_VENDOR_ZUIKI ) {
6096 switch (product_id ) {
6197 case USB_PRODUCT_ZUIKI_MASCON_PRO :
98+ case USB_PRODUCT_ZUIKI_EVOTOP_UWB_DINPUT :
99+ case USB_PRODUCT_ZUIKI_EVOTOP_PC_DINPUT :
100+ case USB_PRODUCT_ZUIKI_EVOTOP_PC_BT :
62101 return true;
63102 default :
64103 break ;
@@ -69,14 +108,49 @@ static bool HIDAPI_DriverZUIKI_IsSupportedDevice(SDL_HIDAPI_Device *device, cons
69108
70109static bool HIDAPI_DriverZUIKI_InitDevice (SDL_HIDAPI_Device * device )
71110{
111+ Uint8 data [USB_PACKET_LENGTH * 2 ];
72112 SDL_DriverZUIKI_Context * ctx = (SDL_DriverZUIKI_Context * )SDL_calloc (1 , sizeof (* ctx ));
73113 if (!ctx ) {
74114 return false;
75115 }
76116 device -> context = ctx ;
117+ ctx -> sensors_supported = false;
118+
119+ // Read report data once for device initialization
120+ int size = -1 ;
121+ Uint8 retry_count = 0 ;
122+ while (retry_count < MAX_RETRY_COUNT ) {
123+ size = SDL_hid_read_timeout (device -> dev , data , sizeof (data ), 10 );
124+ if (size > 0 ) {
125+ break ;
126+ }
127+ retry_count ++ ;
128+ }
129+ if (size <= 0 ) {
130+ return false;
131+ }
77132
78- if (device -> product_id == USB_PRODUCT_ZUIKI_MASCON_PRO ) {
79- HIDAPI_SetDeviceName (device , "ZUIKI MASCON PRO" );
133+ switch (device -> product_id ) {
134+ case USB_PRODUCT_ZUIKI_MASCON_PRO :
135+ HIDAPI_SetDeviceName (device , "ZUIKI MASCON PRO" );
136+ break ;
137+ case USB_PRODUCT_ZUIKI_EVOTOP_PC_DINPUT :
138+ ctx -> sensors_supported = true;
139+ ctx -> sensor_rate = 200.0f ;
140+ break ;
141+ case USB_PRODUCT_ZUIKI_EVOTOP_UWB_DINPUT :
142+ ctx -> sensors_supported = true;
143+ ctx -> sensor_rate = 100.0f ;
144+ break ;
145+ case USB_PRODUCT_ZUIKI_EVOTOP_PC_BT :
146+ if (size > 0 && data [16 ] != 0 ) {
147+ ctx -> sensors_supported = true;
148+ ctx -> sensor_rate = 50.0f ;
149+ }
150+ HIDAPI_SetDeviceName (device , "ZUIKI EVOTOP" );
151+ break ;
152+ default :
153+ break ;
80154 }
81155
82156 return HIDAPI_JoystickConnected (device , NULL );
@@ -106,6 +180,10 @@ static bool HIDAPI_DriverZUIKI_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joyst
106180 joystick -> nbuttons = 11 ;
107181 joystick -> naxes = SDL_GAMEPAD_AXIS_COUNT ;
108182 joystick -> nhats = 1 ;
183+ if (ctx -> sensors_supported ) {
184+ SDL_PrivateJoystickAddSensor (joystick , SDL_SENSOR_GYRO , ctx -> sensor_rate );
185+ SDL_PrivateJoystickAddSensor (joystick , SDL_SENSOR_ACCEL , ctx -> sensor_rate );
186+ }
109187
110188 return true;
111189}
@@ -148,6 +226,10 @@ static bool HIDAPI_DriverZUIKI_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL
148226
149227static bool HIDAPI_DriverZUIKI_SetJoystickSensorsEnabled (SDL_HIDAPI_Device * device , SDL_Joystick * joystick , bool enabled )
150228{
229+ SDL_DriverZUIKI_Context * ctx = (SDL_DriverZUIKI_Context * )device -> context ;
230+ if (ctx -> sensors_supported ) {
231+ return true;
232+ }
151233 return SDL_Unsupported ();
152234}
153235
@@ -226,6 +308,123 @@ static void HIDAPI_DriverZUIKI_HandleOldStatePacket(SDL_Joystick *joystick, SDL_
226308 }
227309#undef READ_STICK_AXIS
228310
311+ if (ctx -> sensors_supported ) {
312+ Uint64 sensor_timestamp = timestamp ;
313+ float gyro_values [3 ];
314+ gyro_values [0 ] = median_filter_update (& ctx -> filter_gyro_x , LOAD16 (data [8 ], data [9 ]) * GYRO_SCALE );
315+ gyro_values [1 ] = median_filter_update (& ctx -> filter_gyro_y , LOAD16 (data [12 ], data [13 ]) * GYRO_SCALE );
316+ gyro_values [2 ] = median_filter_update (& ctx -> filter_gyro_z , - LOAD16 (data [10 ], data [11 ]) * GYRO_SCALE );
317+ float accel_values [3 ];
318+ accel_values [0 ] = LOAD16 (data [14 ], data [15 ]) * ACCEL_SCALE ;
319+ accel_values [2 ] = - LOAD16 (data [16 ], data [17 ]) * ACCEL_SCALE ;
320+ accel_values [1 ] = LOAD16 (data [18 ], data [19 ]) * ACCEL_SCALE ;
321+ #ifdef DEBUG_ZUIKI_PROTOCOL
322+ SDL_Log ("Gyro raw: %d, %d, %d -> scaled: %.2f, %.2f, %.2f rad/s" ,
323+ LOAD16 (data [8 ], data [9 ]), LOAD16 (data [10 ], data [11 ]), LOAD16 (data [12 ], data [13 ]),
324+ gyro_values [0 ], gyro_values [1 ], gyro_values [2 ]);
325+ SDL_Log ("Accel raw: %d, %d, %d -> scaled: %.2f, %.2f, %.2f m/s²" ,
326+ LOAD16 (data [14 ], data [15 ]), LOAD16 (data [16 ], data [17 ]), LOAD16 (data [18 ], data [19 ]),
327+ accel_values [0 ], accel_values [1 ], accel_values [2 ]);
328+ #endif
329+
330+ SDL_SendJoystickSensor (timestamp , joystick , SDL_SENSOR_GYRO , sensor_timestamp , gyro_values , 3 );
331+ SDL_SendJoystickSensor (timestamp , joystick , SDL_SENSOR_ACCEL , sensor_timestamp , accel_values , 3 );
332+ }
333+
334+ SDL_memcpy (ctx -> last_state , data , SDL_min (size , sizeof (ctx -> last_state )));
335+ }
336+
337+ static void HIDAPI_DriverZUIKI_Handle_EVOTOP_PCBT_StatePacket (SDL_Joystick * joystick , SDL_DriverZUIKI_Context * ctx , Uint8 * data , int size )
338+ {
339+ Sint16 axis ;
340+ Uint64 timestamp = SDL_GetTicksNS ();
341+
342+ axis = (Sint16 )HIDAPI_RemapVal ((float )(data [2 ] << 8 | data [1 ]), 0x0000 , 0xffff , SDL_MIN_SINT16 , SDL_MAX_SINT16 );
343+ SDL_SendJoystickAxis (timestamp , joystick , SDL_GAMEPAD_AXIS_LEFTX , axis );
344+ axis = (Sint16 )HIDAPI_RemapVal ((float )(data [4 ] << 8 | data [3 ]), 0x0000 , 0xffff , SDL_MIN_SINT16 , SDL_MAX_SINT16 );
345+ SDL_SendJoystickAxis (timestamp , joystick , SDL_GAMEPAD_AXIS_LEFTY , axis );
346+ axis = (Sint16 )HIDAPI_RemapVal ((float )(data [6 ] << 8 | data [5 ]), 0x0000 , 0xffff , SDL_MIN_SINT16 , SDL_MAX_SINT16 );
347+ SDL_SendJoystickAxis (timestamp , joystick , SDL_GAMEPAD_AXIS_RIGHTX , axis );
348+ axis = (Sint16 )HIDAPI_RemapVal ((float )(data [8 ] << 8 | data [7 ]), 0x0000 , 0xffff , SDL_MIN_SINT16 , SDL_MAX_SINT16 );
349+ SDL_SendJoystickAxis (timestamp , joystick , SDL_GAMEPAD_AXIS_RIGHTY , axis );
350+
351+ axis = (Sint16 )HIDAPI_RemapVal ((float )(data [10 ] << 8 | data [9 ]), 0x0000 , 0x03ff , SDL_MIN_SINT16 , SDL_MAX_SINT16 );
352+ SDL_SendJoystickAxis (timestamp , joystick , SDL_GAMEPAD_AXIS_LEFT_TRIGGER , axis );
353+ axis = (Sint16 )HIDAPI_RemapVal ((float )(data [12 ] << 8 | data [11 ]), 0x0000 , 0x03ff , SDL_MIN_SINT16 , SDL_MAX_SINT16 );
354+ SDL_SendJoystickAxis (timestamp , joystick , SDL_GAMEPAD_AXIS_RIGHT_TRIGGER , axis );
355+
356+ if (ctx -> last_state [13 ] != data [13 ]) {
357+ Uint8 hat ;
358+ switch (data [13 ]) {
359+ case 1 :
360+ hat = SDL_HAT_UP ;
361+ break ;
362+ case 2 :
363+ hat = SDL_HAT_RIGHTUP ;
364+ break ;
365+ case 3 :
366+ hat = SDL_HAT_RIGHT ;
367+ break ;
368+ case 4 :
369+ hat = SDL_HAT_RIGHTDOWN ;
370+ break ;
371+ case 5 :
372+ hat = SDL_HAT_DOWN ;
373+ break ;
374+ case 6 :
375+ hat = SDL_HAT_LEFTDOWN ;
376+ break ;
377+ case 7 :
378+ hat = SDL_HAT_LEFT ;
379+ break ;
380+ case 8 :
381+ hat = SDL_HAT_LEFTUP ;
382+ break ;
383+ default :
384+ hat = SDL_HAT_CENTERED ;
385+ break ;
386+ }
387+ SDL_SendJoystickHat (timestamp , joystick , 0 , hat );
388+ }
389+ if (ctx -> last_state [14 ] != data [14 ]) {
390+ SDL_SendJoystickButton (timestamp , joystick , SDL_GAMEPAD_BUTTON_SOUTH , ((data [14 ] & 0x01 ) != 0 ));
391+ SDL_SendJoystickButton (timestamp , joystick , SDL_GAMEPAD_BUTTON_EAST , ((data [14 ] & 0x02 ) != 0 ));
392+ SDL_SendJoystickButton (timestamp , joystick , SDL_GAMEPAD_BUTTON_WEST , ((data [14 ] & 0x08 ) != 0 ));
393+ SDL_SendJoystickButton (timestamp , joystick , SDL_GAMEPAD_BUTTON_NORTH , ((data [14 ] & 0x10 ) != 0 ));
394+ SDL_SendJoystickButton (timestamp , joystick , SDL_GAMEPAD_BUTTON_LEFT_SHOULDER , ((data [14 ] & 0x40 ) != 0 ));
395+ SDL_SendJoystickButton (timestamp , joystick , SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER , ((data [14 ] & 0x80 ) != 0 ));
396+ }
397+
398+ if (ctx -> last_state [15 ] != data [15 ]) {
399+ SDL_SendJoystickButton (timestamp , joystick , SDL_GAMEPAD_BUTTON_BACK , ((data [15 ] & 0x04 ) != 0 ));
400+ SDL_SendJoystickButton (timestamp , joystick , SDL_GAMEPAD_BUTTON_START , ((data [15 ] & 0x08 ) != 0 ));
401+ SDL_SendJoystickButton (timestamp , joystick , SDL_GAMEPAD_BUTTON_GUIDE , ((data [15 ] & 0x10 ) != 0 ));
402+ SDL_SendJoystickButton (timestamp , joystick , SDL_GAMEPAD_BUTTON_LEFT_STICK , ((data [15 ] & 0x20 ) != 0 ));
403+ SDL_SendJoystickButton (timestamp , joystick , SDL_GAMEPAD_BUTTON_RIGHT_STICK , ((data [15 ] & 0x40 ) != 0 ));
404+ }
405+
406+ if (ctx -> sensors_supported ) {
407+ Uint64 sensor_timestamp = timestamp ;
408+ float gyro_values [3 ];
409+ gyro_values [0 ] = median_filter_update (& ctx -> filter_gyro_x , LOAD16 (data [17 ], data [18 ]) * GYRO_SCALE );
410+ gyro_values [1 ] = median_filter_update (& ctx -> filter_gyro_y , LOAD16 (data [21 ], data [22 ]) * GYRO_SCALE );
411+ gyro_values [2 ] = median_filter_update (& ctx -> filter_gyro_z , - LOAD16 (data [19 ], data [20 ]) * GYRO_SCALE );
412+ SDL_SendJoystickSensor (timestamp , joystick , SDL_SENSOR_GYRO , sensor_timestamp , gyro_values , 3 );
413+ float accel_values [3 ];
414+ accel_values [0 ] = LOAD16 (data [23 ], data [24 ]) * ACCEL_SCALE ;
415+ accel_values [2 ] = - LOAD16 (data [25 ], data [26 ]) * ACCEL_SCALE ;
416+ accel_values [1 ] = LOAD16 (data [27 ], data [28 ]) * ACCEL_SCALE ;
417+ SDL_SendJoystickSensor (timestamp , joystick , SDL_SENSOR_ACCEL , sensor_timestamp , accel_values , 3 );
418+ #ifdef DEBUG_ZUIKI_PROTOCOL
419+ SDL_Log ("Gyro raw: %d, %d, %d -> scaled: %.2f, %.2f, %.2f rad/s" ,
420+ LOAD16 (data [17 ], data [18 ]), LOAD16 (data [19 ], data [20 ]), LOAD16 (data [21 ], data [22 ]),
421+ gyro_values [0 ], gyro_values [1 ], gyro_values [2 ]);
422+ SDL_Log ("Accel raw: %d, %d, %d -> scaled: %.2f, %.2f, %.2f m/s²" ,
423+ LOAD16 (data [23 ], data [24 ]), LOAD16 (data [25 ], data [26 ]), LOAD16 (data [27 ], data [28 ]),
424+ accel_values [0 ], accel_values [1 ], accel_values [2 ]);
425+ #endif
426+ }
427+
229428 SDL_memcpy (ctx -> last_state , data , SDL_min (size , sizeof (ctx -> last_state )));
230429}
231430
@@ -250,7 +449,11 @@ static bool HIDAPI_DriverZUIKI_UpdateDevice(SDL_HIDAPI_Device *device)
250449 continue ;
251450 }
252451
253- if (size == 8 ) {
452+ if (device -> product_id == USB_PRODUCT_ZUIKI_EVOTOP_PC_BT ) {
453+ HIDAPI_DriverZUIKI_Handle_EVOTOP_PCBT_StatePacket (joystick , ctx , data , size );
454+ } else if (device -> product_id == USB_PRODUCT_ZUIKI_EVOTOP_PC_DINPUT
455+ || device -> product_id == USB_PRODUCT_ZUIKI_MASCON_PRO
456+ || device -> product_id == USB_PRODUCT_ZUIKI_EVOTOP_UWB_DINPUT ) {
254457 HIDAPI_DriverZUIKI_HandleOldStatePacket (joystick , ctx , data , size );
255458 }
256459 }
0 commit comments