Procházet zdrojové kódy

Merge remote-tracking branch 'origin/master' into device-class-and-flags

TheOnlyJoey před 7 roky
rodič
revize
2eb59afbc2

+ 14 - 0
CMakeLists.txt

@@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 2.8)
 
 project(openhmd C CXX)
 set(CMAKE_C_FLAGS "-std=c99")
+set(CMAKE_POSITION_INDEPENDENT_CODE ON)
 
 set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")
 include_directories(${CMAKE_CURRENT_LIST_DIR}/include)
@@ -31,6 +32,7 @@ OPTION(OPENHMD_DRIVER_OCULUS_RIFT "Oculus Rift DK1 and DK2" ON)
 OPTION(OPENHMD_DRIVER_DEEPOON "Deepoon E2" ON)
 OPTION(OPENHMD_DRIVER_PSVR "Sony PSVR" ON)
 OPTION(OPENHMD_DRIVER_HTC_VIVE "HTC Vive" ON)
+OPTION(OPENHMD_DRIVER_NOLO "NOLO VR CV1" ON)
 OPTION(OPENHMD_DRIVER_EXTERNAL "External sensor driver" ON)
 OPTION(OPENHMD_DRIVER_ANDROID "General Android driver" OFF)
 
@@ -87,6 +89,18 @@ if(OPENHMD_DRIVER_HTC_VIVE)
 	set(LIBS ${LIBS} ${HIDAPI_LIBRARIES})
 endif(OPENHMD_DRIVER_HTC_VIVE)
 
+if(OPENHMD_DRIVER_NOLO)
+	set(openhmd_source_files ${openhmd_source_files}
+	${CMAKE_CURRENT_LIST_DIR}/src/drv_nolo/nolo.c
+	${CMAKE_CURRENT_LIST_DIR}/src/drv_nolo/packet.c
+	)
+	add_definitions(-DDRIVER_NOLO)
+
+	find_package(HIDAPI REQUIRED)
+	include_directories(${HIDAPI_INCLUDE_DIRS})
+	set(LIBS ${LIBS} ${HIDAPI_LIBRARIES})
+endif(OPENHMD_DRIVER_NOLO)
+
 if (OPENHMD_DRIVER_EXTERNAL)
 	set(openhmd_source_files ${openhmd_source_files}
 	${CMAKE_CURRENT_LIST_DIR}/src/drv_external/external.c

+ 5 - 14
README.md

@@ -5,12 +5,7 @@ This project aims to provide a Free and Open Source API and drivers for immersiv
 OpenHMD is released under the permissive Boost Software License (see LICENSE for more information), to make sure it can be linked and distributed with both free and non-free software. While it doesn't require contribution from the users, it is still very appreciated.
 
 ## Supported Devices
-  * Oculus Rift DK1, DK2 and CV1 (rotation only)
-  * Deepoon E2
-  * HTC Vive (rotation only)
-  * PSVR
-  * Android based devices
-  * External Sensor (passthrough for external sensors)
+For a full list of supported devices please check https://github.com/OpenHMD/OpenHMD/wiki/Support-List
 
 ## Supported Platforms
   * Linux
@@ -59,12 +54,8 @@ These can be enabled or disabled adding -DDRIVER_OF_CHOICE=ON after the cmake co
 ### Configuring udev on Linux
 To avoid having to run your applications as root to access USB devices you have to add a udev rule (this will be included in .deb packages, etc).
 
-as root, run:
-
-    echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="2833", MODE="0666", GROUP="plugdev"' > /etc/udev/rules.d/83-hmd.rules
-    echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="0bb4", MODE="0666", GROUP="plugdev"' >> /etc/udev/rules.d/83-hmd.rules
-    echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="28de", MODE="0666", GROUP="plugdev"' >> /etc/udev/rules.d/83-hmd.rules
-    udevadm control --reload-rules
+A full list of known usb devices and instructions on how to add them can be found on:
+https://github.com/OpenHMD/OpenHMD/wiki/Udev-rules-list
 
 After this you have to unplug your device and plug it back in. You should now be able to access the HMD as a normal user.
 
@@ -99,9 +90,9 @@ If you're linking statically with OpenHMD using windows/mingw you have to make s
 Note that this is *only* if you're linking statically! If you're using the DLL then you *must not* define OHMD_STATIC. (If you're not sure then you're probably linking dynamically and won't have to worry about this).
 
 ## Pre-built packages
-Will be available soon.
+A list of pre-built backages can be found on http://www.openhmd.net/index.php/download/
 
 ## Using OpenHMD
-See the examples/ subdirectory for usage examples. The OpenGL example is not built by default, to build it use the --enable-openglexample option for the configure script. It requires SDL, glew and OpenGL.
+See the examples/ subdirectory for usage examples. The OpenGL example is not built by default, to build it use the --enable-openglexample option for the configure script. It requires SDL2, glew and OpenGL.
 
 An API reference can be generated using doxygen and is also available here: http://openhmd.net/doxygen/0.1.0/openhmd_8h.html

+ 15 - 1
configure.ac

@@ -16,6 +16,7 @@ PKG_PROG_PKG_CONFIG([0.24])
 m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
 
 hidapi="hidapi"
+AC_SUBST(hidapi)
 
 AC_SUBST(PKG_CONFIG_EXTRA_PATH, "")
 AC_SUBST(EXTRA_LD_FLAGS, "")
@@ -70,6 +71,15 @@ AC_ARG_ENABLE([driver-psvr],
 
 AM_CONDITIONAL([BUILD_DRIVER_PSVR], [test "x$driver_psvr_enabled" != "xno"])
 
+# NOLO
+AC_ARG_ENABLE([driver-nolo],
+        [AS_HELP_STRING([--disable-driver-nolo],
+                [disable building of NOLO VR driver [default=yes]])],
+        [driver_nolo_enabled=$enableval],
+        [driver_nolo_enabled='yes'])
+
+AM_CONDITIONAL([BUILD_DRIVER_NOLO], [test "x$driver_nolo_enabled" != "xno"])
+
 # External Driver
 AC_ARG_ENABLE([driver-external],
         [AS_HELP_STRING([--disable-driver-external],
@@ -104,6 +114,10 @@ AS_IF([test "x$driver_deepoon_enabled" != "xno"],
 AS_IF([test "x$driver_psvr_enabled" != "xno"],
 	[PKG_CHECK_MODULES([hidapi], [$hidapi] >= 0.0.5)])
 
+# Libs required by NOLO VR Driver
+AS_IF([test "x$driver_nolo_enabled" != "xno"],
+	[PKG_CHECK_MODULES([hidapi], [$hidapi] >= 0.0.5)])
+
 # Do we build OpenGL example?
 AC_ARG_ENABLE([openglexample],
         [AS_HELP_STRING([--enable-openglexample],
@@ -145,5 +159,5 @@ AC_PROG_CC
 AC_PROG_CC_C99
 
 AC_CONFIG_HEADERS([config.h])
-AC_CONFIG_FILES([Makefile src/Makefile tests/Makefile tests/unittests/Makefile examples/Makefile examples/opengl/Makefile examples/simple/Makefile])
+AC_CONFIG_FILES([Makefile src/Makefile tests/Makefile tests/unittests/Makefile examples/Makefile examples/opengl/Makefile examples/simple/Makefile pkg-config/openhmd.pc])
 AC_OUTPUT 

+ 2 - 2
pkg-config/openhmd.pc

@@ -5,7 +5,7 @@ includedir=${prefix}/include/openhmd
 Name: openhmd
 Description: API and drivers for immersive technology devices such as HMDs
 Version: 0.0.1
-Requires: hidapi-libusb
+Requires: @hidapi@
 Conflicts:
-Libs: -L${libdir} -lopenhmd
+Libs: -L${libdir} -lopenhmd -l@hidapi@
 Cflags: -I${includedir}

+ 11 - 0
src/Makefile.am

@@ -61,6 +61,17 @@ libopenhmd_la_LDFLAGS += $(hidapi_LIBS)
 
 endif
 
+if BUILD_DRIVER_NOLO
+
+libopenhmd_la_SOURCES += \
+	drv_nolo/nolo.c \
+	drv_nolo/packet.c
+
+libopenhmd_la_CPPFLAGS += $(hidapi_CFLAGS) -DDRIVER_NOLO
+libopenhmd_la_LDFLAGS += $(hidapi_LIBS)
+
+endif
+
 if BUILD_DRIVER_EXTERNAL
 
 libopenhmd_la_SOURCES += \

+ 13 - 11
src/drv_deepoon/deepoon.c

@@ -290,21 +290,23 @@ static void get_device_list(ohmd_driver* driver, ohmd_device_list* list)
 	struct hid_device_info* cur_dev = devs;
 
 	while (cur_dev) {
-		ohmd_device_desc* desc = &list->devices[list->num_devices++];
+		// This is needed because DeePoon share USB IDs with the Nolo.
+		if (wcscmp(cur_dev->manufacturer_string, L"DeePoon VR, Inc.")==0 &&
+			wcscmp(cur_dev->product_string, L"DeePoon Tracker Device")==0) {
 
-		strcpy(desc->driver, "Deepoon Driver");
-		strcpy(desc->vendor, "Deepoon");
-		strcpy(desc->product, "Deepoon E2");
+			ohmd_device_desc* desc = &list->devices[list->num_devices++];
 
-		desc->revision = 0;
+			strcpy(desc->driver, "Deepoon Driver");
+			strcpy(desc->vendor, "Deepoon");
+			strcpy(desc->product, "Deepoon E2");
 
-		strcpy(desc->path, cur_dev->path);
-
-		desc->driver_ptr = driver;
-	
-		desc->device_class = OHMD_DEVICE_CLASS_HMD;
-		desc->device_flags = OHMD_DEVICE_FLAGS_ROTATIONAL_TRACKING;
+			desc->device_class = OHMD_DEVICE_CLASS_HMD;
+			desc->device_flags = OHMD_DEVICE_FLAGS_ROTATIONAL_TRACKING;
 
+			desc->revision = 0;
+			strcpy(desc->path, cur_dev->path);
+			desc->driver_ptr = driver;
+		}
 		cur_dev = cur_dev->next;
 	}
 

+ 2 - 0
src/drv_htc_vive/vive.c

@@ -349,6 +349,8 @@ static ohmd_device* open_device(ohmd_driver* driver, ohmd_device_desc* desc)
 	//printf("Result: %s\n", packet_buffer);
 	vive_decode_config_packet(&priv->vive_config, packet_buffer, offset);
 
+	free(packet_buffer);
+
 	// Set default device properties
 	ohmd_set_default_device_properties(&priv->base.properties);
 

+ 327 - 0
src/drv_nolo/nolo.c

@@ -0,0 +1,327 @@
+/*
+ * OpenHMD - Free and Open Source API and drivers for immersive technology.
+ * Copyright (C) 2013 Fredrik Hultin.
+ * Copyright (C) 2013 Jakob Bornecrantz.
+ * Copyright (C) 2017 Joey Ferwerda.
+ * Distributed under the Boost 1.0 licence, see LICENSE for full text.
+ *
+ * Original implementation by: Yann Vernier.
+ */
+
+/* NOLO VR- HID/USB Driver Implementation */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+
+#include "nolo.h"
+
+#define NOLO_ID					0x0483 //ST microcontroller
+#define NOLO_HMD				0x5750
+
+static const int controllerLength = 3 + (3+4)*2 + 2 + 2 + 1;
+static devices_t* nolo_devices;
+
+static drv_priv* drv_priv_get(ohmd_device* device)
+{
+	return (drv_priv*)device;
+}
+
+static int send_feature_report(drv_priv* priv, const unsigned char *data, size_t length)
+{
+	return hid_send_feature_report(priv->handle, data, length);
+}
+
+static void update_device(ohmd_device* device)
+{
+	drv_priv* priv = drv_priv_get(device);
+	unsigned char buffer[FEATURE_BUFFER_SIZE];
+
+	// Only update when physical device
+	if (priv->id != 0)
+		return;
+
+	devices_t* current = nolo_devices;
+	drv_priv* controller0 = NULL;
+	drv_priv* controller1 = NULL;
+
+	//Check if controllers exist
+	while (current != NULL) {
+		if (current->drv->hmd_tracker == priv)
+		{
+			if (current->drv->controller0)
+				controller0 = current->drv->controller0;
+			if (current->drv->controller1)
+				controller1 = current->drv->controller1;
+			break;
+		}
+		current = current->next;
+	}
+
+	// Read all the messages from the device.
+	while(true){
+		int size = hid_read(priv->handle, buffer, FEATURE_BUFFER_SIZE);
+		if(size < 0){
+			LOGE("error reading from device");
+			return;
+		} else if(size == 0) {
+			return; // No more messages, return.
+		}
+
+		nolo_decrypt_data(buffer);
+
+		// currently the only message type the hardware supports
+		switch (buffer[0]) {
+			case 0xa5:  // Controllers packet
+			{
+				if (controller0)
+					nolo_decode_controller(controller0, buffer+1);
+				if (controller1)
+					nolo_decode_controller(controller1, buffer+64-controllerLength);
+			break;
+			}
+			case 0xa6: // HMD packet
+				nolo_decode_hmd_marker(priv, buffer+0x15);
+				nolo_decode_base_station(priv, buffer+0x36);
+			break;
+			default:
+				LOGE("unknown message type: %u", buffer[0]);
+		}
+	}
+
+
+	return;
+}
+
+static int getf(ohmd_device* device, ohmd_float_value type, float* out)
+{
+	drv_priv* priv = drv_priv_get(device);
+
+	switch(type){
+
+	case OHMD_ROTATION_QUAT: {
+			*(quatf*)out = priv->base.rotation;
+			break;
+		}
+
+	case OHMD_POSITION_VECTOR:
+		if(priv->id == 0) {
+			// HMD
+			*(vec3f*)out = priv->base.position;
+		}
+		else if(priv->id == 1) {
+			// Controller 0
+			*(vec3f*)out = priv->base.position;
+		}
+		else if(priv->id == 2) {
+			// Controller 1
+			*(vec3f*)out = priv->base.position;
+		}
+		break;
+
+	default:
+		ohmd_set_error(priv->base.ctx, "invalid type given to getf (%ud)", type);
+		return -1;
+		break;
+	}
+
+	return 0;
+}
+
+static void close_device(ohmd_device* device)
+{
+	LOGD("closing device");
+	drv_priv* priv = drv_priv_get(device);
+	hid_close(priv->handle);
+	free(priv);
+}
+
+static char* _hid_to_unix_path(char* path)
+{
+	const int len = 16;
+	char bus [16];
+	char dev [16];
+	char *result = malloc( sizeof(char) * ( 20 + 1 ) );
+
+	sprintf (bus, "%.*s\n", len, path);
+	sprintf (dev, "%.*s\n", len, path + 5);
+
+	sprintf (result, "/dev/bus/usb/%03d/%03d",
+		(int)strtol(bus, NULL, 16),
+		(int)strtol(dev, NULL, 16));
+	return result;
+}
+
+
+void push_device(devices_t * head, drv_nolo* val) {
+	devices_t* current = head;
+
+	if (!nolo_devices)
+	{
+		nolo_devices = calloc(1, sizeof(devices_t));
+		nolo_devices->drv = val;
+		nolo_devices->next = NULL;
+		return;
+	}
+
+	while (current->next != NULL) {
+		current = current->next;
+	}
+
+	/* now we can add a new variable */
+	current->next = calloc(1, sizeof(devices_t));
+	current->next->drv = val;
+	current->next->next = NULL;
+}
+
+static ohmd_device* open_device(ohmd_driver* driver, ohmd_device_desc* desc)
+{
+	drv_priv* priv = ohmd_alloc(driver->ctx, sizeof(drv_priv));
+	if(!priv)
+		goto cleanup;
+
+	priv->id = desc->id;
+	priv->base.ctx = driver->ctx;
+
+	// Open the HID device when physical device
+	if (priv->id == 0)
+	{
+		priv->handle = hid_open_path(desc->path);
+
+		if(!priv->handle) {
+			char* path = _hid_to_unix_path(desc->path);
+			ohmd_set_error(driver->ctx, "Could not open %s. "
+										"Check your rights.", path);
+			free(path);
+			goto cleanup;
+		}
+
+		if(hid_set_nonblocking(priv->handle, 1) == -1){
+			ohmd_set_error(driver->ctx, "failed to set non-blocking on device");
+			goto cleanup;
+		}
+
+	}
+
+	devices_t* current = nolo_devices;
+	drv_nolo* mNOLO = NULL;
+
+	//Check if the opened device is part of a group
+	while (current != NULL) {
+		if (strcmp(current->drv->path, desc->path)==0)
+			mNOLO = current->drv;
+		current = current->next;
+	}
+
+	if (!mNOLO)
+	{
+		//Create new group
+		mNOLO = calloc(1, sizeof(drv_nolo));
+		mNOLO->hmd_tracker = NULL;
+		mNOLO->controller0 = NULL;
+		mNOLO->controller1 = NULL;
+		strcpy(mNOLO->path, desc->path);
+		push_device(nolo_devices, mNOLO);
+	}
+
+	if (priv->id == 0) {
+		mNOLO->hmd_tracker = priv;
+	}
+	else if (priv->id == 1) {
+		mNOLO->controller0 = priv;
+		priv->base.properties.digital_button_count = 6;
+	}
+	else if (priv->id == 2) {
+		mNOLO->controller1 = priv;
+		priv->base.properties.digital_button_count = 6;
+	}
+
+	// Set default device properties
+	ohmd_set_default_device_properties(&priv->base.properties);
+
+	// set up device callbacks
+	priv->base.update = update_device;
+	priv->base.close = close_device;
+	priv->base.getf = getf;
+
+	return &priv->base;
+
+cleanup:
+	if(priv)
+		free(priv);
+
+	return NULL;
+}
+
+static void get_device_list(ohmd_driver* driver, ohmd_device_list* list)
+{
+	struct hid_device_info* devs = hid_enumerate(NOLO_ID, NOLO_HMD);
+	struct hid_device_info* cur_dev = devs;
+
+	int id = 0;
+	while (cur_dev) {
+		if (wcscmp(cur_dev->manufacturer_string, L"LYRobotix")==0 &&
+			wcscmp(cur_dev->product_string, L"NOLO")==0) {
+			ohmd_device_desc* desc = &list->devices[list->num_devices++];
+
+			strcpy(desc->driver, "OpenHMD NOLO VR CV1 driver");
+			strcpy(desc->vendor, "LYRobotix");
+			strcpy(desc->product, "NOLO CV1");
+
+			desc->revision = 0;
+
+			strcpy(desc->path, cur_dev->path);
+
+			desc->driver_ptr = driver;
+			desc->id = id++;
+
+			//Controller 0
+			desc = &list->devices[list->num_devices++];
+
+			strcpy(desc->driver, "OpenHMD NOLO VR CV1 driver");
+			strcpy(desc->vendor, "LYRobotix");
+			strcpy(desc->product, "NOLO CV1: Controller 0");
+
+			strcpy(desc->path, cur_dev->path);
+
+			desc->driver_ptr = driver;
+			desc->id = id++;
+
+			// Controller 1
+			desc = &list->devices[list->num_devices++];
+
+			strcpy(desc->driver, "OpenHMD NOLO VR CV1 driver");
+			strcpy(desc->vendor, "LYRobotix");
+			strcpy(desc->product, "NOLO CV1: Controller 1");
+
+			strcpy(desc->path, cur_dev->path);
+
+			desc->driver_ptr = driver;
+			desc->id = id++;
+		}
+		cur_dev = cur_dev->next;
+	}
+	hid_free_enumeration(devs);
+}
+
+static void destroy_driver(ohmd_driver* drv)
+{
+	LOGD("shutting down NOLO CV1 driver");
+	hid_exit();
+	free(drv);
+}
+
+ohmd_driver* ohmd_create_nolo_drv(ohmd_context* ctx)
+{
+	ohmd_driver* drv = ohmd_alloc(ctx, sizeof(ohmd_driver));
+	if(drv == NULL)
+		return NULL;
+
+	drv->get_device_list = get_device_list;
+	drv->open_device = open_device;
+	drv->destroy = destroy_driver;
+	drv->ctx = ctx;
+
+	return drv;
+}

+ 47 - 0
src/drv_nolo/nolo.h

@@ -0,0 +1,47 @@
+/*
+ * OpenHMD - Free and Open Source API and drivers for immersive technology.
+ * Copyright (C) 2017 Joey Ferwerda.
+ * Distributed under the Boost 1.0 licence, see LICENSE for full text.
+ */
+
+/* NOLO VR - Internal Interface */
+
+#ifndef NOLODRIVER_H
+#define NOLODRIVER_H
+
+#include "../openhmdi.h"
+#include <hidapi.h>
+
+#define FEATURE_BUFFER_SIZE 64
+
+typedef struct {
+	ohmd_device base;
+
+	hid_device* handle;
+	int id;
+	uint8_t button_state;
+} drv_priv;
+
+typedef struct{
+	char path[OHMD_STR_SIZE];
+	drv_priv* hmd_tracker;
+	drv_priv* controller0;
+	drv_priv* controller1;
+} drv_nolo;
+
+typedef struct devices{
+	drv_nolo* drv;
+	struct devices * next;
+} devices_t;
+
+void btea_decrypt(uint32_t *v, int n, int base_rounds, uint32_t const key[4]);
+void nolo_decrypt_data(unsigned char* buf);
+
+void nolo_decode_base_station(drv_priv* priv, unsigned char* data);
+void nolo_decode_hmd_marker(drv_priv* priv, unsigned char* data);
+void nolo_decode_controller(drv_priv* priv, unsigned char* data);
+void nolo_decode_orientation(const unsigned char* data, quatf* quat);
+void nolo_decode_position(const unsigned char* data, vec3f* pos);
+
+#endif
+

+ 169 - 0
src/drv_nolo/packet.c

@@ -0,0 +1,169 @@
+/*
+ * OpenHMD - Free and Open Source API and drivers for immersive technology.
+ * Copyright (C) 2017 Joey Ferwerda.
+ * Distributed under the Boost 1.0 licence, see LICENSE for full text.
+ */
+
+/* NOLO VR - Packet Decoding and Utilities */
+
+#include <stdio.h>
+#include "nolo.h"
+
+#define DELTA 0x9e3779b9
+#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))
+
+void btea_decrypt(uint32_t *v, int n, int base_rounds, uint32_t const key[4])
+{
+	uint32_t y, z, sum;
+	unsigned p, rounds, e;
+
+	/* Decoding Part */
+	rounds = base_rounds + 52/n;
+	sum = rounds*DELTA;
+	y = v[0];
+
+	do {
+		e = (sum >> 2) & 3;
+		for (p=n-1; p>0; p--) {
+			z = v[p-1];
+			y = v[p] -= MX;
+		}
+
+		z = v[n-1];
+		y = v[0] -= MX;
+		sum -= DELTA;
+	} while (--rounds);
+}
+
+void nolo_decrypt_data(unsigned char* buf)
+{
+	const int cryptwords = (64-4)/4, cryptoffset = 1;
+	static const uint32_t key[4] = {0x875bcc51, 0xa7637a66, 0x50960967, 0xf8536c51};
+	uint32_t cryptpart[cryptwords];
+
+	// Decrypt encrypted portion
+	for (int i = 0; i < cryptwords; i++) {
+	cryptpart[i] =
+		((uint32_t)buf[cryptoffset+4*i  ]) << 0  |
+		((uint32_t)buf[cryptoffset+4*i+1]) << 8  |
+		((uint32_t)buf[cryptoffset+4*i+2]) << 16 |
+		((uint32_t)buf[cryptoffset+4*i+3]) << 24;
+	}
+
+	btea_decrypt(cryptpart, cryptwords, 1, key);
+
+	for (int i = 0; i < cryptwords; i++) {
+		buf[cryptoffset+4*i  ] = cryptpart[i] >> 0;
+		buf[cryptoffset+4*i+1] = cryptpart[i] >> 8;
+		buf[cryptoffset+4*i+2] = cryptpart[i] >> 16;
+		buf[cryptoffset+4*i+3] = cryptpart[i] >> 24;
+	}
+}
+
+void nolo_decode_position(const unsigned char* data, vec3f* pos)
+{
+	const double scale = 0.0001f;
+
+	pos->x = (scale * (int16_t)(data[0]<<8 | data[1]));
+	pos->y = (scale * (int16_t)(data[2]<<8 | data[3]));
+	pos->z = (scale *          (data[4]<<8 | data[5]));
+}
+
+void nolo_decode_orientation(const unsigned char* data, quatf* quat)
+{
+	double w,i,j,k, scale;
+	// CV1 order
+	w = (int16_t)(data[0]<<8 | data[1]);
+	i = (int16_t)(data[2]<<8 | data[3]);
+	j = (int16_t)(data[4]<<8 | data[5]);
+	k = (int16_t)(data[6]<<8 | data[7]);
+	// Normalize (unknown if scale is constant)
+	//scale = 1.0/sqrt(i*i+j*j+k*k+w*w);
+	// Turns out it is fixed point. But the android driver author
+	// either didn't know, or didn't trust it.
+	// Unknown if normalizing it helps
+	scale = 1.0 / 16384;
+	//std::cout << "Scale: " << scale << std::endl;
+	w *= scale;
+	i *= scale;
+	j *= scale;
+	k *= scale;
+
+	// Reorder
+	quat->w = w;
+	quat->x = i;
+	quat->y = k;
+	quat->z = -j;
+}
+
+void nolo_decode_controller(drv_priv* priv, unsigned char* data)
+{
+	uint8_t bit, newbuttonstate;
+
+	if (data[0] != 2 || data[1] != 1) {
+	// Unknown version
+	/* Happens when controllers aren't on.
+	std::cout << "Nolo: Unknown controller "
+	  << " version " << (int)data[0] << " " << (int)data[1]
+	  << std::endl;
+	*/
+		return;
+	}
+
+	vec3f position;
+	quatf orientation;
+
+	nolo_decode_position(data+3, &position);
+	nolo_decode_orientation(data+3+3*2, &orientation);
+
+	//Change button state
+	newbuttonstate = data[3+3*2+4*2];
+	if (priv->button_state != newbuttonstate)
+	{
+		for (bit=0; bit<6; bit++)
+		{
+			if ((priv->button_state & 1<<bit) != (newbuttonstate & 1<<bit))
+			{
+				ohmd_digital_input_event event = { /* button index */ bit, /* button state */ (newbuttonstate & 1<<bit ? OHMD_BUTTON_DOWN : OHMD_BUTTON_UP) };
+				ohmdq_push(priv->base.digital_input_event_queue, &event);
+			}
+		}
+		priv->button_state = newbuttonstate;
+	}
+
+	priv->base.position = position;
+	priv->base.rotation = orientation;
+}
+
+void nolo_decode_hmd_marker(drv_priv* priv, unsigned char* data)
+{
+	if (data[0] != 2 || data[1] != 1) {
+		/* Happens with corrupt packages (mixed with controller data)
+		std::cout << "Nolo: Unknown headset marker"
+		  << " version " << (int)data[0] << " " << (int)data[1]
+		  << std::endl;
+		*/
+		// Unknown version
+		return;
+	}
+
+	vec3f homepos;
+	vec3f position;
+	quatf orientation;
+
+	nolo_decode_position(data+3, &position);
+	nolo_decode_position(data+3+3*2, &homepos);
+	nolo_decode_orientation(data+3+2*3*2+1, &orientation);
+
+	// Tracker viewer kept using the home for head.
+	// Something wrong with how they handle the descriptors.
+	priv->base.position = position;
+	priv->base.rotation = orientation;
+}
+
+void nolo_decode_base_station(drv_priv* priv, unsigned char* data)
+{
+	// Unknown version
+	if (data[0] != 2 || data[1] != 1)
+		return;
+}

+ 9 - 3
src/openhmd.c

@@ -42,6 +42,10 @@ ohmd_context* OHMD_APIENTRY ohmd_ctx_create(void)
 	ctx->drivers[ctx->num_drivers++] = ohmd_create_psvr_drv(ctx);
 #endif
 
+#if DRIVER_NOLO
+	ctx->drivers[ctx->num_drivers++] = ohmd_create_nolo_drv(ctx);
+#endif
+
 #if DRIVER_EXTERNAL
 	ctx->drivers[ctx->num_drivers++] = ohmd_create_external_drv(ctx);
 #endif
@@ -195,8 +199,10 @@ ohmd_device* OHMD_APIENTRY ohmd_list_open_device_s(ohmd_context* ctx, int index,
 		ohmd_driver* driver = (ohmd_driver*)desc->driver_ptr;
 		ohmd_device* device = driver->open_device(driver, desc);
 
-		if (device == NULL)
+		if (device == NULL) {
+			ohmd_unlock_mutex(ctx->update_mutex);
 			return NULL;
+		}
 
 		device->rotation_correction.w = 1;
 
@@ -269,7 +275,7 @@ static int ohmd_device_getf_unp(ohmd_device* device, ohmd_float_value type, floa
 			rot = tmp;
 			mat4x4f orient, world_shift, result;
 			omat4x4f_init_look_at(&orient, &rot, &point);
-			omat4x4f_init_translate(&world_shift, +(device->properties.ipd / 2.0f), 0, 0);
+			omat4x4f_init_translate(&world_shift, -device->position.x +(device->properties.ipd / 2.0f), -device->position.y, -device->position.z);
 			omat4x4f_mult(&world_shift, &orient, &result);
 			omat4x4f_transpose(&result, (mat4x4f*)out);
 			return OHMD_S_OK;
@@ -280,7 +286,7 @@ static int ohmd_device_getf_unp(ohmd_device* device, ohmd_float_value type, floa
 			oquatf_mult_me(&rot, &device->rotation_correction);
 			mat4x4f orient, world_shift, result;
 			omat4x4f_init_look_at(&orient, &rot, &point);
-			omat4x4f_init_translate(&world_shift, -(device->properties.ipd / 2.0f), 0, 0);
+			omat4x4f_init_translate(&world_shift, -device->position.x + -(device->properties.ipd / 2.0f), -device->position.y, -device->position.z);
 			omat4x4f_mult(&world_shift, &orient, &result);
 			omat4x4f_transpose(&result, (mat4x4f*)out);
 			return OHMD_S_OK;

+ 1 - 0
src/openhmdi.h

@@ -146,6 +146,7 @@ ohmd_driver* ohmd_create_oculus_rift_drv(ohmd_context* ctx);
 ohmd_driver* ohmd_create_deepoon_drv(ohmd_context* ctx);
 ohmd_driver* ohmd_create_htc_vive_drv(ohmd_context* ctx);
 ohmd_driver* ohmd_create_psvr_drv(ohmd_context* ctx);
+ohmd_driver* ohmd_create_nolo_drv(ohmd_context* ctx);
 ohmd_driver* ohmd_create_external_drv(ohmd_context* ctx);
 ohmd_driver* ohmd_create_android_drv(ohmd_context* ctx);