Initial commit
Signed-off-by: TheJackiMonster <thejackimonster@gmail.com>
This commit is contained in:
commit
b694a15cb1
11 changed files with 1115 additions and 0 deletions
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
.idea/
|
||||
|
||||
cmake-build-debug/
|
||||
cmake-build-release/
|
||||
|
||||
build/
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
[submodule "modules/Fusion"]
|
||||
path = modules/Fusion
|
||||
url = https://github.com/xioTechnologies/Fusion.git
|
23
CMakeLists.txt
Normal file
23
CMakeLists.txt
Normal 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
21
LICENSE.md
Normal 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
62
README.md
Normal 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
1
modules/Fusion
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 6c0de47eb6da355985c86030e78e552ab588e049
|
345
src/device3.c
Normal file
345
src/device3.c
Normal 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
98
src/device3.h
Normal 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
346
src/device4.c
Normal 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
104
src/device4.h
Normal 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
106
src/driver.c
Normal 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;
|
||||
}
|
||||
}
|
Reference in a new issue