Inital commit
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
add_library(stkshid STATIC platform/win/stk_win.c)
|
||||
target_include_directories(stkshid PUBLIC "${CMAKE_SOURCE_DIR}")
|
||||
target_compile_features(stkshid PRIVATE c_std_23)
|
||||
target_link_libraries(stkshid PRIVATE setupapi hid)
|
||||
@@ -0,0 +1,124 @@
|
||||
#include "stk/stk.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <setupapi.h>
|
||||
#include <hidsdi.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
// GUID_DEVINTERFACE_HID from devguid.h
|
||||
static const GUID GUID_DEVINTERFACE_HID = {
|
||||
0x4d1e55b2, 0xf16f, 0x11cf,
|
||||
{ 0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 }
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Internal state
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static HANDLE g_handle = INVALID_HANDLE_VALUE;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Platform backend
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
int stk_open(stk_config_t const* config) {
|
||||
if (!config)
|
||||
return -1;
|
||||
|
||||
HDEVINFO info = SetupDiGetClassDevsW(&GUID_DEVINTERFACE_HID, NULL,
|
||||
NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
||||
if (info == INVALID_HANDLE_VALUE)
|
||||
return -1;
|
||||
|
||||
for (DWORD i = 0; ; i++) {
|
||||
SP_DEVICE_INTERFACE_DATA iface = { .cbSize = sizeof(iface) };
|
||||
if (!SetupDiEnumDeviceInterfaces(info, NULL,
|
||||
&GUID_DEVINTERFACE_HID, i, &iface))
|
||||
break;
|
||||
|
||||
DWORD buflen = 0;
|
||||
SetupDiGetDeviceInterfaceDetailW(info, &iface, NULL, 0, &buflen, NULL);
|
||||
if (buflen == 0)
|
||||
continue;
|
||||
|
||||
PSP_DEVICE_INTERFACE_DETAIL_DATA_W detail =
|
||||
(PSP_DEVICE_INTERFACE_DETAIL_DATA_W)HeapAlloc(GetProcessHeap(),
|
||||
0, buflen);
|
||||
detail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);
|
||||
|
||||
if (!SetupDiGetDeviceInterfaceDetailW(info, &iface, detail, buflen,
|
||||
&buflen, NULL)) {
|
||||
HeapFree(GetProcessHeap(), 0, detail);
|
||||
continue;
|
||||
}
|
||||
|
||||
HANDLE handle = CreateFileW(detail->DevicePath,
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
NULL, OPEN_EXISTING, 0,
|
||||
NULL);
|
||||
|
||||
HeapFree(GetProcessHeap(), 0, detail);
|
||||
|
||||
if (handle == INVALID_HANDLE_VALUE)
|
||||
continue;
|
||||
|
||||
HIDD_ATTRIBUTES attrs = { .Size = sizeof(attrs) };
|
||||
if (!HidD_GetAttributes(handle, &attrs)) {
|
||||
CloseHandle(handle);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (attrs.VendorID == config->vendor_id &&
|
||||
attrs.ProductID == config->product_id) {
|
||||
g_handle = handle;
|
||||
SetupDiDestroyDeviceInfoList(info);
|
||||
return 0;
|
||||
}
|
||||
|
||||
CloseHandle(handle);
|
||||
}
|
||||
|
||||
SetupDiDestroyDeviceInfoList(info);
|
||||
fprintf(stderr, "stk: device 0x%04X:0x%04X not found\n",
|
||||
config->vendor_id, config->product_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int stk_read(stk_state_t* state) {
|
||||
if (g_handle == INVALID_HANDLE_VALUE)
|
||||
return -1;
|
||||
|
||||
uint8_t buf[64];
|
||||
DWORD bytes = 0;
|
||||
|
||||
if (!ReadFile(g_handle, buf, sizeof(buf), &bytes, NULL))
|
||||
return -1;
|
||||
|
||||
if (bytes < 20)
|
||||
return -1;
|
||||
|
||||
// Parse axes (little-endian 16-bit values, offset by 2 bytes)
|
||||
for (int i = 0; i < STK_NUM_AXES; i++) {
|
||||
size_t off = 2 + (size_t)i * 2;
|
||||
uint16_t val = (uint16_t)buf[off] |
|
||||
((uint16_t)buf[off + 1] << 8);
|
||||
state->axes[i] = (int16_t)val;
|
||||
}
|
||||
|
||||
// Parse buttons (byte 1 + bytes 18-19 = 24 bits)
|
||||
state->buttons = (uint32_t)buf[1] |
|
||||
((uint32_t)buf[18] << 8) |
|
||||
((uint32_t)buf[19] << 16);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void stk_close(void) {
|
||||
if (g_handle != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(g_handle);
|
||||
g_handle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* @file stk.h
|
||||
* @brief EdgeTX RC library interface for HID joystick mode.
|
||||
*
|
||||
* Reads raw HID input reports from the EdgeTX radio and exposes
|
||||
* 4 analog axes (sticks + throttle) and 24 digital buttons
|
||||
* (switches, rockers, paddles).
|
||||
*
|
||||
* @par Report layout (20 bytes):
|
||||
* @code
|
||||
* byte 0 : report ID (0x00)
|
||||
* byte 1 : buttons bits 0..7
|
||||
* bytes 2..17 : 8 axes x 16-bit LE (HID usage 0x30..0x37)
|
||||
* bytes 18..19 : buttons bits 8..23
|
||||
* @endcode
|
||||
*
|
||||
* @par Active axes:
|
||||
* | Index | Usage | Physical control | Rest value |
|
||||
* |-------|-------|-----------------|------------|
|
||||
* | 0 | — | unused | always 0 |
|
||||
* | 1 | RX | right stick X | ~1024 |
|
||||
* | 2 | RY | right stick Y | ~1024 |
|
||||
* | 3 | — | unused | always 0 |
|
||||
* | 4 | Z | left stick Y | 0..2047 |
|
||||
* | 5 | VR | left stick X | ~1024 |
|
||||
* | 6..7 | — | unused | always 0 |
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Axis range (10-bit ADC on the radio)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define STK_AXIS_MIN 0
|
||||
#define STK_AXIS_MAX 2047
|
||||
#define STK_AXIS_MID 1024
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Default device IDs (EdgeTX radio in HID joystick mode)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define STK_DEFAULT_VID 0x1209
|
||||
#define STK_DEFAULT_PID 0x4F54
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Configuration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
typedef struct {
|
||||
uint16_t vendor_id;
|
||||
uint16_t product_id;
|
||||
} stk_config_t;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Radio state
|
||||
//
|
||||
// axes[] holds all 8 HID axes (indices 0-7). Only indices 1, 2, 4, 5
|
||||
// are wired to physical controls. The rest stay at 0.
|
||||
//
|
||||
// buttons is a 24-bit bitmask: bit N set = switch/button N+1 pressed.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define STK_NUM_AXES 8
|
||||
#define STK_NUM_BUTTONS 24
|
||||
|
||||
typedef struct {
|
||||
int16_t axes[STK_NUM_AXES];
|
||||
uint32_t buttons;
|
||||
} stk_state_t;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Convenience accessors for the 4 active axes
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static inline int16_t stk_right_x(stk_state_t const* s) {
|
||||
return s->axes[1];
|
||||
}
|
||||
|
||||
static inline int16_t stk_right_y(stk_state_t const* s) {
|
||||
return s->axes[2];
|
||||
}
|
||||
|
||||
static inline int16_t stk_throttle(stk_state_t const* s) {
|
||||
return s->axes[4];
|
||||
}
|
||||
|
||||
static inline int16_t stk_left_x(stk_state_t const* s) {
|
||||
return s->axes[5];
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Button bitmask helpers (bit 0 = first switch, bit 23 = last)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static inline bool stk_button_pressed(stk_state_t const* s,
|
||||
uint8_t index) {
|
||||
if (index >= STK_NUM_BUTTONS)
|
||||
return false;
|
||||
return (s->buttons >> index) & 1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// API
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Open the HID radio device.
|
||||
* config must remain valid for the lifetime of the open session.
|
||||
* Returns 0 on success, -1 on failure.
|
||||
*/
|
||||
int stk_open(stk_config_t const* config);
|
||||
|
||||
/**
|
||||
* Read the current radio state (non-blocking).
|
||||
* Returns 0 on success, -1 on failure or no new data.
|
||||
*/
|
||||
int stk_read(stk_state_t* state);
|
||||
|
||||
/**
|
||||
* Close the radio device. Safe to call multiple times.
|
||||
*/
|
||||
void stk_close(void);
|
||||
Reference in New Issue
Block a user