#include "ootx_decoder.h" #include "survive.h" #include "survive_config.h" #include "survive_internal.h" #include "survive_kalman_tracker.h" #include "survive_recording.h" #include #include #include static FLT freq_per_channel[NUM_GEN2_LIGHTHOUSES] = { 50.0521, 50.1567, 50.3673, 50.5796, 50.6864, 50.9014, 51.0096, 51.1182, 51.2273, 51.6685, 52.2307, 52.6894, 52.9217, 53.2741, 53.7514, 54.1150, }; static void ootx_bad_crc_clbk(ootx_decoder_context *ct, ootx_packet *pkt, uint32_t checksum) { SurviveContext *ctx = ((SurviveObject *)(ct->user))->ctx; int id = ct->user1; if (!ctx->bsd[id].OOTXSet) SV_VERBOSE(200, "(%d) Failed CRC", ctx->bsd[id].mode != 255 ? ctx->bsd[id].mode : id); } static void ootx_error_clbk_d(ootx_decoder_context *ct, const char *msg) { SurviveContext *ctx = ((SurviveObject *)(ct->user))->ctx; int id = ct->user1; if (!ctx->bsd[id].OOTXSet) SV_INFO("(%d) %s", ctx->bsd[id].mode != 255 ? ctx->bsd[id].mode : id, msg); } STATIC_CONFIG_ITEM(SERIALIZE_OOTX, "serialize-ootx", 'b', "Serialize out ootx", 0) static void ootx_packet_clbk_d_gen2(ootx_decoder_context *ct, ootx_packet *packet) { SurviveContext *ctx = ((SurviveObject *)(ct->user))->ctx; int id = ct->user1; lighthouse_info_v15 v15; init_lighthouse_info_v15(&v15, packet->data); if (survive_configi(ctx, SERIALIZE_OOTX_TAG, SC_GET, 0) == 1) { char filename[128]; snprintf(filename, 128, "LH%02d_%08x.ootx", v15.mode_current & 0x7F, (unsigned)v15.id); FILE *f = fopen(filename, "w"); fwrite(packet->data, packet->length, 1, f); fclose(f); } BaseStationData *b = &ctx->bsd[id]; b->OOTXChecked |= true; FLT accel[3] = {v15.accel_dir[0], v15.accel_dir[1], v15.accel_dir[2]}; bool upChanged = norm3d(b->accel) != 0.0 && dist3d(b->accel, accel) > 1e-3; if (upChanged) { SV_VERBOSE(10, "OOTX up direction changed for %x (%f)", b->BaseStationID, norm3d(b->accel)); } bool doSave = b->BaseStationID != v15.id || b->OOTXSet == false || upChanged; b->OOTXSet = 1; if (doSave) { SV_INFO("Got OOTX packet %d %08x", ctx->bsd[id].mode, (unsigned)v15.id); b->BaseStationID = v15.id; for (int i = 0; i < 2; i++) { b->fcal[i].phase = v15.fcal_phase[i]; b->fcal[i].tilt = v15.fcal_tilt[i]; b->fcal[i].curve = v15.fcal_curve[i]; b->fcal[i].gibpha = v15.fcal_gibphase[i]; b->fcal[i].gibmag = v15.fcal_gibmag[i]; b->fcal[i].ogeephase = v15.fcal_ogeephase[i]; b->fcal[i].ogeemag = v15.fcal_ogeemag[i]; } for (int i = 0; i < 3; i++) { b->accel[i] = v15.accel_dir[i]; } b->sys_unlock_count = v15.sys_unlock_count; // Although we know this already.... b->mode = v15.mode_current & 0x7F; survive_reset_lighthouse_position(ctx, id); SURVIVE_INVOKE_HOOK(ootx_received, ctx, id); } } static void ootx_packet_cblk_d_gen1(ootx_decoder_context *ct, ootx_packet *packet) { SurviveContext *ctx = ((SurviveObject *)(ct->user))->ctx; int id = ct->user1; lighthouse_info_v6 v6; init_lighthouse_info_v6(&v6, packet->data); BaseStationData *b = &ctx->bsd[id]; b->OOTXChecked = true; FLT accel[3] = {v6.accel_dir_x, v6.accel_dir_y, v6.accel_dir_z}; bool upChanged = norm3d(b->accel) != 0.0 && dist3d(b->accel, accel) > 1e-3; bool doSave = b->BaseStationID != v6.id || b->OOTXSet == false || upChanged || b->mode != v6.mode_current; b->sys_unlock_count = v6.sys_unlock_count; b->OOTXSet = 1; if (doSave) { SV_VERBOSE(50, "Got OOTX packet %d %08x", ctx->bsd[id].mode, (unsigned)v6.id); b->BaseStationID = v6.id; b->fcal[0].phase = v6.fcal_0_phase; b->fcal[1].phase = v6.fcal_1_phase; b->fcal[0].tilt = tan(v6.fcal_0_tilt); b->fcal[1].tilt = tan(v6.fcal_1_tilt); b->fcal[0].curve = v6.fcal_0_curve; b->fcal[1].curve = v6.fcal_1_curve; b->fcal[0].gibpha = v6.fcal_0_gibphase; b->fcal[1].gibpha = v6.fcal_1_gibphase; b->fcal[0].gibmag = v6.fcal_0_gibmag; b->fcal[1].gibmag = v6.fcal_1_gibmag; b->accel[0] = v6.accel_dir_x; b->accel[1] = v6.accel_dir_y; b->accel[2] = v6.accel_dir_z; b->mode = v6.mode_current; survive_reset_lighthouse_position(ctx, id); SURVIVE_INVOKE_HOOK(ootx_received, ctx, id); } } void survive_ootx_dump_decoder_context(struct SurviveContext *ctx, int bsd_idx) { ootx_decoder_context *decoderContext = ctx->bsd[bsd_idx].ootx_data; if (decoderContext == 0) return; SV_VERBOSE(105, "OOTX stats for LH%d (mode: %d, %u)", bsd_idx, ctx->bsd[bsd_idx].mode, ctx->bsd[bsd_idx].BaseStationID); SV_VERBOSE(105, "\tBits seen: %u (%d bytes)", decoderContext->stats.bits_seen, decoderContext->stats.bits_seen / 8); SV_VERBOSE(105, "\tBad CRCs: %u", decoderContext->stats.bad_crcs); SV_VERBOSE(105, "\tBad sync bits: %u", decoderContext->stats.bad_sync_bits); SV_VERBOSE(105, "\tPackets found: %u", decoderContext->stats.packets_found); SV_VERBOSE(105, "\tPayload size: %u", decoderContext->stats.used_bytes); SV_VERBOSE(105, "\tPackage bits: %u", decoderContext->stats.package_bits); SV_VERBOSE(105, "\tGuessed bits: %u (%5.2f%%)", decoderContext->stats.guess_bits, decoderContext->stats.guess_bits / (FLT)decoderContext->stats.package_bits * 100.); FLT d = survive_run_time(ctx) - decoderContext->stats.started_s; SV_VERBOSE(105, "\tTime: %2.2f (%2.2fb/s, %2.2fb/s)", d, decoderContext->stats.bits_seen / d, decoderContext->stats.used_bytes * 8 / d); } void survive_ootx_free_decoder_context(struct SurviveContext *ctx, int bsd_idx) { ootx_decoder_context *decoderContext = ctx->bsd[bsd_idx].ootx_data; ctx->bsd[bsd_idx].ootx_data = 0; if (decoderContext == 0) return; survive_ootx_dump_decoder_context(ctx, bsd_idx); ootx_free_decoder_context(decoderContext); free(decoderContext); } void survive_ootx_behavior(SurviveObject *so, int8_t bsd_idx, int8_t lh_version, int ootx) { struct SurviveContext *ctx = so->ctx; if (ctx->bsd[bsd_idx].OOTXChecked == false) { ootx_decoder_context *decoderContext = ctx->bsd[bsd_idx].ootx_data; if (decoderContext == 0) { if (lh_version == 1) { SV_INFO("OOTX not set for LH in channel %d; attaching ootx decoder using device %s", ctx->bsd[bsd_idx].mode, so->codename); } else { SV_INFO("OOTX not set for LH %d; attaching ootx decoder using device %s", bsd_idx, so->codename); } decoderContext = ctx->bsd[bsd_idx].ootx_data = SV_CALLOC(sizeof(ootx_decoder_context)); ootx_init_decoder_context(decoderContext, survive_run_time(ctx)); decoderContext->user1 = bsd_idx; decoderContext->user = so; decoderContext->ignore_sync_bit_error = survive_configi(ctx, "ootx-ignore-sync-error", SC_SETCONFIG, 0); decoderContext->ootx_packet_clbk = lh_version ? ootx_packet_clbk_d_gen2 : ootx_packet_cblk_d_gen1; decoderContext->ootx_error_clbk = ootx_error_clbk_d; decoderContext->ootx_bad_crc_clbk = ootx_bad_crc_clbk; } if (decoderContext->user == so) { ootx_pump_bit(decoderContext, ootx); if (ctx->bsd[bsd_idx].OOTXChecked) { ctx->bsd[bsd_idx].OOTXChecked = false; survive_ootx_dump_decoder_context(ctx, bsd_idx); // survive_ootx_free_decoder_context(ctx, bsd_idx); } } } } SURVIVE_EXPORT void survive_default_sync_process(SurviveObject *so, survive_channel channel, survive_timecode timecode, bool ootx, bool gen) { struct SurviveContext *ctx = so->ctx; int8_t bsd_idx = survive_get_bsd_idx(ctx, channel); if (bsd_idx == -1) { SV_WARN("Invalid channel requested(%d) for %s", channel, so->codename) return; } if (so->ctx->bsd[bsd_idx].disable) return; assert(channel <= NUM_GEN2_LIGHTHOUSES); survive_recording_sync_process(so, channel, timecode, ootx, gen); bool isIniting = so->last_sync_time[bsd_idx] == 0; if (!isIniting) { survive_timecode time_delta = survive_timecode_difference(timecode, so->last_sync_time[bsd_idx]); survive_timecode predicted_time = 48000000. / freq_per_channel[channel]; int rotations_since = (time_delta + predicted_time / 2) / predicted_time; int skipped_syncs = rotations_since - 1; time_delta -= predicted_time * skipped_syncs; FLT hz = 48000000. / time_delta; FLT err = fabs(hz - freq_per_channel[channel]); SV_DATA_LOG("lh_freq_err[%d]", &err, 1, channel); if (err > 1) { if (skipped_syncs > 10) { so->last_sync_time[bsd_idx] = 0; so->stats.sync_resets[bsd_idx]++; } SV_VERBOSE(100, "Sync hz %2d: %8.6fhz (err: %0.6fhz) ootx: %d gen: %d time: %u count: %u", channel, hz, err, ootx, gen, timecode, so->stats.syncs[bsd_idx]); so->stats.bad_syncs[bsd_idx] += skipped_syncs; so->sync_count[bsd_idx] += skipped_syncs; return; } else { SV_VERBOSE(400, "%s Sync ch%2d %6.3fhz (err: %0.6fhz) ootx: %d gen: %d time: %u count: %u", survive_colorize(so->codename), channel, hz, err, ootx, gen, timecode, so->stats.syncs[bsd_idx]); } so->stats.skipped_syncs[bsd_idx] += skipped_syncs; // Every skipped sync halves our ootx success rate; but if we don't send anything in it will be wrong 100% of // the time for (int i = 0; i < skipped_syncs; i++) { survive_ootx_behavior(so, bsd_idx, ctx->lh_version, -1); } so->last_time_between_sync[bsd_idx] = time_delta; } so->stats.syncs[bsd_idx]++; so->sync_count[bsd_idx]++; so->last_sync_time[bsd_idx] = timecode; survive_ootx_behavior(so, bsd_idx, ctx->lh_version, ootx); PoserDataLightGen2 l = {.common = { .hdr = { .pt = POSERDATA_SYNC_GEN2, .timecode = SurviveSensorActivations_long_timecode_light(&so->activations, timecode), }, .lh = bsd_idx, }}; if (bsd_idx < ctx->activeLighthouses) { if (SurviveSensorActivations_add_gen2(&so->activations, &l) == false) { so->stats.rejected_data[bsd_idx]++; } else { so->stats.accepted_data[bsd_idx]++; } } so->stats.hit_from_lhs[bsd_idx]++; if (ctx->lh_version != -1) { survive_kalman_tracker_integrate_light(so->tracker, &l.common); SURVIVE_POSER_INVOKE(so, &l); } } static inline int8_t determine_plane(SurviveObject *so, int8_t bsd_idx, FLT angle) { static int naive_plane_only = -1; if (naive_plane_only == -1) naive_plane_only = survive_configi(so->ctx, "naive-plane-only", SC_GET, 0); int8_t naive_plane = angle > LINMATHPI; if (naive_plane_only != 0) return naive_plane; int8_t plane = naive_plane; FLT m = .05; bool borderLine = angle > LINMATHPI * (1 - 2 * m) && angle < LINMATHPI * (1 + 2 * m); bool veryBorderLine = angle > LINMATHPI * (1 - m) && angle < LINMATHPI * (1 + m); FLT angle_for_axis[2] = {angle - 2. / 3. * LINMATHPI, angle - 4. / 3. * LINMATHPI}; if (borderLine) { if (veryBorderLine) plane = -1; FLT err[2] = {0}; for (int i = 0; i < 2; i++) { err[i] = fabsf((float)(angle_for_axis[i] - so->activations.angles_center_x[bsd_idx][i])); if (isnan(err[i])) err[i] = 1.; } if (err[0] <= .1 || err[1] <= .1) { plane = err[1] < err[0]; } if (plane != -1 && naive_plane != plane) { so->stats.extent_hits++; so->stats.min_extent = linmath_min(angle - LINMATHPI, so->stats.min_extent); so->stats.max_extent = linmath_max(angle - LINMATHPI, so->stats.max_extent); } else if (plane == -1) { so->stats.extent_misses++; } else if (plane == naive_plane) { so->stats.naive_hits++; } } return plane; } SURVIVE_EXPORT void survive_default_sweep_process(SurviveObject *so, survive_channel channel, int sensor_id, survive_timecode timecode, bool half_clock_flag) { struct SurviveContext *ctx = so->ctx; int8_t bsd_idx = survive_get_bsd_idx(ctx, channel); if (bsd_idx == -1) { SV_WARN("Invalid channel requested(%d) for %s", channel, so->codename) return; } if (so->ctx->bsd[bsd_idx].disable) return; survive_notify_gen2(so, "sweep called"); survive_recording_sweep_process(so, channel, sensor_id, timecode, half_clock_flag); survive_timecode last_sweep = so->last_sync_time[bsd_idx]; if (last_sweep == 0) { return; } assert(channel <= NUM_GEN2_LIGHTHOUSES); // SV_INFO("Sensor ch%2d %2d %d %12x %6d", channel, sensor_id, flag, timecode, timecode // so->last_sync_time[bsd_idx]); survive_timecode time_delta = survive_timecode_difference(timecode, last_sweep); FLT time_since_sync = (time_delta / 48000000.); if (half_clock_flag) time_since_sync += 0.5 / 48000000.; FLT hz = 48000000. / so->last_time_between_sync[bsd_idx]; if (fabs(hz - freq_per_channel[channel]) > 1.0) { hz = freq_per_channel[channel]; } survive_timecode predicted_time = 48000000. / freq_per_channel[channel]; int rotations_since = time_delta / predicted_time; FLT time_per_rot = 1. / hz; time_since_sync -= time_per_rot * rotations_since; if (rotations_since > 10) { SV_VERBOSE(100, "Dropping light data ch %d sync: %fms rotations missed: %d (at %u)", channel, time_since_sync * 1000., rotations_since, timecode); so->stats.dropped_light[bsd_idx]++; return; } FLT angle = time_since_sync / time_per_rot * 2. * LINMATHPI; FLT angle2 = (time_since_sync + .5 / 48000000.) / time_per_rot * 2. * LINMATHPI; SV_VERBOSE(450, "%s %7.3f Sensor ch%2d.%02d %+8.3fdeg %12f %d time_since_sync: %.16f rot: %5u tc: %u", survive_colorize(so->codename), survive_run_time(ctx), channel, sensor_id, angle / LINMATHPI * 180., angle2 / LINMATHPI * 180., half_clock_flag, time_since_sync, rotations_since + so->sync_count[bsd_idx], timecode); FLT angle_for_axis[2] = {angle - 2. / 3. * LINMATHPI, angle - 4. / 3. * LINMATHPI}; int8_t plane = determine_plane(so, bsd_idx, angle); SV_DATA_LOG("time_since_sync[%d,%d,%d]", &time_since_sync, 1, channel, sensor_id, plane); so->stats.hit_from_lhs[bsd_idx]++; if (plane >= 0) SURVIVE_INVOKE_HOOK_SO(sweep_angle, so, channel, sensor_id, timecode, plane, angle_for_axis[plane]); } SURVIVE_EXPORT void survive_default_sweep_angle_process(SurviveObject *so, survive_channel channel, int sensor_id, survive_timecode timecode, int8_t plane, FLT angle) { struct SurviveContext *ctx = so->ctx; int8_t bsd_idx = survive_get_bsd_idx(ctx, channel); if (bsd_idx == -1) { SV_WARN("Invalid channel requested(%d) for %s", channel, so->codename) return; } PoserDataLightGen2 l = { .common = { .hdr = { .pt = POSERDATA_LIGHT_GEN2, .timecode = SurviveSensorActivations_long_timecode_light(&so->activations, timecode), }, .sensor_id = sensor_id, .angle = angle, .lh = bsd_idx, }, .plane = plane, .sync = so->sync_count[bsd_idx]}; SV_VERBOSE(500, "%s %7.3f Sensor ch%2d.%02d.%d %+8.3fdeg", survive_colorize(so->codename), survive_run_time(ctx), channel, sensor_id, plane, angle / LINMATHPI * 180.); // Simulate the use of only one lighthouse in playback mode. if (bsd_idx < ctx->activeLighthouses) { if (SurviveSensorActivations_add_gen2(&so->activations, &l) == false) { so->stats.rejected_data[bsd_idx]++; } else { so->stats.accepted_data[bsd_idx]++; survive_kalman_tracker_integrate_light(so->tracker, &l.common); } } survive_recording_sweep_angle_process(so, channel, sensor_id, timecode, plane, angle); SURVIVE_POSER_INVOKE(so, &l); } SURVIVE_EXPORT void survive_default_gen_detected_process(SurviveObject *so, int lh_version) { SurviveContext *ctx = so->ctx; if (ctx->lh_version != -1) { static bool seenWarning = false; if (seenWarning == false) { SV_WARN("Detected both LH gen1 and LH gen2 systems. Mixed mode is only supported for experimentation."); seenWarning = true; } ctx->lh_version = 3; return; } assert(ctx->lh_version == -1); SV_INFO("Detected LH gen %d system.", lh_version + 1); if (lh_version != ctx->lh_version_configed && ctx->lh_version_configed != -1) { SV_WARN("Configuration was valid for gen %d; resetting BSD positions and OOTX", ctx->lh_version_configed + 1); for (int i = 0; i < NUM_GEN2_LIGHTHOUSES; i++) { ctx->bsd[i].PositionSet = ctx->bsd[i].OOTXSet = 0; ctx->bsd[i].mode = -1; } } for (int i = 0; i < ctx->objs_ct; i++) { // When the USB devices transition; they reset their clock or something -- so clear out old data. SurviveSensorActivations_reset(&ctx->objs[i]->activations); } ctx->lh_version = lh_version; survive_configi(ctx, "configed-lighthouse-gen", SC_OVERRIDE | SC_SETCONFIG, lh_version + 1); config_save(ctx); }