Initial commit

Signed-off-by: TheJackiMonster <thejackimonster@gmail.com>
This commit is contained in:
TheJackiMonster 2023-03-31 15:11:41 +02:00
commit b694a15cb1
No known key found for this signature in database
GPG key ID: D850A5F772E880F9
11 changed files with 1115 additions and 0 deletions

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
.idea/
cmake-build-debug/
cmake-build-release/
build/

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "modules/Fusion"]
path = modules/Fusion
url = https://github.com/xioTechnologies/Fusion.git

23
CMakeLists.txt Normal file
View file

@ -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
)

21
LICENSE.md Normal file
View file

@ -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.

62
README.md Normal file
View file

@ -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
```

1
modules/Fusion Submodule

@ -0,0 +1 @@
Subproject commit 6c0de47eb6da355985c86030e78e552ab588e049

345
src/device3.c Normal file
View file

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
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);
}

98
src/device3.h Normal file
View file

@ -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 <stdbool.h>
#include <stdint.h>
#include <Fusion/Fusion.h>
#include <libusb-1.0/libusb.h>
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);

346
src/device4.c Normal file
View file

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
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);
}

104
src/device4.h Normal file
View file

@ -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 <stdbool.h>
#include <stdint.h>
#include <libusb-1.0/libusb.h>
#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);

106
src/driver.c Normal file
View file

@ -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 <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <Fusion/Fusion.h>
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;
}
}