From b90c396d3404af47cdeb6d609d95aca2976034a4 Mon Sep 17 00:00:00 2001 From: Oleg Makarenko Date: Fri, 13 Jun 2025 02:58:22 +0300 Subject: [PATCH] Add GT Neo LED update feature --- src/monocoque/devices/CMakeLists.txt | 2 + src/monocoque/devices/usb/wheels/gtneo.c | 225 +++++++++++++++++++++++ src/monocoque/devices/usb/wheels/gtneo.h | 26 +++ src/monocoque/devices/wheeldevice.c | 34 +++- src/monocoque/devices/wheeldevice.h | 3 +- src/monocoque/helper/confighelper.c | 5 + src/monocoque/helper/confighelper.h | 3 +- 7 files changed, 295 insertions(+), 3 deletions(-) create mode 100644 src/monocoque/devices/usb/wheels/gtneo.c create mode 100644 src/monocoque/devices/usb/wheels/gtneo.h diff --git a/src/monocoque/devices/CMakeLists.txt b/src/monocoque/devices/CMakeLists.txt index 7054d28..82aa52b 100644 --- a/src/monocoque/devices/CMakeLists.txt +++ b/src/monocoque/devices/CMakeLists.txt @@ -29,6 +29,8 @@ set(devices_source_files usb/wheels/cammusc5.c usb/wheels/cammusc12.h usb/wheels/cammusc12.c + usb/wheels/gtneo.h + usb/wheels/gtneo.c sound/usb_generic_shaker_pulse.c serial/arduino.h serial/arduino.c diff --git a/src/monocoque/devices/usb/wheels/gtneo.c b/src/monocoque/devices/usb/wheels/gtneo.c new file mode 100644 index 0000000..401d4cf --- /dev/null +++ b/src/monocoque/devices/usb/wheels/gtneo.c @@ -0,0 +1,225 @@ +#include +#include +#include +#include + +#include "gtneo.h" +#include +#include "../../../slog/slog.h" +#include "../../serial/arduinoledlua.h" + +// Simagic allows setting any led, therefore LED index should be defined. +struct LED { + uint8_t index; + uint8_t red; + uint8_t green; + uint8_t blue; +}; + +// Simagic is using SEND_FEATURE_REPORT +// ReportID is 240 (0xf0) +// Data length is 64 bytes +// Prefix is 00 00 00 00 00 +// Then we have some descriptor. I had 0xEC and 0x03. +// Then: length of LED array +// Then: number of LED in hex +// RGB value for that LED + + +void prepare_device(hid_device *device) { + uint8_t prepare_report[SIMAGIC_MAX_PAYLOAD_SIZE] = {0}; + prepare_report[0] = 0x00; + prepare_report[1] = 0x00; + prepare_report[2] = 0x00; + prepare_report[3] = 0x00; + prepare_report[4] = 0x00; + prepare_report[5] = SIMAGIC_LED_DESCRIPTOR_LED_CONTROL; + prepare_report[6] = SIMAGIC_LED_DESCRIPTOR_PREPARE_LEDS; + prepare_report[7] = 0x01; + + uint8_t report_id_with_data[SIMAGIC_MAX_PAYLOAD_SIZE] = {SIMAGIC_LED_REPORT_ID}; // Report ID at the start + memcpy(&report_id_with_data[1], prepare_report, SIMAGIC_MAX_PAYLOAD_SIZE-1); + + // Send the HID feature report + int res = hid_send_feature_report(device, report_id_with_data, sizeof(report_id_with_data)); + if (res < 0) { + fprintf(stderr, "Failed to send HID feature report\n"); + } +} + +/* + Arbitrary function for sending out leds to simagic. + Inputs: + device: hid_device handle + leds: array of the struct LED + led_count: count of leds in leds array +*/ +int send_leds(hid_device *device, struct LED* leds, size_t led_count) { + uint8_t descriptor[2] = {SIMAGIC_LED_DESCRIPTOR_LED_CONTROL, SIMAGIC_LED_DESCRIPTOR_SET_LEDS}; + + prepare_device(device); + int result = 0; + size_t led_i = 0; + while (led_i < led_count) { + size_t chunk_length = (SIMAGIC_MAX_PAYLOAD_SIZE -1-9) / 4; // 1 for reportid, 9 bytes for header, rest for LED data (each LED is 4 bytes) + size_t chunk_size = chunk_length * 4; + + uint8_t report[SIMAGIC_MAX_PAYLOAD_SIZE] = {0}; // SIMAGIC_MAX_PAYLOAD_SIZE bytes per report + memcpy(report, (uint8_t[]){0x00, 0x00, 0x00, 0x00, 0x00}, 5); // Set the prefix (5 bytes) + + memcpy(&report[5], descriptor, 2); // Set the descriptor (2 bytes) + + // Fill in the LED data + for (size_t i = 0; i < chunk_length && led_i + i < led_count; ++i) { + memcpy(&report[8+i*4], &leds[led_i+i], 4); + report[7]++; // Set the LED array length for this chunk + } + + uint8_t report_id_with_data[SIMAGIC_MAX_PAYLOAD_SIZE] = {SIMAGIC_LED_REPORT_ID}; + memcpy(&report_id_with_data[1], report, SIMAGIC_MAX_PAYLOAD_SIZE-1); + + // Send the HID feature report + int res = hid_send_feature_report(device, report_id_with_data, sizeof(report_id_with_data)); + if (res < 0) { + fprintf(stderr, "Failed to send HID feature report chunk\n"); + return -1; + } + result += res; + + // Move to the next chunk of LEDs + led_i += chunk_length; + } + return result; +} + + +// default simpro profile: +// 97: b b b b b b b b b b b b b b b //flashing +// 93: b b b b b b b b b b b b b b b //flashing +// 90: g g g g g g g g g y y y w w w +// 88: g g g g g g g g g y y y +// 85: g g g g g g g g g +// 83: g g g g g g g +// 78: g g g g g +// 75: g g g +// 70: g +// less: empty (000000) +// Telemetry LEDs are 58-72 + + +int simagic_gtneo_customled_update(USBDevice* usbdevice, SimData* simdata) +{ + int res = 1; + + size_t led_count = GT_NEO_LEDS_TOTAL-GT_NEO_LEDS_TELEMETRY_START; // Expose whole LED array + size_t bufsize = (led_count * 3); + char ledbytes[bufsize]; + for(int x = 0; x < bufsize; x++) + ledbytes[x] = 0x00; + + lua_State* L = usbdevice->m.L; + + lua_pushstring(L, "buff"); + lua_pushlightuserdata(L, &ledbytes); + lua_settable(L, LUA_REGISTRYINDEX); + + simdata_to_lua(L, simdata); + lua_setglobal(L, "simdata"); + + lua_pushinteger(L, led_count); + lua_setglobal(L, "TotalLeds"); + + lua_register(L, "set_led_to_color", set_led_to_color); + lua_register(L, "set_led_range_to_color", set_led_range_to_color); + lua_register(L, "set_led_to_rgb_color", set_led_to_rgb_color); + lua_register(L, "set_led_range_to_rgb_color", set_led_range_to_rgb_color); + lua_register(L, "led_clear_all", led_clear_all); + + lua_pushinteger(L, LUALEDCOLOR_RED); + lua_setglobal(L, "RED"); + lua_pushinteger(L, LUALEDCOLOR_GREEN); + lua_setglobal(L, "GREEN"); + lua_pushinteger(L, LUALEDCOLOR_BLUE); + lua_setglobal(L, "BLUE"); + lua_pushinteger(L, LUALEDCOLOR_YELLOW); + lua_setglobal(L, "YELLOW"); + lua_pushinteger(L, LUALEDCOLOR_ORANGE); + lua_setglobal(L, "ORANGE"); + + lua_getglobal(L,"myFunc"); + if (lua_pcall(L, 0, 0, 0) != LUA_OK) + { + fprintf(stderr, "Error calling Lua script: %s\n", lua_tostring(L, -1)); + } + + // ledbytes is an array of leds. We need to copy colours to our struct LED and call 'send_leds' + struct LED teleleds[led_count]; + + for (int i = 0; i < led_count; i++) { + teleleds[i] = (struct LED){GT_NEO_LEDS_TELEMETRY_START+i, 0x00, 0x00, 0x00}; + teleleds[i].red = ledbytes[(i * 3) + 0]; + teleleds[i].green = ledbytes[(i * 3) + 1]; + teleleds[i].blue = ledbytes[(i * 3) + 2]; + } + + res = send_leds(usbdevice->handle, teleleds, led_count); + return res; +} + + +int simagic_gtneo_init(USBDevice* wheeldevice, const char* luafile) +{ + wheeldevice->u.wheeldevice.useLua = false; + + slogi("initializing Simagic GT Neo wheel..."); + + int res = 0; + + res = hid_init(); + + wheeldevice->handle = hid_open(SIMAGIC_GTNEO_VENDORID, SIMAGIC_GTNEO_PRODUCTID, NULL); + + if (!wheeldevice->handle) + { + sloge("Could not find attached GT Neo Wheel"); + res = hid_exit(); + return 1; + } + slogd("Found Simagic GT Neo Wheel..."); + + slogt("Using lua file"); + + lua_State* L = luaL_newstate(); + luaL_openlibs(L); + + //int top=lua_gettop(L); + int status = luaL_loadfile(L, luafile); + + if (status) { + /* If something went wrong, error message is at the top of */ + /* the stack */ + sloge("There is an issue with your lua script"); + fprintf(stderr, "Couldn't load file: %s\n", lua_tostring(L, -1)); + lua_close(L); + return -1; + //exit(1); + } + + wheeldevice->u.wheeldevice.useLua = true; + lua_setglobal(L,"myFunc"); + + wheeldevice->m.L = L; + + return res; +} + + +int simagic_gtneo_free(USBDevice* wheeldevice) +{ + int res = 0; + + hid_close(wheeldevice->handle); + res = hid_exit(); + + return res; +} diff --git a/src/monocoque/devices/usb/wheels/gtneo.h b/src/monocoque/devices/usb/wheels/gtneo.h new file mode 100644 index 0000000..10da7af --- /dev/null +++ b/src/monocoque/devices/usb/wheels/gtneo.h @@ -0,0 +1,26 @@ +#ifndef _GTNEO_H +#define _GTNEO_H + +#include "../../wheeldevice.h" +#include "../../simdevice.h" + +#define SIMAGIC_GTNEO_VENDORID 0x3670 +#define SIMAGIC_GTNEO_PRODUCTID 0x0805 + +#define SIMAGIC_MAX_PAYLOAD_SIZE 64 +#define SIMAGIC_LED_REPORT_ID 0xf0 // LED control report id? + +#define SIMAGIC_LED_DESCRIPTOR_LED_CONTROL 0xEC // LED control? +#define SIMAGIC_LED_DESCRIPTOR_PREPARE_LEDS 0x02 //probably something like 'prepare led canvas' +#define SIMAGIC_LED_DESCRIPTOR_SET_LEDS 0x03 //probably 'set temporare leds' + +// expose whole LED array to the lua. +// Telemetry leds are 58-72 +#define GT_NEO_LEDS_TELEMETRY_START 0 +#define GT_NEO_LEDS_TOTAL 73 + +int simagic_gtneo_init(USBDevice* wheeldevice, const char* luafile); +int simagic_gtneo_free(USBDevice* wheeldevice); +int simagic_gtneo_customled_update(USBDevice* usbdevice, SimData* simdata); + +#endif diff --git a/src/monocoque/devices/wheeldevice.c b/src/monocoque/devices/wheeldevice.c index 2cb9305..bdeddd8 100644 --- a/src/monocoque/devices/wheeldevice.c +++ b/src/monocoque/devices/wheeldevice.c @@ -4,6 +4,7 @@ #include "usb/wheels/cammusc5.h" #include "usb/wheels/cammusc12.h" +#include "usb/wheels/gtneo.h" #include "serial/moza.h" #include "simdevice.h" @@ -30,7 +31,16 @@ int wheeldev_update(USBDevice* usbdevice, SimData* simdata) cammusc12_update(usbdevice, simdata->maxrpm, simdata->rpms, simdata->gear, simdata->velocity); } break; - + case WHEELDEV_SIMAGICGTNEO : + if(usbdevice->u.wheeldevice.useLua == true) + { + simagic_gtneo_customled_update(usbdevice, simdata); + } + else + { + sloge("GT Neo requires config file"); + } + break; } return 0; @@ -93,6 +103,28 @@ int wheeldev_init(USBDevice* usbdevice, DeviceSettings* ds) error = cammusc12_init(usbdevice, NULL); } break; + case SIMDEVSUBTYPE_SIMAGICGTNEO: + wheeldevice->type = WHEELDEV_SIMAGICGTNEO; + slogi("Attempting to initialize Simagic GT Neo"); + + if(ds->has_config == true) + { + usbdevice->m.device_specific_config_file = strdup(ds->specific_config_file); + error = simagic_gtneo_init(usbdevice, usbdevice->m.device_specific_config_file); + if(error != 0) + { + if(usbdevice->m.device_specific_config_file != NULL) + { + free(usbdevice->m.device_specific_config_file); + } + } + } + else + { + sloge("Simagic GT Neo requires lua config file to function"); + error = -1; + } + break; default: wheeldevice->type = WHEELDEV_UNKNOWN; slogw("Unknown cammus wheel detected, trying C5"); diff --git a/src/monocoque/devices/wheeldevice.h b/src/monocoque/devices/wheeldevice.h index c791dd1..7b636b4 100644 --- a/src/monocoque/devices/wheeldevice.h +++ b/src/monocoque/devices/wheeldevice.h @@ -11,7 +11,8 @@ typedef enum WHEELDEV_UNKNOWN = 0, WHEELDEV_CAMMUSC5 = 1, WHEELDEV_CAMMUSC12 = 2, - WHEELDEV_MOZAR5 = 3 + WHEELDEV_MOZAR5 = 3, + WHEELDEV_SIMAGICGTNEO = 4 } WheelType; diff --git a/src/monocoque/helper/confighelper.c b/src/monocoque/helper/confighelper.c index b6e5769..02df557 100644 --- a/src/monocoque/helper/confighelper.c +++ b/src/monocoque/helper/confighelper.c @@ -113,6 +113,11 @@ int strtodevsubsubtype(const char* device_subsubtype, DeviceSettings* ds) ds->dev_subsubtype = SIMDEVSUBTYPE_SIMAGICP1000PEDALS; devfound = true; } + if (strcicmp(device_subsubtype, "SIMAGICGTNEO") == 0) + { + ds->dev_subsubtype = SIMDEVSUBTYPE_SIMAGICGTNEO; + devfound = true; + } if(devfound == false) { diff --git a/src/monocoque/helper/confighelper.h b/src/monocoque/helper/confighelper.h index 945bb4e..a5a3baf 100644 --- a/src/monocoque/helper/confighelper.h +++ b/src/monocoque/helper/confighelper.h @@ -41,7 +41,8 @@ typedef enum SIMDEVSUBTYPE_CAMMUSC12 = 2, SIMDEVSUBTYPE_MOZAR5 = 3, SIMDEVSUBTYPE_CSLELITEV3PEDALS = 4, - SIMDEVSUBTYPE_SIMAGICP1000PEDALS =5 + SIMDEVSUBTYPE_SIMAGICP1000PEDALS = 5, + SIMDEVSUBTYPE_SIMAGICGTNEO = 6 } DeviceSubSubType;