mirror of
https://gitlab.freedesktop.org/monado/monado.git
synced 2025-01-23 07:01:55 +00:00
495eecb65f
Find and capture input from cameras, and split according to frame type. Send long exposure tracking frames through the AEG module, and SLAM and hand tracking trackers. Add controller emulated hand devices. The native "fisheye62" camera distortion model is dynamically converted to OpenCV Kannala-Brandt parameters using a TinyCeres solver.
302 lines
9 KiB
C
302 lines
9 KiB
C
/*
|
|
* Copyright 2020 Jan Schmidt
|
|
* SPDX-License-Identifier: BSL-1.0
|
|
*
|
|
* OpenHMD - Free and Open Source API and drivers for immersive technology.
|
|
*/
|
|
/*!
|
|
* @file
|
|
* @brief Oculus Rift S HMD Radio management
|
|
*
|
|
* Functions for serialising requests to communicate with
|
|
* Touch controllers over the HMDs wireless radio link,
|
|
* collecting responses and delivering them back via callbacks.
|
|
*
|
|
* Ported from OpenHMD
|
|
*
|
|
* @author Jan Schmidt <jan@centricular.com>
|
|
*/
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
|
|
#include "util/u_misc.h"
|
|
#include "rift_s.h"
|
|
#include "rift_s_radio.h"
|
|
#include "rift_s_protocol.h"
|
|
|
|
/* Struct that forms a double linked queue of pending commands,
|
|
* with the head being the currently active command */
|
|
struct rift_s_radio_command
|
|
{
|
|
rift_s_radio_command *prev;
|
|
rift_s_radio_command *next;
|
|
|
|
/* Request packet data */
|
|
rift_s_hmd_radio_command_t read_command;
|
|
|
|
/* Completion callback */
|
|
rift_s_radio_completion_fn cb;
|
|
void *cb_data;
|
|
};
|
|
|
|
static int
|
|
get_radio_response_report(struct os_hid_device *hid, rift_s_hmd_radio_response_t *radio_response)
|
|
{
|
|
int ret;
|
|
|
|
radio_response->cmd = 0xb;
|
|
ret = os_hid_get_feature(hid, radio_response->cmd, (uint8_t *)(radio_response),
|
|
sizeof(rift_s_hmd_radio_response_t));
|
|
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
rift_s_radio_update(rift_s_radio_state *state, struct os_hid_device *hid)
|
|
{
|
|
bool read_another = false;
|
|
|
|
do {
|
|
/* Send a radio command if there is none active and some pending */
|
|
if (state->command_result_pending == false && state->pending_commands) {
|
|
rift_s_radio_command *cmd = state->pending_commands;
|
|
rift_s_hmd_radio_command_t *pkt = &cmd->read_command;
|
|
|
|
pkt->cmd = 0x12;
|
|
os_hid_set_feature(hid, (uint8_t *)pkt, sizeof(*pkt));
|
|
// rift_s_hexdump_buffer ("ControllerFWSend", (unsigned char *)(pkt), sizeof(*pkt));
|
|
state->command_result_pending = true;
|
|
}
|
|
|
|
if (!state->command_result_pending)
|
|
break; /* Nothing to do right now */
|
|
|
|
/* There's a command result pending, poll the radio response until it's complete */
|
|
rift_s_hmd_radio_response_t radio_response;
|
|
|
|
/* The radio response is ready when the busy flag has cleared, and the seqnum
|
|
* has incremented */
|
|
int ret = get_radio_response_report(hid, &radio_response);
|
|
|
|
if (ret < 2) {
|
|
break; /* HID read failed - bail */
|
|
}
|
|
|
|
if (radio_response.busy_flag != 0x00 || radio_response.seqnum == state->last_radio_seqnum) {
|
|
if (radio_response.busy_flag)
|
|
state->last_radio_seqnum = radio_response.seqnum;
|
|
return;
|
|
}
|
|
state->last_radio_seqnum = radio_response.seqnum;
|
|
|
|
/* We have the controller response! */
|
|
assert(ret > 3 && ret <= (int)sizeof(radio_response));
|
|
|
|
state->command_result_pending = false;
|
|
// rift_s_hexdump_buffer ("ControllerFWReply", (unsigned char *)(&radio_response), ret);
|
|
|
|
assert(state->pending_commands != NULL);
|
|
|
|
rift_s_radio_command *cmd = state->pending_commands;
|
|
|
|
/* Pop the head off the cmds queue, because it's complete now */
|
|
state->pending_commands = cmd->next;
|
|
if (state->pending_commands == NULL)
|
|
state->pending_commands_tail = NULL;
|
|
else
|
|
state->pending_commands->prev = NULL;
|
|
|
|
/* Call the completion callback */
|
|
if (cmd->cb)
|
|
cmd->cb(true, radio_response.response_bytes, ret - 3, cmd->cb_data);
|
|
free(cmd);
|
|
read_another = true;
|
|
|
|
} while (read_another);
|
|
}
|
|
|
|
void
|
|
rift_s_radio_state_init(rift_s_radio_state *state)
|
|
{
|
|
state->command_result_pending = false;
|
|
state->pending_commands = NULL;
|
|
state->pending_commands_tail = NULL;
|
|
state->last_radio_seqnum = -1;
|
|
}
|
|
|
|
void
|
|
rift_s_radio_state_clear(rift_s_radio_state *state)
|
|
{
|
|
/* Clear and free any pending commands. */
|
|
rift_s_radio_command *head = state->pending_commands;
|
|
while (head != NULL) {
|
|
rift_s_radio_command *prev = head;
|
|
head = prev->next;
|
|
|
|
if (prev->cb)
|
|
prev->cb(false, NULL, 0, prev->cb_data);
|
|
free(prev);
|
|
}
|
|
|
|
state->pending_commands = state->pending_commands_tail = NULL;
|
|
}
|
|
|
|
void
|
|
rift_s_radio_queue_command(rift_s_radio_state *state,
|
|
const uint64_t device_id,
|
|
const uint8_t *cmd_bytes,
|
|
const int cmd_bytes_len,
|
|
rift_s_radio_completion_fn cb,
|
|
void *cb_data)
|
|
{
|
|
rift_s_radio_command *cmd = U_TYPED_CALLOC(rift_s_radio_command);
|
|
|
|
assert(cmd_bytes_len <= (int)sizeof(cmd->read_command.cmd_bytes));
|
|
|
|
cmd->read_command.device_id = device_id;
|
|
memcpy(cmd->read_command.cmd_bytes, cmd_bytes, cmd_bytes_len);
|
|
cmd->cb = cb;
|
|
cmd->cb_data = cb_data;
|
|
|
|
/* Append to the pending commands queue. The command itself will be sent by the update() function
|
|
* when possible */
|
|
if (state->pending_commands_tail == NULL) {
|
|
assert(state->pending_commands == NULL);
|
|
state->pending_commands = state->pending_commands_tail = cmd;
|
|
} else {
|
|
state->pending_commands_tail->next = cmd;
|
|
cmd->prev = state->pending_commands_tail;
|
|
state->pending_commands_tail = cmd;
|
|
}
|
|
}
|
|
|
|
/* The current blocks are ~2-3KB, so this should be enough to read
|
|
* the JSON config: */
|
|
#define MAX_JSON_LEN 4096
|
|
|
|
typedef struct rift_s_radio_json_read_state
|
|
{
|
|
rift_s_radio_state *state;
|
|
uint64_t device_id;
|
|
rift_s_radio_completion_fn cb;
|
|
void *cb_data;
|
|
|
|
uint32_t cur_offset;
|
|
uint16_t block_len; /* Expected length, from the header */
|
|
|
|
uint8_t data[MAX_JSON_LEN + 1];
|
|
uint16_t data_len;
|
|
|
|
} rift_s_radio_json_read_state;
|
|
|
|
static void
|
|
read_json_cb(bool success, uint8_t *response_bytes, int response_bytes_len, rift_s_radio_json_read_state *json_read)
|
|
{
|
|
if (!success) {
|
|
/* Failed, report to the caller */
|
|
goto fail;
|
|
}
|
|
|
|
if (response_bytes_len < 5) {
|
|
RIFT_S_WARN("Not enough bytes in radio response - needed 5, got %d\n", response_bytes_len);
|
|
goto fail;
|
|
}
|
|
|
|
uint8_t reply_len = response_bytes[4];
|
|
response_bytes += 5;
|
|
|
|
if (json_read->cur_offset == 0) {
|
|
/* Read the header 0u32 0x20 01 00 62 09 7b 22 67 79 | .{..... ..b.{"gy
|
|
72 6f 5f 6d 22 3a 5b 2d 30 2e 30 31 34 33 35 36 | ro_m":[-0.014356
|
|
38 38 36 36 2c 2d 30 2e | 8866,-0.
|
|
= len 0x20, 0001 (file type, or sequence number?), 0x0962 = file length */
|
|
if (reply_len < 4) {
|
|
RIFT_S_WARN("Not enough bytes in remote configuration header - needed 4, got %d\n", reply_len);
|
|
goto fail; /* Not enough bytes in header */
|
|
}
|
|
|
|
uint16_t file_type = (response_bytes[1] << 8) | response_bytes[0];
|
|
uint16_t block_len = (response_bytes[3] << 8) | response_bytes[2];
|
|
|
|
if (file_type != 1) {
|
|
RIFT_S_WARN("Unknown file type in remote configuration header - expected 1, got %d\n",
|
|
file_type);
|
|
goto fail;
|
|
}
|
|
/* Assert if the MAX_JSON_LEN ever needs expanding */
|
|
assert(block_len <= MAX_JSON_LEN);
|
|
if (block_len > MAX_JSON_LEN) {
|
|
RIFT_S_WARN(
|
|
"Remote configuration block too long. Please expand the read buffer (needed %u bytes)\n",
|
|
block_len);
|
|
goto fail;
|
|
}
|
|
|
|
json_read->block_len = block_len;
|
|
json_read->cur_offset = 0x4;
|
|
} else {
|
|
uint16_t data_remain = json_read->block_len - json_read->data_len;
|
|
|
|
if (reply_len > data_remain)
|
|
reply_len = data_remain; /* Truncate any over-read */
|
|
|
|
/* Append these bytes to the buffer */
|
|
memcpy(json_read->data + json_read->data_len, response_bytes, reply_len);
|
|
json_read->data_len += reply_len;
|
|
}
|
|
|
|
/* If there is no more to read, then report success to the caller and return */
|
|
if (json_read->data_len >= json_read->block_len) {
|
|
json_read->data[json_read->data_len] = 0;
|
|
|
|
if (json_read->cb)
|
|
json_read->cb(true, json_read->data, json_read->data_len, json_read->cb_data);
|
|
free(json_read);
|
|
return;
|
|
}
|
|
|
|
/* Otherwise request more data */
|
|
uint8_t read_len = MIN(0x20, json_read->block_len - json_read->data_len);
|
|
uint8_t read_cmd[] = {0x2b, 0x20, 0xe8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00};
|
|
|
|
read_cmd[4] = json_read->cur_offset;
|
|
read_cmd[5] = json_read->cur_offset >> 8;
|
|
read_cmd[6] = json_read->cur_offset >> 16;
|
|
read_cmd[7] = json_read->cur_offset >> 24;
|
|
read_cmd[8] = read_len;
|
|
|
|
rift_s_radio_queue_command(json_read->state, json_read->device_id, read_cmd, sizeof(read_cmd),
|
|
(rift_s_radio_completion_fn)read_json_cb, json_read);
|
|
|
|
json_read->cur_offset += read_len;
|
|
return;
|
|
|
|
fail:
|
|
if (json_read->cb)
|
|
json_read->cb(success, json_read->data, json_read->data_len, json_read->cb_data);
|
|
free(json_read);
|
|
return;
|
|
}
|
|
|
|
void
|
|
rift_s_radio_get_json_block(rift_s_radio_state *state,
|
|
const uint64_t device_id,
|
|
rift_s_radio_completion_fn cb,
|
|
void *cb_data)
|
|
{
|
|
/* Configuration JSON block reading */
|
|
rift_s_radio_json_read_state *json_read = U_TYPED_CALLOC(rift_s_radio_json_read_state);
|
|
/* cmd = 0x2b reply_buffer_len = 0x20 timeout(?) = 0x3e8 (=1000) offset = 0u32 len = 0x20 */
|
|
const uint8_t read_cmd[] = {0x2b, 0x20, 0xe8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00};
|
|
|
|
json_read->state = state;
|
|
json_read->device_id = device_id;
|
|
json_read->cb = cb;
|
|
json_read->cb_data = cb_data;
|
|
|
|
rift_s_radio_queue_command(state, device_id, read_cmd, sizeof(read_cmd),
|
|
(rift_s_radio_completion_fn)read_json_cb, json_read);
|
|
}
|