Skip to content

Protocol

D.J. Figueroa edited this page Jan 14, 2021 · 2 revisions

Formal documentation will come soon (tm), in the mean time, here is some sample code to demonstrate how this early and subject to change protocol works.

#include <iostream>
#include <thread>
#include <string.h>
#define NOMINMAX
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <math.h>

int main() {
	// Initialize WinSock
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
		std::cerr << "WinSock not initialized";
		return -1;
	}

	char SERVER_ADDRESS[] = "127.0.0.1";
	int SERVER_PORT = 8082;


	SOCKET s = INVALID_SOCKET;
	struct sockaddr_in serv_addr;
	
	s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s == INVALID_SOCKET) {
		std::cerr << "Could not open socket" << std::endl;
		return -1;
	}

	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(SERVER_PORT);
	if (inet_pton(AF_INET, SERVER_ADDRESS, &serv_addr.sin_addr) <= 0) {
		std::cerr << "Invalid address" << std::endl;
	}
	

	// The "handshake"
	// There actually isn't any real communication going on here (yet), it's just the device
	// letting the driver know that it exists, which will create the OpenVR reference device
	// and display a globe icon in the SteamVR devices panel.

	char handshake[] = { 0x00 };
	sendto(s, handshake, sizeof(handshake), 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

	// Advertising
	// This is where the client intializes tracked devices. The format is as follows:
	// 0x01 Advertise Byte	
	//
	// for each tracker:
	// char TRACKER_ID			A number between 1-127 that the client will refer to the tracker by
	// STRING TRACKER_SERIAL	        Serial string for the device
	// 0x00					String terminator

	struct Tracker {
		char id;
		std::string serial;
		bool isVisible;
	};

	Tracker t;
	t.id = 1;
	t.serial = "Sample Device";
	t.isVisible = true;
	int advertise_length = t.serial.length() + 3;
	char* advertise = new char[advertise_length];
	advertise[0] = 0x01;
	advertise[1] = t.id;
	for (int i = 0; i < t.serial.length(); i++) {
		advertise[2 + i] = t.serial[i];
	}
	advertise[advertise_length - 1] = 0;

	sendto(s, advertise, advertise_length, 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

	// Update
	// This is where the client sends positional and rotational data for trackers. The format is as follows:
	// 0x03 Update Byte	
	//
	// for each tracker:
	// char TRACKER_ID			A number between 1-127 of a tracker already advertised. 
	//					If the tracker is visible, send the id as a positive number.
	//					If it is not visible, send the id as a negative number. 		
	//					(ex: visible tracker id = 1, not visible tracker id = -1)
        //
	// NOTE: ONLY SEND TVEC AND RVEC IF THE TRACKER IS VISIBLE! If it is not visible, the driver will start processing the next byte as a new tracker to be updated.					
	// double[3] rvec			A rotation vector represented as a Rodrigues rotation vector https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula. 
	//					This was inspired by OpenCV's rotation vectors, which the application of this driver is using.
	// double[3] tvec			A position vector (x,y,z)

	while (true) {
		char update[1 + 1 + sizeof(double[6])] = { 0 };
		update[0] = 0x03;
		update[1] = t.isVisible ? t.id : -t.id;
		double rvec[3] = { 0 };
		double now = std::chrono::system_clock::now().time_since_epoch().count();
		double y = sin(now / 2000000); // some arbitrary division to make a sine wave
		double tvec[3] = { 0, y, 0 };
		memcpy(&update[2], rvec, sizeof(rvec));
		memcpy(&update[2 + sizeof(double[3])], tvec, sizeof(tvec));
		sendto(s, update, sizeof(update), 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
		std::this_thread::sleep_for(std::chrono::milliseconds(17)); // ~ 60 Hz update rate
	}	

	return 0;
}

Clone this wiki locally