mirror of
https://github.com/collabora/libsurvive.git
synced 2026-08-04 00:51:21 -04:00
858 lines
29 KiB
C
858 lines
29 KiB
C
//
|
|
#include "survive_internal.h"
|
|
#include <assert.h>
|
|
#include <math.h> /* for sqrt */
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define DEBUG_TB(...) \
|
|
SV_VERBOSE(((Global_Disambiguator_data_t *)d->so->ctx->disambiguator_data)->verbosity, __VA_ARGS__)
|
|
//#define DEBUG_TB(...)
|
|
/**
|
|
* The lighthouses go in the following order:
|
|
*
|
|
* Ticks State
|
|
* 0 ACode 0b1x0 (4) <--- B
|
|
* 20 000 ACode 0b0x0 (0) <--- A/c
|
|
* LH A X Sweep
|
|
* 400 000 ACode 0b1x1 (5) <--- B
|
|
* 420 000 ACode 0b0x1 (1) <--- A/c
|
|
* LH A Y SWEEP
|
|
* 800 000 ACode 0b0x0 (0) <--- B
|
|
* 820 000 ACode 0b1x0 (4) <--- A/c
|
|
* LH B X Sweep
|
|
* 1 200 000 ACode 0b0x1 (1) <--- B
|
|
* 1 220 000 ACode 0b1x1 (5) <--- A/c
|
|
* LH B Y SWEEP
|
|
* 1 600 000 < REPEAT >
|
|
*
|
|
* NOTE: Obviously you cut the data bit out for this
|
|
*
|
|
* This disambiguator works by finding where in that order it is, and tracking along with it.
|
|
* It is able to maintain this tracking for extended periods of time without further data
|
|
* by knowing the modulo of the start of the cycle and calculating appropriatly although this
|
|
* will run into issues when the timestamp rolls over or we simply drift off in accuracy.
|
|
*
|
|
* Neither case is terminal though; it will just have to find the modulo again which only takes
|
|
* a handful of pulses.
|
|
*
|
|
* The main advantage to this scheme is that its reasonably fast and is able to deal with being
|
|
* close enough to the lighthouse that the lengths are in a valid sync pulse range.
|
|
*/
|
|
|
|
// Every pulse_window seems roughly 20k ticks long. That leaves ~360 to the capture window
|
|
#define PULSE_WINDOW 20000
|
|
#define CAPTURE_WINDOW 360000
|
|
|
|
enum LighthouseState {
|
|
LS_UNKNOWN = 0,
|
|
|
|
LS_WaitLHA_ACode4 = 1,
|
|
LS_WaitLHA_ACode0,
|
|
LS_SweepAX,
|
|
LS_WaitLHA_ACode5,
|
|
LS_WaitLHA_ACode1,
|
|
LS_SweepAY,
|
|
LS_WaitLHB_ACode0,
|
|
LS_WaitLHB_ACode4,
|
|
LS_SweepBX,
|
|
LS_WaitLHB_ACode1,
|
|
LS_WaitLHB_ACode5,
|
|
LS_SweepBY,
|
|
|
|
LS_END
|
|
};
|
|
|
|
static const char *LighthouseStateName(enum LighthouseState s) {
|
|
#define CASE(x) \
|
|
case x: \
|
|
return #x;
|
|
switch (s) {
|
|
CASE(LS_UNKNOWN);
|
|
CASE(LS_WaitLHA_ACode4);
|
|
CASE(LS_WaitLHA_ACode0);
|
|
CASE(LS_SweepAX);
|
|
CASE(LS_WaitLHA_ACode5);
|
|
CASE(LS_WaitLHA_ACode1);
|
|
CASE(LS_SweepAY);
|
|
CASE(LS_WaitLHB_ACode0);
|
|
CASE(LS_WaitLHB_ACode4);
|
|
CASE(LS_SweepBX);
|
|
CASE(LS_WaitLHB_ACode1);
|
|
CASE(LS_WaitLHB_ACode5);
|
|
CASE(LS_SweepBY);
|
|
CASE(LS_END);
|
|
}
|
|
return "<UNKNOWN>";
|
|
}
|
|
|
|
typedef struct {
|
|
int acode, lh, axis, window;
|
|
bool is_sweep;
|
|
} LighthouseStateParameters;
|
|
|
|
// clang-format off
|
|
const LighthouseStateParameters LS_Params[LS_END + 1] = {
|
|
{.lh = -1, .axis = -1, .window = 0},
|
|
|
|
{.acode = 4, .lh = 1, .axis = 0, .window = PULSE_WINDOW}, // 0
|
|
{.acode = 0, .lh = 0, .axis = 0, .window = PULSE_WINDOW}, // 20000
|
|
{.acode = 4, .lh = 0, .axis = 0, .window = CAPTURE_WINDOW, .is_sweep = 1}, // 40000
|
|
|
|
{.acode = 5, .lh = 1, .axis = 1, .window = PULSE_WINDOW}, // 400000
|
|
{.acode = 1, .lh = 0, .axis = 1, .window = PULSE_WINDOW}, // 420000
|
|
{.acode = 1, .lh = 0, .axis = 1, .window = CAPTURE_WINDOW, .is_sweep = 1}, // 440000
|
|
|
|
// In 60hz single LH mode, it just repeats the above. With any other configuration, the second half of the table is used.
|
|
{.acode = 0, .lh = 1, .axis = 0, .window = PULSE_WINDOW}, // 800000
|
|
{.acode = 4, .lh = 0, .axis = 0, .window = PULSE_WINDOW}, // 820000
|
|
{.acode = 4, .lh = 1, .axis = 0, .window = CAPTURE_WINDOW, .is_sweep = 1}, // 840000
|
|
|
|
{.acode = 1, .lh = 1, .axis = 1, .window = PULSE_WINDOW}, // 1200000
|
|
{.acode = 5, .lh = 0, .axis = 1, .window = PULSE_WINDOW}, // 1220000
|
|
{.acode = 5, .lh = 1, .axis = 1, .window = CAPTURE_WINDOW, .is_sweep = 1}, // 1240000
|
|
|
|
{.lh = -1, .axis = -1, .window = 0} // 1600000
|
|
};
|
|
// clang-format on
|
|
|
|
#define ACODE_TIMING(acode) \
|
|
((3000 + ((acode)&1) * 500 + (((acode) >> 1) & 1) * 1000 + (((acode) >> 2) & 1) * 2000) - 250)
|
|
#define ACODE(s, d, a) ((s << 2) | (d << 1) | a)
|
|
|
|
static inline int LSParam_acode(enum LighthouseState s) { return LS_Params[s].acode; }
|
|
|
|
static int LSParam_offset_for_state(enum LighthouseState s) {
|
|
static int offsets[LS_END + 1] = {-1};
|
|
if (offsets[0] == -1) {
|
|
int offset = 0;
|
|
for (int i = 0; i < LS_END + 1; i++) {
|
|
offsets[i] = offset;
|
|
offset += LS_Params[i].window;
|
|
}
|
|
}
|
|
return offsets[s];
|
|
}
|
|
|
|
static enum LighthouseState LighthouseState_findByOffset(int offset, int *error) {
|
|
for (int i = 2; i < LS_END + 1; i++) {
|
|
if (LSParam_offset_for_state(i) > offset) {
|
|
int offset_from_last = LSParam_offset_for_state(i - 1);
|
|
int offset_from_this = LSParam_offset_for_state(i);
|
|
|
|
int dist_from_last = offset - offset_from_last;
|
|
int dist_from_this = offset_from_this - offset;
|
|
|
|
bool this_is_closest = dist_from_last > dist_from_this;
|
|
if (LS_Params[i - 1].is_sweep && dist_from_this > 1000) {
|
|
this_is_closest = false;
|
|
}
|
|
|
|
if (error) {
|
|
*error = this_is_closest ? dist_from_this : dist_from_last;
|
|
}
|
|
return this_is_closest ? i : i - 1;
|
|
}
|
|
}
|
|
assert(false);
|
|
return -1;
|
|
}
|
|
|
|
typedef struct {
|
|
SurviveContext *ctx;
|
|
|
|
bool single_60hz_mode;
|
|
int light_min_length;
|
|
int verbosity;
|
|
} Global_Disambiguator_data_t;
|
|
|
|
STRUCT_CONFIG_SECTION(Global_Disambiguator_data_t)
|
|
STRUCT_CONFIG_ITEM("light-min-length", "Minimum length of V1 light to accept.", 100, t->light_min_length);
|
|
STRUCT_CONFIG_ITEM("disambiguator-verbosity", "Verbosity of disambiguator", 1000, t->verbosity);
|
|
END_STRUCT_CONFIG_SECTION(Global_Disambiguator_data_t)
|
|
|
|
typedef struct {
|
|
SurviveObject *so;
|
|
/* Keep running average of sync signals as they come in */
|
|
uint32_t last_timestamp;
|
|
uint64_t last_sync_timestamp;
|
|
uint64_t last_sync_length;
|
|
int last_sync_count;
|
|
|
|
uint32_t first_sync_timestamp;
|
|
uint32_t longest_sync_length;
|
|
|
|
struct {
|
|
uint32_t sync_count[2];
|
|
uint32_t drop_syncs[2];
|
|
|
|
uint32_t sweep_hit_count;
|
|
uint32_t drop_sweeps;
|
|
|
|
uint32_t confidence_resets;
|
|
uint32_t sync_time_error;
|
|
} stats;
|
|
|
|
/** This part of the structure is general use when we know our state */
|
|
enum LighthouseState state;
|
|
|
|
// We track offset for both lighthouses seperately
|
|
uint32_t mod_offset[NUM_GEN1_LIGHTHOUSES];
|
|
int confidence;
|
|
|
|
/** This rest of the structure is dedicated to finding a state when we are unknown */
|
|
int stabalize;
|
|
int failures;
|
|
bool lastWasSync;
|
|
|
|
#define SYNC_HISTORY_LEN 12
|
|
LightcapElement sync_history[SYNC_HISTORY_LEN];
|
|
int sync_offset;
|
|
|
|
LightcapElement sweep_data[];
|
|
} Disambiguator_data_t;
|
|
|
|
static int find_acode(uint32_t pulseLen) {
|
|
const static int offset = 50;
|
|
if (pulseLen < 2500 + offset)
|
|
return -1;
|
|
|
|
if (pulseLen < 3000 + offset)
|
|
return 0;
|
|
if (pulseLen < 3500 + offset)
|
|
return 1;
|
|
if (pulseLen < 4000 + offset)
|
|
return 2;
|
|
if (pulseLen < 4500 + offset)
|
|
return 3;
|
|
if (pulseLen < 5000 + offset)
|
|
return 4;
|
|
if (pulseLen < 5500 + offset)
|
|
return 5;
|
|
if (pulseLen < 6000 + offset)
|
|
return 6;
|
|
if (pulseLen < 6500 + offset)
|
|
return 7;
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int32_t overlap_area(const LightcapElement *a, const LightcapElement *b) {
|
|
if (a->timestamp > b->timestamp)
|
|
return overlap_area(b, a);
|
|
|
|
// a_start must be <= than b_start here
|
|
uint32_t a_end = a->timestamp + a->length;
|
|
|
|
uint32_t b_start = b->timestamp;
|
|
uint32_t b_end = b->timestamp + b->length;
|
|
|
|
uint32_t c_start = 0, c_end = 0;
|
|
|
|
if (a_end >= b_start) {
|
|
c_start = b->timestamp;
|
|
c_end = b_end > a_end ? a_end : b_end;
|
|
}
|
|
|
|
return c_end - c_start;
|
|
}
|
|
static bool overlaps(const LightcapElement *a, const LightcapElement *b) {
|
|
int overlap = overlap_area(a, b);
|
|
return overlap > a->length / 2;
|
|
}
|
|
|
|
const int SKIP_BIT = 4;
|
|
const int DATA_BIT = 2;
|
|
const int AXIS_BIT = 1;
|
|
|
|
#define LOWER_SYNC_TIME 2250
|
|
#define UPPER_SYNC_TIME 6750
|
|
|
|
#define DIV_ROUND_CLOSEST(n, d) ((((n) < 0) ^ ((d) < 0)) ? (((n) - (d) / 2) / (d)) : (((n) + (d) / 2) / (d)))
|
|
|
|
LightcapElement get_last_sync(Disambiguator_data_t *d) {
|
|
if (d->last_sync_count == 0) {
|
|
return (LightcapElement){0};
|
|
}
|
|
LightcapElement lastSync = {
|
|
.timestamp = d->first_sync_timestamp, .length = d->longest_sync_length, .sensor_id = -d->last_sync_count};
|
|
return lastSync;
|
|
}
|
|
|
|
enum LightcapClassification { LCC_SWEEP, LCC_SYNC };
|
|
static enum LightcapClassification naive_classify(Disambiguator_data_t *d, const LightcapElement *le) {
|
|
bool clearlyNotSync = le->length < LOWER_SYNC_TIME || le->length > UPPER_SYNC_TIME;
|
|
|
|
if (clearlyNotSync) {
|
|
return LCC_SWEEP;
|
|
} else {
|
|
return LCC_SYNC;
|
|
}
|
|
}
|
|
|
|
static uint32_t SolveForMod_Offset(Disambiguator_data_t *d, enum LighthouseState state, const LightcapElement *le) {
|
|
assert(LS_Params[state].is_sweep == 0); // Doesn't work for sweep data
|
|
SurviveContext *ctx = d->so->ctx;
|
|
DEBUG_TB("Solve for mod %d (%u - %u) = %u", state, le->timestamp, LSParam_offset_for_state(state),
|
|
(le->timestamp - LSParam_offset_for_state(state)));
|
|
|
|
return (le->timestamp - LSParam_offset_for_state(state));
|
|
}
|
|
|
|
static enum LighthouseState SetState(Disambiguator_data_t *d, const LightcapElement *le,
|
|
enum LighthouseState new_state);
|
|
|
|
static enum LighthouseState EndSweep(Disambiguator_data_t *d, const LightcapElement *le) { return LS_UNKNOWN; }
|
|
|
|
static void AddSyncHistory(Disambiguator_data_t *d, LightcapElement sync) {
|
|
if (sync.length) {
|
|
d->sync_history[d->sync_offset++] = sync;
|
|
if (d->sync_offset >= SYNC_HISTORY_LEN)
|
|
d->sync_offset = 0;
|
|
}
|
|
}
|
|
|
|
static Disambiguator_data_t *get_best_latest_state(Global_Disambiguator_data_t *g) {
|
|
int max_confidence = 0;
|
|
Disambiguator_data_t *best_d = 0;
|
|
for (int i = 0; i < g->ctx->objs_ct; i++) {
|
|
Disambiguator_data_t *d = g->ctx->objs[i]->disambiguator_data;
|
|
if (d && d->state != LS_UNKNOWN && max_confidence < d->confidence) {
|
|
best_d = d;
|
|
max_confidence = d->confidence;
|
|
}
|
|
}
|
|
|
|
return best_d;
|
|
}
|
|
|
|
static uint32_t calculate_error(int target_acode, const LightcapElement *le) {
|
|
// Calculate what it would be with and without data
|
|
uint32_t time_error_d0 = abs(ACODE_TIMING(target_acode) - le->length);
|
|
uint32_t time_error_d1 = abs(ACODE_TIMING(target_acode | DATA_BIT) - le->length);
|
|
|
|
// Take the least of the two erors
|
|
return (time_error_d0) > (time_error_d1) ? time_error_d1 : time_error_d0;
|
|
}
|
|
|
|
#define DEBUG_LOCK DEBUG_TB
|
|
static uint32_t apply_mod_offset(uint32_t timestamp, uint32_t mod_offset, enum LighthouseState end_state) {
|
|
int mod_group = LSParam_offset_for_state(end_state);
|
|
if (timestamp > mod_offset)
|
|
return (timestamp - mod_offset) % mod_group;
|
|
|
|
// Indicates mod_offset was from _before_ a 32bit rollover
|
|
if (mod_offset - timestamp > 0xFFFFFFFF / 2) {
|
|
return (0xFFFFFFFF - mod_offset + timestamp) % mod_group;
|
|
}
|
|
|
|
timestamp = timestamp % mod_group;
|
|
mod_offset = mod_offset % mod_group;
|
|
|
|
int rtn = ((int32_t)timestamp - (int32_t)mod_offset) % mod_group;
|
|
if (rtn < 0)
|
|
rtn += mod_group;
|
|
return rtn;
|
|
}
|
|
|
|
static int find_inliers(Disambiguator_data_t *d, uint32_t guess_mod, bool test60hz) {
|
|
int inliers = 0;
|
|
SurviveContext *ctx = d->so->ctx;
|
|
for (int i = 0; i < SYNC_HISTORY_LEN && d->sync_history[i].length > 0; i++) {
|
|
const LightcapElement *le = &d->sync_history[i];
|
|
|
|
int end_of_mod = test60hz ? LS_WaitLHB_ACode0 : LS_END;
|
|
int le_offset = apply_mod_offset(le->timestamp, guess_mod, end_of_mod);
|
|
|
|
int offset_error;
|
|
enum LighthouseState this_state = LighthouseState_findByOffset(le_offset, &offset_error);
|
|
|
|
int best_acode = find_acode(le->length) & ~2;
|
|
int acode = LSParam_acode(this_state);
|
|
uint32_t error = calculate_error(acode, le);
|
|
|
|
int last_idx = i == 0 ? (SYNC_HISTORY_LEN - 1) : i - 1;
|
|
int32_t time_diff = (le->timestamp - d->sync_history[last_idx].timestamp);
|
|
|
|
DEBUG_LOCK("--%2d %10u %10u(%10d) %4u (%2d) %d(%d)(%d) \t %2d %6u %6u %6u %6d", i, le_offset, le->timestamp,
|
|
time_diff, le->length, le->sensor_id, acode, best_acode, LS_Params[this_state].lh, this_state,
|
|
ACODE_TIMING(acode), ACODE_TIMING(acode | DATA_BIT), error, offset_error);
|
|
|
|
if (LS_Params[this_state].is_sweep)
|
|
continue;
|
|
|
|
if (LS_Params[this_state].lh && test60hz)
|
|
continue;
|
|
|
|
if (error < 500 && offset_error < 500) {
|
|
inliers++;
|
|
}
|
|
}
|
|
return inliers;
|
|
}
|
|
|
|
static enum LighthouseState find_relative_offset(Disambiguator_data_t *d, uint32_t *mod, bool *single_60hz) {
|
|
SurviveContext *ctx = d->so->ctx;
|
|
Global_Disambiguator_data_t *g = d->so->ctx->disambiguator_data;
|
|
Disambiguator_data_t *best_d = get_best_latest_state(g);
|
|
|
|
int ri = (d->sync_offset + (SYNC_HISTORY_LEN - 1)) % SYNC_HISTORY_LEN;
|
|
LightcapElement *re = d->sync_history + ri;
|
|
int acode = find_acode(re->length) & 0x5;
|
|
|
|
DEBUG_LOCK("Starting search... %s %d %d", survive_colorize(d->so->codename), ri, acode);
|
|
for (enum LighthouseState guess = LS_UNKNOWN + 1; guess != LS_END; guess++) {
|
|
const LighthouseStateParameters *params = &LS_Params[guess];
|
|
// if (LSParam_acode(guess) == acode && !params->is_sweep) {
|
|
if (!params->is_sweep) {
|
|
uint32_t guess_mod = SolveForMod_Offset(d, guess, re);
|
|
DEBUG_LOCK("%10u %4u %d %u %u %d", re->timestamp, re->length, acode & 0x5, guess_mod,
|
|
re->timestamp - guess_mod, guess);
|
|
|
|
for (int test60hz = 0; test60hz < ((guess >= LS_WaitLHB_ACode0) ? 1 : 2); test60hz++) {
|
|
// We are already locked on one device; so we know if its 60hz mode or not
|
|
if (best_d && test60hz != g->single_60hz_mode)
|
|
continue;
|
|
|
|
int inliers = find_inliers(d, guess_mod, test60hz);
|
|
DEBUG_LOCK("With 60hz -- %d %d", test60hz, inliers);
|
|
if (inliers > SYNC_HISTORY_LEN - 1) {
|
|
*mod = guess_mod;
|
|
*single_60hz = test60hz == 1;
|
|
return guess;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return LS_UNKNOWN;
|
|
}
|
|
|
|
static enum LighthouseState EndSync(Disambiguator_data_t *d, const LightcapElement *le) {
|
|
LightcapElement lastSync = get_last_sync(d);
|
|
Global_Disambiguator_data_t *g = d->so->ctx->disambiguator_data;
|
|
|
|
AddSyncHistory(d, lastSync);
|
|
|
|
uint32_t mod = 0;
|
|
bool is60hz;
|
|
enum LighthouseState new_state = find_relative_offset(d, &mod, &is60hz);
|
|
if (new_state != LS_UNKNOWN) {
|
|
d->mod_offset[0] = d->mod_offset[1] = mod;
|
|
g->single_60hz_mode = is60hz;
|
|
if (g->single_60hz_mode) {
|
|
SurviveContext *ctx = d->so->ctx;
|
|
SV_INFO("Disambiguator is in 60hz mode (mode A)");
|
|
}
|
|
return new_state;
|
|
} else {
|
|
return LS_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
static void RegisterSync(Disambiguator_data_t *d, const LightcapElement *le) {
|
|
if (le->timestamp < d->first_sync_timestamp || d->longest_sync_length == 0)
|
|
d->first_sync_timestamp = le->timestamp;
|
|
|
|
if (le->length > d->longest_sync_length) {
|
|
d->longest_sync_length = le->length;
|
|
}
|
|
|
|
d->last_sync_timestamp += le->timestamp;
|
|
d->last_sync_length += le->length;
|
|
d->last_sync_count++;
|
|
}
|
|
|
|
static void ResetSync(Disambiguator_data_t *d) {
|
|
d->first_sync_timestamp = d->longest_sync_length = 0;
|
|
d->last_sync_timestamp = d->last_sync_length = d->last_sync_count = 0;
|
|
}
|
|
|
|
static enum LighthouseState AttemptFindState(Disambiguator_data_t *d, const LightcapElement *le) {
|
|
/*
|
|
enum LighthouseState best_guess = get_best_latest_state(d->so->ctx->disambiguator_data);
|
|
if(best_guess != LS_UNKNOWN) {
|
|
SurviveContext* ctx = d->so->ctx;
|
|
SV_INFO("Disambiguator solving state by stealing other trackers state");
|
|
d->mod_offset = SolveForMod_Offset(d, best_guess, le);
|
|
return best_guess;
|
|
}
|
|
*/
|
|
|
|
enum LightcapClassification classification = naive_classify(d, le);
|
|
|
|
if (classification == LCC_SYNC) {
|
|
LightcapElement lastSync = get_last_sync(d);
|
|
|
|
// Handle the case that this is a new SYNC coming in
|
|
if (d->lastWasSync == false || overlaps(&lastSync, le) == false) {
|
|
// Now that the previous two states are in, check to see if they tell us where we are
|
|
enum LighthouseState new_state = d->lastWasSync ? EndSync(d, le) : EndSweep(d, le);
|
|
if (new_state != LS_UNKNOWN)
|
|
return new_state;
|
|
|
|
// Otherwise, just reset the sync registers and do another
|
|
|
|
ResetSync(d);
|
|
}
|
|
|
|
RegisterSync(d, le);
|
|
d->lastWasSync = true;
|
|
} else {
|
|
// If this is the start of a new sweep, check to see if the end of the sync solves
|
|
// the state
|
|
if (d->lastWasSync) {
|
|
enum LighthouseState new_state = EndSync(d, le);
|
|
if (new_state != LS_UNKNOWN)
|
|
return new_state;
|
|
}
|
|
d->lastWasSync = false;
|
|
}
|
|
|
|
return LS_UNKNOWN;
|
|
}
|
|
|
|
static enum LighthouseState SetState(Disambiguator_data_t *d, const LightcapElement *le,
|
|
enum LighthouseState new_state) {
|
|
|
|
SurviveContext *ctx = d->so->ctx;
|
|
Global_Disambiguator_data_t *g = ctx->disambiguator_data;
|
|
|
|
if (new_state >= LS_END)
|
|
new_state = 1;
|
|
|
|
if (d->state == LS_UNKNOWN && new_state != LS_UNKNOWN) {
|
|
Disambiguator_data_t *best_d = get_best_latest_state(g);
|
|
DEBUG_TB("Setting state to %d for %s, best state is %d", new_state, survive_colorize(d->so->codename),
|
|
best_d ? best_d->state : LS_UNKNOWN);
|
|
}
|
|
|
|
SV_VERBOSE(400, "%s Setting state %18s (%2d) -> %18s (%2d)", survive_colorize(d->so->codename),
|
|
LighthouseStateName(d->state), d->state, LighthouseStateName(new_state), new_state);
|
|
|
|
d->state = new_state;
|
|
if (new_state == LS_UNKNOWN) {
|
|
memset(d->sync_history, 0, sizeof(LightcapElement) * SYNC_HISTORY_LEN);
|
|
d->sync_offset = 0;
|
|
}
|
|
if (new_state == LS_UNKNOWN && get_best_latest_state(g) == 0) {
|
|
if (g->single_60hz_mode) {
|
|
SV_INFO("Disambiguator Reseting 60hz mode flag");
|
|
}
|
|
g->single_60hz_mode = false;
|
|
}
|
|
|
|
ResetSync(d);
|
|
|
|
memset(d->sweep_data, 0, sizeof(LightcapElement) * d->so->sensor_ct);
|
|
|
|
return new_state;
|
|
}
|
|
|
|
static void PropagateState(Disambiguator_data_t *d, const LightcapElement *le);
|
|
static void RunACodeCapture(int target_acode, Disambiguator_data_t *d, const LightcapElement *le) {
|
|
// Just ignore small signals; this has a measurable impact on signal quality
|
|
if (le->length < 400)
|
|
return;
|
|
|
|
// We know what state we are in, so we verify that state as opposed to
|
|
// trying to suss out the acode.
|
|
|
|
uint32_t error = calculate_error(target_acode, le);
|
|
SurviveContext *ctx = d->so->ctx;
|
|
Global_Disambiguator_data_t *g = ctx->disambiguator_data;
|
|
|
|
DEBUG_TB("Acode Capture %d (%4d) %4d -- %d or %d", target_acode, error, le->length, ACODE_TIMING(target_acode),
|
|
ACODE_TIMING(target_acode | DATA_BIT));
|
|
// Errors do happen; either reflections or some other noise. Our scheme here is to
|
|
// keep a tally of hits and misses, and if we ever go into the negatives reset
|
|
// the state machine to find the state again.
|
|
if (error > 800) {
|
|
|
|
// Penalize semi-harshly -- if it's ever off track it will take this many syncs
|
|
// to reset
|
|
const int penalty = 3;
|
|
if (d->confidence < penalty) {
|
|
SetState(d, le, LS_UNKNOWN);
|
|
SV_WARN("Disambiguator got lost at %u; refinding state for %s", le->timestamp,
|
|
survive_colorize(d->so->codename));
|
|
d->stats.confidence_resets++;
|
|
}
|
|
d->confidence -= penalty;
|
|
d->stats.sync_time_error++;
|
|
DEBUG_TB("Disambiguator missed %s; %d expected %d but got %d(%d) - %u %d", survive_colorize(d->so->codename),
|
|
error, target_acode, le->length, d->confidence, d->mod_offset[0], le->timestamp);
|
|
return;
|
|
}
|
|
|
|
if (d->confidence < 50) {
|
|
DEBUG_TB("Disambiguator hit %s; %d expected %d but got %d(%d) - %u %u", survive_colorize(d->so->codename),
|
|
error, target_acode, le->length, d->confidence, d->mod_offset[0], le->timestamp);
|
|
}
|
|
|
|
if (d->confidence < 100) {
|
|
d->confidence++;
|
|
}
|
|
// If its a real timestep, integrate it here and we can take the average later
|
|
|
|
RegisterSync(d, le);
|
|
}
|
|
|
|
static void ProcessStateChange(Disambiguator_data_t *d, const LightcapElement *le, enum LighthouseState new_state) {
|
|
SurviveContext *ctx = d->so->ctx;
|
|
Global_Disambiguator_data_t *g = d->so->ctx->disambiguator_data;
|
|
int end_of_mod = g->single_60hz_mode ? LS_WaitLHB_ACode0 : LS_END;
|
|
|
|
// Leaving a sync ...
|
|
if (LS_Params[d->state].is_sweep == 0) {
|
|
if (d->last_sync_count > 0) {
|
|
LightcapElement lastSync = {.timestamp = d->first_sync_timestamp,
|
|
.length = d->longest_sync_length,
|
|
.sensor_id = -d->last_sync_count};
|
|
|
|
AddSyncHistory(d, lastSync);
|
|
// Use the average of the captured pulse to adjust where we are modulo against.
|
|
// This lets us handle drift in any of the timing chararacteristics
|
|
uint32_t new_offset = SolveForMod_Offset(d, d->state, &lastSync);
|
|
int32_t delta = (new_offset - d->mod_offset[LS_Params[d->state].lh]) % end_of_mod;
|
|
if (abs(delta) > 100) {
|
|
SV_WARN("Drift in timecodes %s %u", survive_colorize(d->so->codename), delta);
|
|
}
|
|
d->mod_offset[LS_Params[d->state].lh] = new_offset;
|
|
DEBUG_TB("New offset %2d %d (%d)", LS_Params[d->state].lh, new_offset, delta);
|
|
// Figure out if it looks more like it has data or doesn't. We need this for OOX
|
|
int lengthData = ACODE_TIMING(LSParam_acode(d->state) | DATA_BIT);
|
|
int lengthNoData = ACODE_TIMING(LSParam_acode(d->state));
|
|
bool hasData = abs(lengthData - lastSync.length) < abs(lengthNoData - lastSync.length);
|
|
int acode = LSParam_acode(d->state);
|
|
if (hasData) {
|
|
acode |= DATA_BIT;
|
|
}
|
|
|
|
int next_state = d->state + 1;
|
|
|
|
Global_Disambiguator_data_t *g = ctx->disambiguator_data;
|
|
if (next_state == LS_END || (g->single_60hz_mode && next_state == LS_WaitLHB_ACode0))
|
|
next_state = 0;
|
|
|
|
int index_code = LS_Params[next_state].is_sweep ? -1 : -2;
|
|
if (d->confidence > 80) {
|
|
SURVIVE_INVOKE_HOOK_SO(light, d->so, index_code, acode, 0, lastSync.timestamp, lastSync.length,
|
|
LS_Params[d->state].lh);
|
|
d->stats.sync_count[index_code + 2]++;
|
|
} else {
|
|
d->stats.drop_syncs[index_code + 2]++;
|
|
}
|
|
}
|
|
} else {
|
|
// Leaving a sweep ...
|
|
size_t avg_length = 0;
|
|
|
|
int lh = LS_Params[d->state].lh;
|
|
survive_timecode best_timecode = LSParam_offset_for_state(d->state) + CAPTURE_WINDOW + d->mod_offset[lh];
|
|
size_t cnt = 0;
|
|
|
|
for (int i = 0; i < d->so->sensor_ct; i++) {
|
|
LightcapElement le = d->sweep_data[i];
|
|
if (le.length > g->light_min_length) {
|
|
avg_length += le.length;
|
|
cnt++;
|
|
// best_timecode = le.timestamp;
|
|
}
|
|
}
|
|
if (cnt > 0) {
|
|
FLT var = 3;
|
|
size_t minl = DIV_ROUND_CLOSEST(avg_length, cnt * 4);
|
|
size_t maxl = var * DIV_ROUND_CLOSEST(avg_length, cnt);
|
|
|
|
SurviveObject *so = d->so;
|
|
FLT avg_length_f = avg_length / cnt, maxl_f = maxl, minl_f = minl;
|
|
SV_DATA_LOG("sweep[%d][%d].avg", &avg_length_f, 1, lh, LSParam_acode(d->state) & 1);
|
|
SV_DATA_LOG("sweep[%d][%d].maxl", &maxl_f, 1, lh, LSParam_acode(d->state) & 1);
|
|
SV_DATA_LOG("sweep[%d][%d].minl", &minl_f, 1, lh, LSParam_acode(d->state) & 1);
|
|
|
|
int acode = LSParam_acode(d->state);
|
|
for (int i = 0; i < d->so->sensor_ct; i++) {
|
|
const LightcapElement *le = &d->sweep_data[i];
|
|
// Only care if we actually have data AND we have a time of last sync. We won't have the latter
|
|
// if we synced with the LH at certain times.
|
|
if (le->length > 0 && le->length >= minl && le->length <= maxl) {
|
|
int le_offset = apply_mod_offset(le->timestamp + le->length / 2, d->mod_offset[lh], end_of_mod);
|
|
int32_t offset_from = le_offset - LSParam_offset_for_state(d->state - 1 - lh);
|
|
// if(acode & 1)
|
|
// offset_from += 20000;
|
|
|
|
assert(offset_from > 0);
|
|
// Send the lightburst out.
|
|
if (d->confidence > 80) {
|
|
SURVIVE_INVOKE_HOOK_SO(light, d->so, i, acode, offset_from, le->timestamp, le->length, lh);
|
|
d->stats.sweep_hit_count++;
|
|
} else {
|
|
d->stats.drop_sweeps++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (d->confidence > 80 && cnt > 0) {
|
|
SURVIVE_INVOKE_HOOK_SO(light, d->so, -3, LS_Params[d->state].acode, 0, best_timecode,
|
|
DIV_ROUND_CLOSEST(avg_length, cnt), LS_Params[d->state].lh);
|
|
}
|
|
}
|
|
SetState(d, le, new_state);
|
|
}
|
|
|
|
static inline uint32_t offset_from_state(Disambiguator_data_t *d, const LightcapElement *le) {
|
|
struct SurviveContext *ctx = d->so->ctx;
|
|
Global_Disambiguator_data_t *g = ctx->disambiguator_data;
|
|
int end_of_mod = g->single_60hz_mode ? LS_WaitLHB_ACode0 : LS_END;
|
|
int lh = LS_Params[d->state].lh;
|
|
int le_offset = apply_mod_offset(le->timestamp + le->length / 2, d->mod_offset[lh], end_of_mod);
|
|
int state_offset = le_offset - LSParam_offset_for_state(d->state);
|
|
if (state_offset > LS_Params[d->state].window)
|
|
state_offset = state_offset - LS_Params[d->state].window;
|
|
return state_offset;
|
|
}
|
|
|
|
static void PropagateState(Disambiguator_data_t *d, const LightcapElement *le) {
|
|
struct SurviveContext *ctx = d->so->ctx;
|
|
if (le->sensor_id >= d->so->sensor_ct) {
|
|
SV_WARN("Invalid sensor %d detected hit", le->sensor_id);
|
|
return;
|
|
}
|
|
|
|
Global_Disambiguator_data_t *g = ctx->disambiguator_data;
|
|
int end_of_mod = g->single_60hz_mode ? LS_WaitLHB_ACode0 : LS_END;
|
|
|
|
int lh = LS_Params[d->state].lh;
|
|
int le_offset = apply_mod_offset(le->timestamp + le->length / 2, d->mod_offset[lh], end_of_mod);
|
|
|
|
/** Find where this new element fits into our state machine. This can skip states if its been a while since
|
|
* its been able to process, or if a LH is missing. */
|
|
int offset_error;
|
|
enum LighthouseState new_state = LighthouseState_findByOffset(le_offset, &offset_error);
|
|
|
|
if (d->state != new_state) {
|
|
if (d->state + 1 != new_state && (d->state != (LS_END - 1) && new_state == 1)) {
|
|
DEBUG_TB("Missed some states... %d to %d", d->state, new_state);
|
|
}
|
|
// This processes the change -- think setting buffers, and sending OOTX / lightproc calls
|
|
ProcessStateChange(d, le, new_state);
|
|
}
|
|
|
|
const LighthouseStateParameters *param = &LS_Params[d->state];
|
|
if (param->is_sweep == 0) {
|
|
RunACodeCapture(LSParam_acode(d->state), d, le);
|
|
} else if (le->length > d->sweep_data[le->sensor_id].length &&
|
|
le->length < 10000 /*anything above 10k seems to be bullshit?*/) {
|
|
// Note we only select the highest length one per sweep. Also, we bundle everything up and send it later all at
|
|
// once.
|
|
// so that we can do this filtering. Might not be necessary?
|
|
if (le->length > 3000) {
|
|
d->confidence--;
|
|
}
|
|
assert(le->sensor_id < d->so->sensor_ct);
|
|
d->sweep_data[le->sensor_id] = *le;
|
|
}
|
|
}
|
|
|
|
void DisambiguatorStateBased(SurviveObject *so, const LightcapElement *le) {
|
|
SurviveContext *ctx = so->ctx;
|
|
|
|
// Signal to destroy self
|
|
if (le == 0) {
|
|
Disambiguator_data_t *d = so->disambiguator_data;
|
|
if (d) {
|
|
SV_VERBOSE(5, "StateBased Disambiguator statistics:");
|
|
SV_VERBOSE(5, "\tsync_time_error %u", d->stats.sync_time_error);
|
|
SV_VERBOSE(5, "\tconfidence_resets %u", d->stats.confidence_resets);
|
|
SV_VERBOSE(5, "\tdrop_sweeps %u", d->stats.drop_sweeps);
|
|
SV_VERBOSE(5, "\tsweep_hit_count %u", d->stats.sweep_hit_count);
|
|
for (int i = 0; i < 2; i++) {
|
|
SV_VERBOSE(5, "\tsync_count[%d] %u", i, d->stats.sync_count[i]);
|
|
SV_VERBOSE(5, "\tdrop_syncs[%d] %u", i, d->stats.drop_syncs[i]);
|
|
}
|
|
}
|
|
if (ctx->disambiguator_data) {
|
|
Global_Disambiguator_data_t_detach_config(ctx, ctx->disambiguator_data);
|
|
free(ctx->disambiguator_data);
|
|
ctx->disambiguator_data = 0;
|
|
}
|
|
|
|
free(so->disambiguator_data);
|
|
so->disambiguator_data = 0;
|
|
return;
|
|
}
|
|
|
|
if (ctx->state == SURVIVE_CLOSING) {
|
|
return;
|
|
}
|
|
|
|
// Note, this happens if we don't have config yet -- just bail
|
|
if (so->sensor_ct == 0) {
|
|
return;
|
|
}
|
|
|
|
if (so->ctx->disambiguator_data == NULL) {
|
|
Global_Disambiguator_data_t *d = SV_CALLOC(sizeof(Global_Disambiguator_data_t));
|
|
d->ctx = ctx;
|
|
ctx->disambiguator_data = d;
|
|
Global_Disambiguator_data_t_attach_config(ctx, d);
|
|
}
|
|
|
|
if (so->disambiguator_data == NULL) {
|
|
Disambiguator_data_t *d = SV_CALLOC(sizeof(Disambiguator_data_t) + sizeof(LightcapElement) * so->sensor_ct);
|
|
d->so = so;
|
|
so->disambiguator_data = d;
|
|
}
|
|
|
|
Disambiguator_data_t *d = so->disambiguator_data;
|
|
|
|
// It seems like the first few hundred lightcapelements are missing a ton of data; let it stabilize.
|
|
if (d->stabalize < 200) {
|
|
d->stabalize++;
|
|
return;
|
|
}
|
|
|
|
SV_VERBOSE(3000, "%s LE: %2u\t%4u\t%10u\t%2u\t%7u", so->codename, le->sensor_id, le->length, le->timestamp,
|
|
d->state, offset_from_state(d, le));
|
|
|
|
if (d->state == LS_UNKNOWN) {
|
|
enum LighthouseState new_state = AttemptFindState(d, le);
|
|
if (new_state != LS_UNKNOWN) {
|
|
d->confidence = 0;
|
|
d->failures = 0;
|
|
|
|
int le_offset = (le->timestamp - d->mod_offset[0]) % LSParam_offset_for_state(LS_END);
|
|
enum LighthouseState new_state1 = LighthouseState_findByOffset(le_offset, 0);
|
|
SetState(d, le, new_state);
|
|
SV_INFO("Locked onto state %2d(%2d, %8d) at %12u for %s", new_state, new_state1, le_offset,
|
|
d->mod_offset[0], survive_colorize(d->so->codename));
|
|
} else {
|
|
d->failures++;
|
|
if (d->failures > 1000) {
|
|
d->failures = 0;
|
|
SV_WARN("Could not find disambiguator state for %s", survive_colorize(d->so->codename));
|
|
}
|
|
}
|
|
} else {
|
|
uint32_t timediff = survive_timecode_difference(le->timestamp, d->last_timestamp);
|
|
if (timediff > d->so->timebase_hz) {
|
|
int penalty = timediff / d->so->timebase_hz * 10;
|
|
if (d->confidence < penalty) {
|
|
SetState(d, le, LS_UNKNOWN);
|
|
SV_WARN("Disambiguator got lost at %u (sync timeout %u); refinding state for %s", le->timestamp,
|
|
timediff, survive_colorize(d->so->codename));
|
|
return;
|
|
}
|
|
|
|
d->confidence = d->confidence - penalty;
|
|
}
|
|
PropagateState(d, le);
|
|
}
|
|
|
|
d->last_timestamp = le->timestamp;
|
|
}
|
|
|
|
REGISTER_LINKTIME(DisambiguatorStateBased)
|