cntools / libsurvive

Open Source Lighthouse Tracking System
MIT License
747 stars 136 forks source link

Make Controller reconnect after turning off and on again #314

Open throni3git opened 2 months ago

throni3git commented 2 months ago

Describe the bug I am trying to find a way to reestablish a communication from a Vive Controller (or Vive Tracker) after it was turned off and on again. In the current state of libsurvive with libusb, the according SurviveObject is not reopened for communication again.

My approach My investigation led me to driver_vive.libusb.h to function handle_transfer. There is a flag called request_reopen which is evaluated in driver_vive.c::survive_handle_close_request_flag. This flag will not be set in my case of a Vive Controller connected over a Watchman Dongle, since in the function handle_transfer, iface->consecutive_timeouts will never be increased more than once for wireless connections. It will be increased often when i connect the Controller via cable - this is why @jdavidberger eventually came up with a threshold of 3 consecutive_timeouts to set the request_reopen flag (commit d939c24f4ea43aa82366ca55e35ce064f7450c17). My approach is to modify this section in driver_vive.libusb.h::handle_transfer like the following:

// put this function before `handle_transfer`
static bool survive_device_is_rf(const struct DeviceInfo *device_info) {
    switch (device_info->type) {
    case USB_DEV_HMD:
    case USB_DEV_HMD_IMU_LH:
    case USB_DEV_W_WATCHMAN1:
    case USB_DEV_TRACKER0:
    case USB_DEV_TRACKER1:
        return false;
    }
    return true;
}

static void handle_transfer(struct libusb_transfer *transfer) {
          uint64_t time = OGGetAbsoluteTimeUS();

          SurviveUSBInterface *iface = transfer->user_data;
          SurviveContext *ctx = iface->ctx;
          if (!iface->shutdown && transfer->status == LIBUSB_TRANSFER_TIMED_OUT) {
                    iface->consecutive_timeouts++;
                    // no consecutive timeout counting when using wireless Controller
                    bool is_rf_device = survive_device_is_rf(iface->usbInfo->device_info);
                    if(iface->consecutive_timeouts >= 3 || is_rf_device) {
                              SV_WARN("%f %s Device turned off: %d", survive_run_time(ctx), survive_colorize_codename(iface->assoc_obj),
                              transfer->status);
                              goto object_turned_off;
                    } else {
                              return;
                    }
          }

          // ...

Now this seems to work for me although i am sure the issue is related to some different part of the software. Then i came across a different issue: The SurviveObject is destroyed in this case and after reopening the device and creating a new SurviveObject, some seconds later driver_global_scene_solver.c needs to run_optimization. Later in the chain, survive_create_device is run with garbage SurviveObject instances. I assume that this is a synchronisation issue between the threads.

Data

Hardware setup

Desktop (please complete the following information):

throni3git commented 1 month ago

i found that the GlobalSceneSolver had old data (i called it garbage SurviveObject instances in the original post). As a solution, is implemted a refreshing of the cache in src/driver_global_scene_solver.c in the function add_scenes. I modified it like this:

static size_t add_scenes(struct global_scene_solver *gss, SurviveObject *so) {
    size_t rtn = 0;
    SurviveContext *ctx = so->ctx;

    survive_long_timecode sensor_time_window = SurviveSensorActivations_stationary_time(&so->activations) / 2;

    SurviveSensorActivations *activations = &so->activations;

// begin of insertion

    unsigned int free_idx = 0;
    for (int i = 0; i < GSS_NUM_STORED_SCENES; i++) {
        if (gss->scenes[i].so == NULL) {
            continue;
        }

        bool is_still_valid = false;
        if (gss->scenes[i].so == so) {
            is_still_valid = true;
        }
        for (int j = 0; j < ctx->objs_ct; j++) {
            if (gss->scenes[i].so == ctx->objs[j]) {
                is_still_valid = true;
            }
        }

        if (is_still_valid) {
            gss->scenes[free_idx].so = gss->scenes[i].so;
            free_idx++;
        } else {
            // printf("in GLobalSceneSolver: deleted scene %d with SurviveObject at adress &%p", i, (void* )so);
        }
    }
    gss->scenes_cnt = free_idx;

// end of insertion

    struct PoserDataGlobalScene *scene = &gss->scenes[gss->scenes_cnt % GSS_NUM_STORED_SCENES];

I am not sure if this is the correct place to do this, but it helped.