Skip to content

Commit 2980981

Browse files
committed
Improved handling of Apex 5 acquire protocol
1 parent f30f968 commit 2980981

File tree

1 file changed

+132
-70
lines changed

1 file changed

+132
-70
lines changed

src/joystick/hidapi/SDL_hidapi_flydigi.c

Lines changed: 132 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -61,18 +61,21 @@ enum
6161
#define SENSOR_INTERVAL_APEX5_WIRED_RATE_HZ 970
6262
#define SENSOR_INTERVAL_APEX5_WIRED_NS (SDL_NS_PER_SECOND / SENSOR_INTERVAL_APEX5_WIRED_RATE_HZ)
6363

64-
#define FLYDIGI_ACQUIRE_CONTROLLER_HEARTBEAT_TIME 1000 * 60
65-
66-
#define FLYDIGI_V1_CMD_REPORT_ID 0x05
67-
#define FLYDIGI_V1_HAPTIC_COMMAND 0x0F
68-
#define FLYDIGI_V1_GET_INFO_COMMAND 0xEC
69-
70-
#define FLYDIGI_V2_CMD_REPORT_ID 0x03
71-
#define FLYDIGI_V2_MAGIC1 0x5A
72-
#define FLYDIGI_V2_MAGIC2 0xA5
73-
#define FLYDIGI_V2_GET_INFO_COMMAND 0x01
74-
#define FLYDIGI_V2_HAPTIC_COMMAND 0x12
75-
#define FLYDIGI_V2_ACQUIRE_CONTROLLER_COMMAND 0x1c
64+
#define FLYDIGI_ACQUIRE_CONTROLLER_HEARTBEAT_TIME 1000 * 30
65+
66+
#define FLYDIGI_V1_CMD_REPORT_ID 0x05
67+
#define FLYDIGI_V1_HAPTIC_COMMAND 0x0F
68+
#define FLYDIGI_V1_GET_INFO_COMMAND 0xEC
69+
70+
#define FLYDIGI_V2_CMD_REPORT_ID 0x03
71+
#define FLYDIGI_V2_MAGIC1 0x5A
72+
#define FLYDIGI_V2_MAGIC2 0xA5
73+
#define FLYDIGI_V2_GET_INFO_COMMAND 0x01
74+
#define FLYDIGI_V2_GET_STATUS_COMMAND 0x10
75+
#define FLYDIGI_V2_SET_STATUS_COMMAND 0x11
76+
#define FLYDIGI_V2_HAPTIC_COMMAND 0x12
77+
#define FLYDIGI_V2_ACQUIRE_CONTROLLER_COMMAND 0x1C
78+
#define FLYDIGI_V2_INPUT_REPORT 0xEF
7679

7780
#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8))
7881

@@ -89,7 +92,8 @@ typedef struct
8992
Uint64 sensor_timestamp_step_ns; // Based on observed rate of receipt of IMU sensor packets.
9093
float accelScale;
9194
float gyroScale;
92-
Uint64 last_heartbeat;
95+
Uint64 next_heartbeat;
96+
Uint64 last_packet;
9397
Uint8 last_state[USB_PACKET_LENGTH];
9498
} SDL_DriverFlydigi_Context;
9599

@@ -198,58 +202,78 @@ static bool GetReply(SDL_HIDAPI_Device* device, Uint8 command, Uint8* data, size
198202

199203
static bool SDL_HIDAPI_Flydigi_SendAcquireRequest(SDL_HIDAPI_Device *device, bool acquire)
200204
{
201-
const Uint8 acquireControllerCmd[] = {
205+
const Uint8 acquireControllerCmd[32] = {
202206
FLYDIGI_V2_CMD_REPORT_ID,
203207
FLYDIGI_V2_MAGIC1,
204208
FLYDIGI_V2_MAGIC2,
205209
FLYDIGI_V2_ACQUIRE_CONTROLLER_COMMAND,
206210
23,
207211
acquire ? 1 : 0,
208-
83, 68, 76, 0
212+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
209213
};
214+
215+
if (acquire) {
216+
const char *name = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING);
217+
SDL_assert(name != NULL);
218+
SDL_strlcpy((char *)&acquireControllerCmd[6], name, sizeof(acquireControllerCmd) - 6);
219+
}
210220
if (SDL_hid_write(device->dev, acquireControllerCmd, sizeof(acquireControllerCmd)) < 0) {
211221
return SDL_SetError("Couldn't send acquire command");
212222
}
213223
return true;
214224
}
215225

226+
static bool HIDAPI_DriverFlydigi_HandleAcquireResponse(Uint8 *data, int size)
227+
{
228+
if (data[5] != 1) {
229+
return SDL_SetError("Controller acquiring has been disabled");
230+
}
231+
return true;
232+
}
233+
216234
static bool HIDAPI_DriverFlydigi_InitControllerV2(SDL_HIDAPI_Device *device)
217235
{
218236
SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context;
219237

238+
Uint8 data[USB_PACKET_LENGTH];
220239
const Uint8 query_info[] = { FLYDIGI_V2_CMD_REPORT_ID, FLYDIGI_V2_MAGIC1, FLYDIGI_V2_MAGIC2, FLYDIGI_V2_GET_INFO_COMMAND, 2, 0 };
221240
if (SDL_hid_write(device->dev, query_info, sizeof(query_info)) < 0) {
222241
return SDL_SetError("Couldn't query controller info");
223242
}
243+
if (!GetReply(device, FLYDIGI_V2_GET_INFO_COMMAND, data, sizeof(data))) {
244+
return SDL_SetError("Couldn't get controller info");
245+
}
224246

225-
Uint8 data[USB_PACKET_LENGTH];
226-
if (GetReply(device, FLYDIGI_V2_GET_INFO_COMMAND, data, sizeof(data))) {
227-
ctx->deviceID = data[6];
228-
ctx->firmware_version = LOAD16(data[17], data[16]);
229-
230-
switch (data[7]) {
231-
case 1:
232-
// Wired connection
233-
ctx->wireless = false;
234-
break;
235-
case 2:
236-
// Wireless connection
237-
ctx->wireless = true;
238-
break;
239-
default:
240-
break;
241-
}
247+
// Check the firmware version
248+
ctx->firmware_version = LOAD16(data[17], data[16]);
249+
if (ctx->firmware_version < 0x7031) {
250+
return SDL_SetError("Unsupported firmware version");
242251
}
243252

244-
ctx->last_heartbeat = SDL_GetTicks();
253+
switch (data[7]) {
254+
case 1:
255+
// Wired connection
256+
ctx->wireless = false;
257+
break;
258+
case 2:
259+
// Wireless connection
260+
ctx->wireless = true;
261+
break;
262+
default:
263+
break;
264+
}
265+
ctx->deviceID = data[6];
245266

246-
if (!SDL_HIDAPI_Flydigi_SendAcquireRequest(device, true)) {
247-
return false;
267+
// See whether we can acquire the controller
268+
const Uint8 query_status[] = { FLYDIGI_V2_CMD_REPORT_ID, FLYDIGI_V2_MAGIC1, FLYDIGI_V2_MAGIC2, FLYDIGI_V2_GET_STATUS_COMMAND };
269+
if (SDL_hid_write(device->dev, query_status, sizeof(query_status)) < 0) {
270+
return SDL_SetError("Couldn't query controller status");
248271
}
249-
if (!GetReply(device, FLYDIGI_V2_ACQUIRE_CONTROLLER_COMMAND, data, sizeof(data))) {
250-
return SDL_SetError("Controller acquiring is not supported");
272+
if (!GetReply(device, FLYDIGI_V2_GET_STATUS_COMMAND, data, sizeof(data))) {
273+
return SDL_SetError("Couldn't get controller status");
251274
}
252-
if (data[6] != 1) {
275+
if (data[10] != 1) {
276+
// Click "Allow third-party apps to take over mappings" in the FlyDigi Space Station app
253277
return SDL_SetError("Controller acquiring is disabled");
254278
}
255279
return true;
@@ -341,14 +365,10 @@ static void HIDAPI_DriverFlydigi_UpdateDeviceIdentity(SDL_HIDAPI_Device *device)
341365
case SDL_FLYDIGI_APEX5:
342366
HIDAPI_SetDeviceName(device, "Flydigi Apex 5");
343367
ctx->has_lmrm = true;
344-
345-
// The Apex 5 gyro values are only usable on firmware 7.0.3.0 and newer
346-
if (ctx->firmware_version >= 0x7030) {
347-
ctx->sensors_supported = true;
348-
ctx->accelScale = SDL_STANDARD_GRAVITY / 4096.0f;
349-
ctx->gyroScale = DEG2RAD(2000.0f);
350-
ctx->sensor_timestamp_step_ns = ctx->wireless ? SENSOR_INTERVAL_APEX5_DONGLE_NS : SENSOR_INTERVAL_APEX5_WIRED_NS;
351-
}
368+
ctx->sensors_supported = true;
369+
ctx->accelScale = SDL_STANDARD_GRAVITY / 4096.0f;
370+
ctx->gyroScale = DEG2RAD(2000.0f);
371+
ctx->sensor_timestamp_step_ns = ctx->wireless ? SENSOR_INTERVAL_APEX5_DONGLE_NS : SENSOR_INTERVAL_APEX5_WIRED_NS;
352372
break;
353373
case SDL_FLYDIGI_VADER2:
354374
// The Vader 2 controller has sensors, but they're only reported when gyro mouse is enabled
@@ -503,10 +523,6 @@ static void HIDAPI_DriverFlydigi_HandleStatePacketV1(SDL_Joystick *joystick, SDL
503523
{
504524
Sint16 axis;
505525
Uint64 timestamp = SDL_GetTicksNS();
506-
if (data[0] != 0x04 || data[1] != 0xFE) {
507-
// We don't know how to handle this report
508-
return;
509-
}
510526

511527
Uint8 extra_button_index = SDL_GAMEPAD_NUM_BASE_FLYDIGI_BUTTONS;
512528

@@ -632,21 +648,22 @@ static void HIDAPI_DriverFlydigi_HandleStatePacketV1(SDL_Joystick *joystick, SDL
632648
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
633649
}
634650

651+
static bool HIDAPI_DriverFlydigi_HandlePacketV1(SDL_Joystick *joystick, SDL_DriverFlydigi_Context *ctx, Uint8 *data, int size)
652+
{
653+
if (data[0] != 0x04 || data[1] != 0xFE) {
654+
// We don't know how to handle this report, ignore it
655+
return true;
656+
}
657+
658+
HIDAPI_DriverFlydigi_HandleStatePacketV1(joystick, ctx, data, size);
659+
return true;
660+
}
661+
635662
static void HIDAPI_DriverFlydigi_HandleStatePacketV2(SDL_Joystick *joystick, SDL_DriverFlydigi_Context *ctx, Uint8 *data, int size)
636663
{
637664
Sint16 axis;
638665
Uint64 timestamp = SDL_GetTicksNS();
639666

640-
if (size > 0 && data[0] != 0x5A) {
641-
// If first byte is not 0x5A, it must be REPORT_ID, we need to remove it.
642-
++data;
643-
--size;
644-
}
645-
if (size < 31 || data[0] != 0x5A || data[1] != 0xA5 || data[2] != 0xEF) {
646-
// We don't know how to handle this report
647-
return;
648-
}
649-
650667
Uint8 extra_button_index = SDL_GAMEPAD_NUM_BASE_FLYDIGI_BUTTONS;
651668

652669
if (ctx->last_state[11] != data[11]) {
@@ -766,42 +783,85 @@ static void HIDAPI_DriverFlydigi_HandleStatePacketV2(SDL_Joystick *joystick, SDL
766783
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
767784
}
768785

786+
static bool HIDAPI_DriverFlydigi_HandlePacketV2(SDL_Joystick *joystick, SDL_DriverFlydigi_Context *ctx, Uint8 *data, int size)
787+
{
788+
if (size > 0 && data[0] != 0x5A) {
789+
// If first byte is not 0x5A, it must be REPORT_ID, we need to remove it.
790+
++data;
791+
--size;
792+
}
793+
if (size < 31 || data[0] != FLYDIGI_V2_MAGIC1 || data[1] != FLYDIGI_V2_MAGIC2) {
794+
// We don't know how to handle this report, ignore it
795+
return true;
796+
}
797+
798+
switch (data[2]) {
799+
case FLYDIGI_V2_ACQUIRE_CONTROLLER_COMMAND:
800+
if (!HIDAPI_DriverFlydigi_HandleAcquireResponse(data, size)) {
801+
return false;
802+
}
803+
break;
804+
case FLYDIGI_V2_INPUT_REPORT:
805+
HIDAPI_DriverFlydigi_HandleStatePacketV2(joystick, ctx, data, size);
806+
break;
807+
default:
808+
// We don't recognize this command, ignore it
809+
break;
810+
}
811+
return true;
812+
}
813+
769814
static bool HIDAPI_DriverFlydigi_UpdateDevice(SDL_HIDAPI_Device *device)
770815
{
771816
SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context;
772817
SDL_Joystick *joystick = NULL;
773818
Uint8 data[USB_PACKET_LENGTH];
774819
int size = 0;
820+
Uint64 now = SDL_GetTicks();
775821

776822
if (device->num_joysticks > 0) {
777823
joystick = SDL_GetJoystickFromID(device->joysticks[0]);
778-
} else {
779-
return false;
780824
}
781825

782-
if (device->vendor_id == USB_VENDOR_FLYDIGI_V2) {
783-
Uint64 now = SDL_GetTicks();
784-
if (now >= (ctx->last_heartbeat + FLYDIGI_ACQUIRE_CONTROLLER_HEARTBEAT_TIME)) {
826+
if (device->vendor_id == USB_VENDOR_FLYDIGI_V2 && joystick) {
827+
if (!ctx->next_heartbeat || now >= ctx->next_heartbeat) {
785828
SDL_HIDAPI_Flydigi_SendAcquireRequest(device, true);
786-
ctx->last_heartbeat = now;
829+
ctx->next_heartbeat = now + FLYDIGI_ACQUIRE_CONTROLLER_HEARTBEAT_TIME;
787830
}
788831
}
789832

790833
while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
791834
#ifdef DEBUG_FLYDIGI_PROTOCOL
792835
HIDAPI_DumpPacket("Flydigi packet: size = %d", data, size);
793836
#endif
837+
ctx->last_packet = now;
838+
794839
if (!joystick) {
795840
continue;
796841
}
842+
797843
if (device->vendor_id == USB_VENDOR_FLYDIGI_V1) {
798-
HIDAPI_DriverFlydigi_HandleStatePacketV1(joystick, ctx, data, size);
844+
if (!HIDAPI_DriverFlydigi_HandlePacketV1(joystick, ctx, data, size)) {
845+
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
846+
return false;
847+
}
799848
} else {
800-
HIDAPI_DriverFlydigi_HandleStatePacketV2(joystick, ctx, data, size);
849+
if (!HIDAPI_DriverFlydigi_HandlePacketV2(joystick, ctx, data, size)) {
850+
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
851+
return false;
852+
}
853+
}
854+
}
855+
856+
if (device->vendor_id == USB_VENDOR_FLYDIGI_V2) {
857+
// If we haven't gotten a packet in a while, check to make sure we can still acquire it
858+
const int INPUT_TIMEOUT_MS = 100;
859+
if (now >= (ctx->last_packet + INPUT_TIMEOUT_MS)) {
860+
ctx->next_heartbeat = now;
801861
}
802862
}
803863

804-
if (size < 0) {
864+
if (size < 0 && device->num_joysticks > 0) {
805865
// Read error, device is disconnected
806866
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
807867
}
@@ -810,7 +870,9 @@ static bool HIDAPI_DriverFlydigi_UpdateDevice(SDL_HIDAPI_Device *device)
810870

811871
static void HIDAPI_DriverFlydigi_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
812872
{
813-
SDL_HIDAPI_Flydigi_SendAcquireRequest(device, false);
873+
// Don't unacquire the controller, someone else might be using it too.
874+
// The controller will automatically unacquire itself after a little while
875+
//SDL_HIDAPI_Flydigi_SendAcquireRequest(device, false);
814876
}
815877

816878
static void HIDAPI_DriverFlydigi_FreeDevice(SDL_HIDAPI_Device *device)

0 commit comments

Comments
 (0)