From b694a15cb16535e8cb5d0a1ab7904384796a1b6f Mon Sep 17 00:00:00 2001 From: TheJackiMonster Date: Fri, 31 Mar 2023 15:11:41 +0200 Subject: [PATCH] Initial commit Signed-off-by: TheJackiMonster --- .gitignore | 6 + .gitmodules | 3 + CMakeLists.txt | 23 ++++ LICENSE.md | 21 +++ README.md | 62 +++++++++ modules/Fusion | 1 + src/device3.c | 345 ++++++++++++++++++++++++++++++++++++++++++++++++ src/device3.h | 98 ++++++++++++++ src/device4.c | 346 +++++++++++++++++++++++++++++++++++++++++++++++++ src/device4.h | 104 +++++++++++++++ src/driver.c | 106 +++++++++++++++ 11 files changed, 1115 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 CMakeLists.txt create mode 100644 LICENSE.md create mode 100644 README.md create mode 160000 modules/Fusion create mode 100644 src/device3.c create mode 100644 src/device3.h create mode 100644 src/device4.c create mode 100644 src/device4.h create mode 100644 src/driver.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..69c0bec --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.idea/ + +cmake-build-debug/ +cmake-build-release/ + +build/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..ddbb0a1 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "modules/Fusion"] + path = modules/Fusion + url = https://github.com/xioTechnologies/Fusion.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e20270f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.16) +project(nrealAirLinuxDriver) + +set(CMAKE_C_STANDARD 17) + +add_subdirectory(modules/Fusion/Fusion) + +find_package(PkgConfig REQUIRED) +pkg_search_module(LIBUSB1 REQUIRED libusb-1.0) + +add_executable(nrealAirLinuxDriver + src/device3.c + src/device4.c + src/driver.c +) + +target_include_directories(nrealAirLinuxDriver + SYSTEM BEFORE PRIVATE modules/Fusion ${LIBUSB1_INCLUDE_DIRS} +) + +target_link_libraries(nrealAirLinuxDriver + ${LIBUSB1_LIBRARIES} Fusion m +) \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..03e2456 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 thejackimonster. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..60b8b16 --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +# Custom user-space driver for the nreal Air to use it on Linux + +## Information before use + +The code is provided as is and it's free to use. However the contributors can neither guarantee that +it will work or that it won't damage your device since all of it is based on reverse-engineering +instead of public documentation. The contributors are not responsible for proper or even official +support. So use it at your own risk! + +## Inspiration and motivation + +Because I've written a user-space driver before for a [graphics tablet](https://gitlab.com/TheJackiMonster/HuionGT191LinuxDriver), +I thought I might look into it. To my surprise video output, audio output (stereo) and audio input (microphone) already +worked using drivers from the [Linux kernel](https://linux-hardware.org/?id=usb:3318-0424). So the only piece missing +for full use was the IMU sensor data in theory. + +A big help for implementing this piece of software was the source code and feedback from a custom +driver for Windows [here](https://github.com/MSmithDev/AirAPI_Windows/). Without that I would have +needed to find out payloads my own. So big thanks to such projects being open-source! + +## Features + +The driver will read, parse and interpret sensor data from two HID interfaces to feed custom event +callbacks with data which can be used in user-space applications (for example whether buttons have +been pressed, the current brightness level and the orientation quaternion/matrix/euler angles). + +It's still a work-in-progress project since the goal would be to wire such events up into a +compositor to render whole screens virtually depending on your 6-DoF orientation (without position). + +Also keep in mind that this software will only run on Linux including devices like the Steam Deck, +Pinephone or other mobile Linux devices. + +## Dependencies + +You can build the binary using `cmake` and there are only two dependencies for now: + - [libusb](https://libusb.info/) + - [Fusion](https://github.com/xioTechnologies/Fusion) + +Fusion is a sensor fusion library which is integrated as git submodule. So when you checkout the +repository, just update the submodules to get it. The library `libusb` should be pretty common in +most Linux distributions repositories. + +## Build + +The build process should be straight forward: + +``` +mkdir build +cd build +cmake .. +make +``` + +## Run + +It's easiest to run the software with root privileges. But theoretically it should be possible to +adjust rules of your device to not need them for read/write access vis USB. It's definitely not +planned to require that in the future (but for now): + +``` +sudo nrealAirLinuxDriver +``` diff --git a/modules/Fusion b/modules/Fusion new file mode 160000 index 0000000..6c0de47 --- /dev/null +++ b/modules/Fusion @@ -0,0 +1 @@ +Subproject commit 6c0de47eb6da355985c86030e78e552ab588e049 diff --git a/src/device3.c b/src/device3.c new file mode 100644 index 0000000..ba0ab21 --- /dev/null +++ b/src/device3.c @@ -0,0 +1,345 @@ +// +// Created by thejackimonster on 30.03.23. +// +// Copyright (c) 2023 thejackimonster. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#include "device3.h" + +#include +#include +#include + +device3_type* device3_open(device3_event_callback callback) { + device3_type* device = (device3_type*) malloc(sizeof(device3_type)); + + if (!device) { + perror("Not allocated!\n"); + return NULL; + } + + memset(device, 0, sizeof(device3_type)); + device->vendor_id = 0x3318; + device->product_id = 0x0424; + device->callback = callback; + + if (0 != libusb_init(&(device->context))) { + perror("No context!\n"); + return device; + } + + device->handle = libusb_open_device_with_vid_pid( + device->context, + device->vendor_id, + device->product_id + ); + + if (!device->handle) { + perror("No handle!\n"); + return device; + } + + libusb_device* dev = libusb_get_device(device->handle); + if (!dev) { + perror("No dev!\n"); + return device; + } + + struct libusb_device_descriptor desc; + if (0 != libusb_get_device_descriptor(dev, &desc)) { + perror("No desc!\n"); + return device; + } + + struct libusb_config_descriptor* config; + for (uint8_t i = 0; i < desc.bNumConfigurations; i++) { + if (0 != libusb_get_config_descriptor(dev, i, &config)) { + continue; + } + + const struct libusb_interface* interface; + for (uint8_t j = 0; j < config->bNumInterfaces; j++) { + interface = &(config->interface[j]); + + const struct libusb_interface_descriptor* setting; + for (int k = 0; k < interface->num_altsetting; k++) { + setting = &(interface->altsetting[k]); + + if (LIBUSB_CLASS_HID != setting->bInterfaceClass) { + continue; + } + + if (3 != setting->bInterfaceNumber) { + continue; + } + + device->interface_number = setting->bInterfaceNumber; + + if (2 != setting->bNumEndpoints) { + continue; + } + + device->endpoint_address_in = setting->endpoint[0].bEndpointAddress; + device->max_packet_size_in = setting->endpoint[0].wMaxPacketSize; + + device->endpoint_address_out = setting->endpoint[1].bEndpointAddress; + device->max_packet_size_out = setting->endpoint[1].wMaxPacketSize; + } + } + } + + if (3 != device->interface_number) { + perror("No interface!\n"); + return device; + } + + if (1 == libusb_kernel_driver_active(device->handle, device->interface_number)) { + if (0 == libusb_detach_kernel_driver(device->handle, device->interface_number)) { + device->detached = true; + } else { + perror("Not detached!\n"); + return device; + } + } + + if (0 == libusb_claim_interface(device->handle, device->interface_number)) { + device->claimed = true; + } + + if (device->claimed) { + uint8_t initial_imu_payload [9] = { + 0xaa, 0xc5, 0xd1, 0x21, + 0x42, 0x04, 0x00, 0x19, + 0x01 + }; + + int size = device->max_packet_size_out; + if (sizeof(initial_imu_payload) < size) { + size = sizeof(initial_imu_payload); + } + + int transferred = 0; + int error = libusb_bulk_transfer( + device->handle, + device->endpoint_address_out, + initial_imu_payload, + size, + &transferred, + 0 + ); + + if ((0 != error) || (transferred != sizeof(initial_imu_payload))) { + perror("ERROR\n"); + return device; + } + } + + const uint32_t SAMPLE_RATE = 1000; + + FusionOffsetInitialise(&(device->offset), SAMPLE_RATE); + FusionAhrsInitialise(&(device->ahrs)); + + const FusionAhrsSettings settings = { + .gain = 0.5f, + .accelerationRejection = 10.0f, + .magneticRejection = 20.0f, + .rejectionTimeout = 5 * SAMPLE_RATE, /* 5 seconds */ + }; + + FusionAhrsSetSettings(&(device->ahrs), &settings); + return device; +} + +static void device3_callback(device3_type* device, + uint64_t timestamp, + device3_event_type event, + const FusionAhrs* ahrs) { + if (!device->callback) { + return; + } + + device->callback(timestamp, event, ahrs); +} + +static int32_t pack24bit_signed(const uint8_t* data) { + uint32_t unsigned_value = (data[0]) | (data[1] << 8) | (data[2] << 16); + if ((data[2] & 0x80) != 0) unsigned_value |= (0xFF << 24); + return (int32_t) unsigned_value; +} + +// based on 24bit signed int w/ FSR = +/-2000 dps, datasheet option +#define GYRO_SCALAR (1.0f / 8388608.0f * 2000.0f) + +// based on 24bit signed int w/ FSR = +/-16 g, datasheet option +#define ACCEL_SCALAR (1.0f / 8388608.0f * 16.0f) + +// based on 16bit signed int w/ FSR = +/-16 gauss, datasheet option +#define MAGNO_SCALAR (1.0f / 8388608.0f * 16.0f) + +int device3_read(device3_type* device, int timeout) { + if (!device) { + return -1; + } + + if (!device->claimed) { + perror("Not claimed!\n"); + return -2; + } + + if (device->max_packet_size_in != sizeof(device3_packet_type)) { + perror("Not proper size!\n"); + return -3; + } + + device3_packet_type packet; + memset(&packet, 0, sizeof(device3_packet_type)); + + int transferred = 0; + int error = libusb_bulk_transfer( + device->handle, + device->endpoint_address_in, + (uint8_t*) &packet, + device->max_packet_size_in, + &transferred, + timeout + ); + + if (error == LIBUSB_ERROR_TIMEOUT) { + return 1; + } + + if ((0 != error) || (device->max_packet_size_in != transferred)) { + perror("Not expected issue!\n"); + return -4; + } + + const uint64_t timestamp = packet.timestamp; + + if ((packet.signature[0] == 0xaa) && (packet.signature[1] == 0x53)) { + device3_callback(device, timestamp, DEVICE3_EVENT_INIT, &(device->ahrs)); + return 0; + } + + if ((packet.signature[0] != 0x01) || (packet.signature[1] != 0x02)) { + perror("Not matching signature!\n"); + return -5; + } + + const uint64_t delta = timestamp - device->last_timestamp; + const float deltaTime = (float) ((double) delta / 1e9); + + device->last_timestamp = timestamp; + + int32_t vel_x = pack24bit_signed(packet.angular_velocity_x); + int32_t vel_y = pack24bit_signed(packet.angular_velocity_y); + int32_t vel_z = pack24bit_signed(packet.angular_velocity_z); + + FusionVector gyroscope; + gyroscope.axis.x = vel_x * GYRO_SCALAR; + gyroscope.axis.y = vel_y * GYRO_SCALAR; + gyroscope.axis.z = vel_z * GYRO_SCALAR; + + int32_t accel_x = pack24bit_signed(packet.acceleration_x); + int32_t accel_y = pack24bit_signed(packet.acceleration_y); + int32_t accel_z = pack24bit_signed(packet.acceleration_z); + + FusionVector accelerometer; + accelerometer.axis.x = accel_x * ACCEL_SCALAR; + accelerometer.axis.y = accel_y * ACCEL_SCALAR; + accelerometer.axis.z = accel_z * ACCEL_SCALAR; + + int16_t magnet_x = packet.magnetic_x; + int16_t magnet_y = packet.magnetic_y; + int16_t magnet_z = packet.magnetic_z; + + FusionVector magnetometer; + magnetometer.axis.x = magnet_x * MAGNO_SCALAR; + magnetometer.axis.y = magnet_y * MAGNO_SCALAR; + magnetometer.axis.z = magnet_z * MAGNO_SCALAR; + + const FusionMatrix gyroscopeMisalignment = { 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f }; + const FusionVector gyroscopeSensitivity = { 1.0f, 1.0f, 1.0f }; + const FusionVector gyroscopeOffset = { 0.0f, 0.0f, 0.0f }; + const FusionMatrix accelerometerMisalignment = { 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f }; + const FusionVector accelerometerSensitivity = { 1.0f, 1.0f, 1.0f }; + const FusionVector accelerometerOffset = { 0.0f, 0.0f, 0.0f }; + const FusionMatrix magnetometerMisalignment = { 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f }; + const FusionVector magnetometerSensitivity = { 1.0f, 1.0f, 1.0f }; + const FusionVector magnetometerOffset = { 0.0f, 0.0f, 0.0f }; + const FusionMatrix softIronMatrix = { 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f }; + const FusionVector hardIronOffset = { 0.0f, 0.0f, 0.0f }; + + gyroscope = FusionCalibrationInertial( + gyroscope, + gyroscopeMisalignment, + gyroscopeSensitivity, + gyroscopeOffset + ); + + accelerometer = FusionCalibrationInertial( + accelerometer, + accelerometerMisalignment, + accelerometerSensitivity, + accelerometerOffset + ); + + magnetometer = FusionCalibrationInertial( + magnetometer, + magnetometerMisalignment, + magnetometerSensitivity, + magnetometerOffset + ); + + gyroscope = FusionOffsetUpdate(&(device->offset), gyroscope); + + FusionAhrsUpdate(&(device->ahrs), gyroscope, accelerometer, magnetometer, deltaTime); + + device3_callback(device, timestamp, DEVICE3_EVENT_UPDATE, &(device->ahrs)); + return 0; +} + +void device3_close(device3_type* device) { + if (!device) { + return; + } + + if ((device->claimed) && + (0 == libusb_release_interface(device->handle, device->interface_number))) { + device->claimed = false; + } + + if ((device->detached) && + (0 == libusb_attach_kernel_driver(device->handle, device->interface_number))) { + device->detached = false; + } + + if (device->handle) { + libusb_close(device->handle); + device->handle = NULL; + } + + if (device->context) { + libusb_exit(device->context); + device->context = NULL; + } + + free(device); +} diff --git a/src/device3.h b/src/device3.h new file mode 100644 index 0000000..30b5d50 --- /dev/null +++ b/src/device3.h @@ -0,0 +1,98 @@ +#pragma once +// +// Created by thejackimonster on 30.03.23. +// +// Copyright (c) 2023 thejackimonster. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#include +#include + +#include +#include + +struct __attribute__((__packed__)) device3_packet_t { + uint8_t signature [2]; + uint8_t _padding0 [2]; + uint64_t timestamp; + uint8_t _padding1 [6]; + uint8_t angular_velocity_x [3]; + uint8_t angular_velocity_y [3]; + uint8_t angular_velocity_z [3]; + uint8_t _padding2 [6]; + uint8_t acceleration_x [3]; + uint8_t acceleration_y [3]; + uint8_t acceleration_z [3]; + uint8_t _padding3 [6]; + int16_t magnetic_x; + int16_t magnetic_y; + int16_t magnetic_z; + uint32_t checksum; + uint8_t _padding4 [6]; +}; + +enum device3_event_t { + DEVICE3_EVENT_UNKNOWN = 0, + DEVICE3_EVENT_INIT = 1, + DEVICE3_EVENT_UPDATE = 2, +}; + +typedef struct device3_packet_t device3_packet_type; +typedef enum device3_event_t device3_event_type; +typedef void (*device3_event_callback)( + uint64_t timestamp, + device3_event_type event, + const FusionAhrs* ahrs +); + +struct device3_t { + uint16_t vendor_id; + uint16_t product_id; + + libusb_context* context; + libusb_device_handle* handle; + + uint8_t interface_number; + + uint8_t endpoint_address_in; + uint8_t max_packet_size_in; + + uint8_t endpoint_address_out; + uint8_t max_packet_size_out; + + bool detached; + bool claimed; + + uint64_t last_timestamp; + + FusionOffset offset; + FusionAhrs ahrs; + + device3_event_callback callback; +}; + +typedef struct device3_t device3_type; + +device3_type* device3_open(device3_event_callback callback); + +int device3_read(device3_type* device, int timeout); + +void device3_close(device3_type* device); diff --git a/src/device4.c b/src/device4.c new file mode 100644 index 0000000..d6ec687 --- /dev/null +++ b/src/device4.c @@ -0,0 +1,346 @@ +// +// Created by thejackimonster on 29.03.23. +// +// Copyright (c) 2023 thejackimonster. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#include "device4.h" + +#include +#include +#include + +device4_type* device4_open(device4_event_callback callback) { + device4_type* device = (device4_type*) malloc(sizeof(device4_type)); + + if (!device) { + perror("Not allocated!\n"); + return NULL; + } + + memset(device, 0, sizeof(device4_type)); + device->vendor_id = 0x3318; + device->product_id = 0x0424; + device->callback = callback; + + if (0 != libusb_init(&(device->context))) { + perror("No context!\n"); + return device; + } + + device->handle = libusb_open_device_with_vid_pid( + device->context, + device->vendor_id, + device->product_id + ); + + if (!device->handle) { + perror("No handle!\n"); + return device; + } + + libusb_device* dev = libusb_get_device(device->handle); + if (!dev) { + perror("No dev!\n"); + return device; + } + + struct libusb_device_descriptor desc; + if (0 != libusb_get_device_descriptor(dev, &desc)) { + perror("No desc!\n"); + return device; + } + + struct libusb_config_descriptor* config; + for (uint8_t i = 0; i < desc.bNumConfigurations; i++) { + if (0 != libusb_get_config_descriptor(dev, i, &config)) { + continue; + } + + const struct libusb_interface* interface; + for (uint8_t j = 0; j < config->bNumInterfaces; j++) { + interface = &(config->interface[j]); + + const struct libusb_interface_descriptor* setting; + for (int k = 0; k < interface->num_altsetting; k++) { + setting = &(interface->altsetting[k]); + + if (LIBUSB_CLASS_HID != setting->bInterfaceClass) { + continue; + } + + if (4 != setting->bInterfaceNumber) { + continue; + } + + device->interface_number = setting->bInterfaceNumber; + + if (2 != setting->bNumEndpoints) { + continue; + } + + device->endpoint_address_in = setting->endpoint[0].bEndpointAddress; + device->max_packet_size_in = setting->endpoint[0].wMaxPacketSize; + + device->endpoint_address_out = setting->endpoint[1].bEndpointAddress; + device->max_packet_size_out = setting->endpoint[1].wMaxPacketSize; + } + } + } + + if (4 != device->interface_number) { + perror("No interface!\n"); + return device; + } + + if (1 == libusb_kernel_driver_active(device->handle, device->interface_number)) { + if (0 == libusb_detach_kernel_driver(device->handle, device->interface_number)) { + device->detached = true; + } else { + perror("Not detached!\n"); + return device; + } + } + + if (0 == libusb_claim_interface(device->handle, device->interface_number)) { + device->claimed = true; + } + + if (device->claimed) { + uint8_t initial_brightness_payload [16] = { + 0xfd, 0x1e, 0xb9, 0xf0, + 0x68, 0x11, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03 + }; + + int size = device->max_packet_size_out; + if (sizeof(initial_brightness_payload) < size) { + size = sizeof(initial_brightness_payload); + } + + int transferred = 0; + int error = libusb_bulk_transfer( + device->handle, + device->endpoint_address_out, + initial_brightness_payload, + size, + &transferred, + 0 + ); + + if ((0 != error) || (transferred != sizeof(initial_brightness_payload))) { + perror("ERROR\n"); + return device; + } + } + + return device; +} + +static void device4_callback(device4_type* device, + uint32_t timestamp, + device4_event_type event, + uint8_t brightness, + const char* msg) { + if (!device->callback) { + return; + } + + device->callback(timestamp, event, brightness, msg); +} + +int device4_read(device4_type* device, int timeout) { + if (!device) { + return -1; + } + + if (!device->claimed) { + perror("Not claimed!\n"); + return -2; + } + + if (device->max_packet_size_in != sizeof(device4_packet_type)) { + perror("Not proper size!\n"); + return -3; + } + + device4_packet_type packet; + memset(&packet, 0, sizeof(device4_packet_type)); + + int transferred = 0; + int error = libusb_bulk_transfer( + device->handle, + device->endpoint_address_in, + (uint8_t*) &packet, + device->max_packet_size_in, + &transferred, + timeout + ); + + if (error == LIBUSB_ERROR_TIMEOUT) { + return 1; + } + + if ((0 != error) || (device->max_packet_size_in != transferred)) { + perror("Not expected issue!\n"); + return -4; + } + + const uint32_t timestamp = packet.timestamp; + const size_t data_len = (size_t) &(packet.data) - (size_t) &(packet.length); + + switch (packet.action) { + case DEVICE4_ACTION_PASSIVE_POLL_START: { + break; + } + case DEVICE4_ACTION_BRIGHTNESS_COMMAND: { + const uint8_t brightness = packet.data[1]; + + device->brightness = brightness; + + device4_callback( + device, + timestamp, + DEVICE4_EVENT_BRIGHTNESS_SET, + device->brightness, + NULL + ); + break; + } + case DEVICE4_ACTION_MANUAL_POLL_CLICK: { + const uint8_t button = packet.data[0]; + const uint8_t brightness = packet.data[8]; + + switch (button) { + case DEVICE4_BUTTON_DISPLAY_TOGGLE: + device->active = !device->active; + + if (device->active) { + device4_callback( + device, + timestamp, + DEVICE4_EVENT_SCREEN_ON, + device->brightness, + NULL + ); + } else { + device4_callback( + device, + timestamp, + DEVICE4_EVENT_SCREEN_OFF, + device->brightness, + NULL + ); + } + break; + case DEVICE4_BUTTON_BRIGHTNESS_UP: + device->brightness = brightness; + + device4_callback( + device, + timestamp, + DEVICE4_EVENT_BRIGHTNESS_UP, + device->brightness, + NULL + ); + break; + case DEVICE4_BUTTON_BRIGHTNESS_DOWN: + device->brightness = brightness; + + device4_callback( + device, + timestamp, + DEVICE4_EVENT_BRIGHTNESS_DOWN, + device->brightness, + NULL + ); + break; + default: + break; + } + + break; + } + case DEVICE4_ACTION_ACTIVE_POLL: { + const char* text = packet.text; + const size_t text_len = strlen(text); + + device->active = true; + + if (data_len + text_len != packet.length) { + perror("Not matching length!\n"); + return -5; + } + + device4_callback( + device, + timestamp, + DEVICE4_EVENT_MESSAGE, + device->brightness, + text + ); + break; + } + case DEVICE4_ACTION_PASSIVE_POLL_END: { + break; + } + default: + device4_callback( + device, + timestamp, + DEVICE4_EVENT_UNKNOWN, + device->brightness, + NULL + ); + break; + } + + return 0; +} + +void device4_close(device4_type* device) { + if (!device) { + return; + } + + if ((device->claimed) && + (0 == libusb_release_interface(device->handle, device->interface_number))) { + device->claimed = false; + } + + if ((device->detached) && + (0 == libusb_attach_kernel_driver(device->handle, device->interface_number))) { + device->detached = false; + } + + if (device->handle) { + libusb_close(device->handle); + device->handle = NULL; + } + + if (device->context) { + libusb_exit(device->context); + device->context = NULL; + } + + free(device); +} diff --git a/src/device4.h b/src/device4.h new file mode 100644 index 0000000..4b78013 --- /dev/null +++ b/src/device4.h @@ -0,0 +1,104 @@ +#pragma once +// +// Created by thejackimonster on 29.03.23. +// +// Copyright (c) 2023 thejackimonster. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#include +#include + +#include + +#define DEVICE4_ACTION_PASSIVE_POLL_START 0b00010 +#define DEVICE4_ACTION_BRIGHTNESS_COMMAND 0b00011 +#define DEVICE4_ACTION_MANUAL_POLL_CLICK 0b00101 +#define DEVICE4_ACTION_ACTIVE_POLL 0b01001 +#define DEVICE4_ACTION_PASSIVE_POLL_END 0b10010 + +#define DEVICE4_BUTTON_DISPLAY_TOGGLE 0x1 +#define DEVICE4_BUTTON_BRIGHTNESS_UP 0x2 +#define DEVICE4_BUTTON_BRIGHTNESS_DOWN 0x3 + +struct __attribute__((__packed__)) device4_packet_t { + uint8_t signature; + uint32_t checksum; + uint16_t length; + uint8_t _padding0 [4]; + uint32_t timestamp; + uint8_t action; + uint8_t _padding1 [6]; + union { + char text [42]; + uint8_t data [42]; + }; +}; + +enum device4_event_t { + DEVICE4_EVENT_UNKNOWN = 0, + DEVICE4_EVENT_SCREEN_ON = 1, + DEVICE4_EVENT_SCREEN_OFF = 2, + DEVICE4_EVENT_BRIGHTNESS_SET = 3, + DEVICE4_EVENT_BRIGHTNESS_UP = 4, + DEVICE4_EVENT_BRIGHTNESS_DOWN = 5, + DEVICE4_EVENT_MESSAGE = 6, +}; + +typedef struct device4_packet_t device4_packet_type; +typedef enum device4_event_t device4_event_type; +typedef void (*device4_event_callback)( + uint32_t timestamp, + device4_event_type event, + uint8_t brightness, + const char* msg +); + +struct device4_t { + uint16_t vendor_id; + uint16_t product_id; + + libusb_context* context; + libusb_device_handle* handle; + + uint8_t interface_number; + + uint8_t endpoint_address_in; + uint8_t max_packet_size_in; + + uint8_t endpoint_address_out; + uint8_t max_packet_size_out; + + bool detached; + bool claimed; + bool active; + + uint8_t brightness; + + device4_event_callback callback; +}; + +typedef struct device4_t device4_type; + +device4_type* device4_open(device4_event_callback callback); + +int device4_read(device4_type* device, int timeout); + +void device4_close(device4_type* device); diff --git a/src/driver.c b/src/driver.c new file mode 100644 index 0000000..802d4eb --- /dev/null +++ b/src/driver.c @@ -0,0 +1,106 @@ +// +// Created by thejackimonster on 29.03.23. +// +// Copyright (c) 2023 thejackimonster. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#include "device3.h" +#include "device4.h" + +#include +#include +#include + +#include + +void test3(uint64_t timestamp, + device3_event_type event, + const FusionAhrs* ahrs) { + if (event != DEVICE3_EVENT_UPDATE) { + return; + } + + FusionQuaternion q = FusionAhrsGetQuaternion(ahrs); + FusionEuler e = FusionQuaternionToEuler(q); + + printf("Pitch: %f; Roll: %f; Yaw: %f\n", e.angle.pitch, e.angle.roll, e.angle.yaw); +} + +void test4(uint32_t timestamp, + device4_event_type event, + uint8_t brightness, + const char* msg) { + switch (event) { + case DEVICE4_EVENT_MESSAGE: + printf("Message: `%s`\n", msg); + break; + case DEVICE4_EVENT_BRIGHTNESS_SET: + printf("Set Brightness: %u\n", brightness); + break; + case DEVICE4_EVENT_BRIGHTNESS_UP: + printf("Increase Brightness: %u\n", brightness); + break; + case DEVICE4_EVENT_BRIGHTNESS_DOWN: + printf("Decrease Brightness: %u\n", brightness); + break; + default: + break; + } +} + +int main(int argc, const char** argv) { + pid_t pid = fork(); + + if (pid == -1) { + perror("Could not fork!\n"); + return 1; + } + + if (pid == 0) { + device3_type* dev3 = device3_open(test3); + + while (dev3) { + if (device3_read(dev3, 0) < 0) { + break; + } + } + + device3_close(dev3); + return 0; + } else { + device4_type* dev4 = device4_open(test4); + + while (dev4) { + if (device4_read(dev4, 0) < 0) { + break; + } + } + + device4_close(dev4); + + int status = 0; + if (pid != waitpid(pid, &status, 0)) { + return 1; + } + + return status; + } +}