Tobii 4C (устройство для отслеживания направления взгляда)

ActiveX, .NET, DLL
Ответить
Аватара пользователя
violator
interested
interested
Сообщения: 3
Зарегистрирован: 23 май 2022, 15:09
Версия LabVIEW: 2021
Откуда: Москва
Благодарил (а): 4 раза

Tobii 4C (устройство для отслеживания направления взгляда)

Сообщение violator »

Приветствую
Я пытаюсь получать данные с айтрекера Tobii 4C (устройство для отслеживания направления взгляда) в LabView.
Написал dll, из которой наружу торчат 3 функции:

Код: Выделить всё

TOBII_WRAPPER_API int getGazeData(void *eventRef);			//генерация события, содержащего усредненные координаты взгляда
TOBII_WRAPPER_API bool connectToSauronEye(int gazeBufferLength = 30);	//инициализация и запуск захвата направления взгляда
TOBII_WRAPPER_API bool disconnectFromSauronEye();			//отключение
Создал проект на основе шаблона конечного автомата.
Вызовы функций подключения и отключения айтрекера выполняются успешно, но наткнулся на камень преткновения - получение и обработка события из dll. Вовнутрь Event Structure не заходит.
TobiiSM.png
callLibFunc.png
callLibFunc2.png
Подскажите пожалуйста, где я мог ошибиться?
Фрагмент кода dll, основной проект LV и проект в user.lib в прикрепленном архиве:
TobiiSM_proj.rar
(884.08 КБ) 53 скачивания
Аватара пользователя
IvanLis

Activity Professionalism Tutorials Gold Man of the year 2012
Автор
guru
guru
Сообщения: 5462
Зарегистрирован: 02 дек 2009, 17:44
Награды: 7
Версия LabVIEW: 2015, 2016
Откуда: СССР
Благодарил (а): 28 раз
Поблагодарили: 86 раз

Re: Tobii 4C (устройство для отслеживания направления взгляда)

Сообщение IvanLis »

Я в С не специалист, по этому ничего конкретного не могу сказать по поводу реализации функции.
Но на мой взгляд передавать указатель на очередь внутрь библиотеки не очень.

В любом случае, как у Вас сейчас реализовано, подразумевает асинхронный запуск внешней функции с передачей указателя на очередь, но....
Вы каждый раз создаете новую очередь и запускаете экземпляр функции. Делать это необходимо единожды, потом эта функция должна кидать данные уже в ранее созданную очередь. Но необходимо предусмотреть выход из нее.

Если Вы в состоянии были написать и скомпилировать библиотеку, то думаю не составит труда поправить ее таким образом, что бы функция возвращала координаты по запросу. В любом случае, запоздалые данные о положении курсора нам не нужны, а их пропуск не страшен.
Тогда структура программы выстроится, будет Инициализация -> многократный Опрос устройства -> Выключение

Так и Вам проще и программе.
Аватара пользователя
IvanLis

Activity Professionalism Tutorials Gold Man of the year 2012
Автор
guru
guru
Сообщения: 5462
Зарегистрирован: 02 дек 2009, 17:44
Награды: 7
Версия LabVIEW: 2015, 2016
Откуда: СССР
Благодарил (а): 28 раз
Поблагодарили: 86 раз

Re: Tobii 4C (устройство для отслеживания направления взгляда)

Сообщение IvanLis »

И модель программирования на много проще будет, типа такого...
1.png
Untitled 1.vi
lv2021
(13.22 КБ) 47 скачиваний
Только нужно будет обработку ошибок добавить, что бы при сбое "Опрос" переходило в режим ожидания или переподключалось.
Ну и период опроса 10ms это наверное много :wink:
Аватара пользователя
violator
interested
interested
Сообщения: 3
Зарегистрирован: 23 май 2022, 15:09
Версия LabVIEW: 2021
Откуда: Москва
Благодарил (а): 4 раза

Re: Tobii 4C (устройство для отслеживания направления взгляда)

Сообщение violator »

IvanLis
API айтрекера сам выдает данные посредством коллбека ( onGazeDataEvent() ) я же накапливаю сырые данные во внутреннем кольцевом буфере, усредняю, записывая во внутренний массив, и при вызове getGazeData() из LV создаю событие:

Код: Выделить всё

PostLVUserEvent(event, (void*)g_GazePoint);
, где:
  • event - ссылка на событие;
  • g_GazePoint - указанный выше массив, содержащий пару координат X-Y на момент вызова функции.
Насколько понял в LV не получится быть "ведомым", реагируя на обновленные данные айтрекера (и пр) :-(

Попробую переделать vi согласно приложенному варианту.
p.s. По поводу интервала запроса в 10мс не страшно, айтрекер выдает сырые данные с частотой ~90 сэмплов/с, с буфером интервал увеличится немного.
Аватара пользователя
dadreamer

Activity Professionalism Автор
professor
professor
Сообщения: 3926
Зарегистрирован: 17 фев 2013, 16:33
Награды: 4
Версия LabVIEW: 2.5 — 2022
Благодарил (а): 11 раз
Поблагодарили: 126 раз
Контактная информация:

Re: Tobii 4C (устройство для отслеживания направления взгляда)

Сообщение dadreamer »

Похоже, вот тут ошибка.
2022-05-23_20-50-57.jpg
Заводить в DLL надо Event Refnum, а не Registration Refnum. Можете посмотреть пример, как передавать события из DLL в :labview: https://lavag.org/topic/13507-labview-q ... ment=81075 Там, правда, выполняется работа с камерой, но принцип похож (коллбэк используется).
Кроме того, я сильно не уверен, что запостив вот это

Код: Выделить всё

static double g_GazePoint[2] = {0.0};
в :labview: , вы получите именно такой массив. Дело в том, что :labview: хранит данные в своей памяти несколько иначе, чем другие языки. Почитайте статью How LabVIEW Stores Data in Memory. Одномерный массив Double будет выглядеть так:

Код: Выделить всё

/* lv_prolog.h and lv_epilog.h set up the correct alignment for LabVIEW data. */
#include "lv_prolog.h"

/* Typedefs */

typedef struct {
	int32_t dimSize;
	double elt[1];
	} TD1;
typedef TD1 **TD1Hdl;

#include "lv_epilog.h"
Поэтому надо сначала массив из Си привести к массиву LV и потом уже постить через PostLVUserEvent.

Да, IvanLis верно написал, что постоянно UE никто не создаёт. Обычно всегда это делается однократно до цикла, а в самом цикле программа просто ждёт данные из другого цикла или библиотеки.
Аватара пользователя
IvanLis

Activity Professionalism Tutorials Gold Man of the year 2012
Автор
guru
guru
Сообщения: 5462
Зарегистрирован: 02 дек 2009, 17:44
Награды: 7
Версия LabVIEW: 2015, 2016
Откуда: СССР
Благодарил (а): 28 раз
Поблагодарили: 86 раз

Re: Tobii 4C (устройство для отслеживания направления взгляда)

Сообщение IvanLis »

violator писал(а): 23 май 2022, 18:25 Насколько понял в LV не получится быть "ведомым", реагируя на обновленные данные айтрекера (и пр) :-(
.....
p.s. По поводу интервала запроса в 10мс не страшно, айтрекер выдает сырые данные с частотой ~90 сэмплов/с, с буфером интервал увеличится немного.
LabVIEW может быть и ведущим и ведомым, только для этого желательно разные циклы использовать, но не обязательно.
Использование Вами Event, это как раз и есть попытка сделать реакцию на внешние события, может в этом и есть смысл, но мне не совсем понятен, я сторонник того, что "хвост не виляет собакой".

10ms = 100 запросов/сек, у Вас буфер даже не обновиться, так что опрашивать чаще 10 раз в сек (100ms) наверное смысла нет.

Дело в том, что Вы неверное используете событие и да бы не усложнять программу, я предлагаю более простое решение.
1. При инициализации, Вы вызываете функцию bool connectToSauronEye(int gazeBufferLength = 10) из dll, которая начинает опрашивать устройство и используя кольцевой буфер осреднять данные, размер буфера желательно ограничить (например 10 последних отсчетов). Этот буфер живет отдельно от :labview: и данные в нем постоянно обновляются.
2. Когда Вам необходимы данные из буфера, Вы вызываете функцию uint32_t getGazeData(), которая возвращает осредненные координаты курсора. Отображаете пользователю и т.д., Вам же потом захочется, что бы :labview: еще что-то делала, а не только отображала положение курсора.
3. При отключении от устройства, вы вызываете функцию bool disconnectFromSauronEye(), что останавливает буфер и выгружает все лишнее из памяти.

Опять же для упрощения получения данных, можно две координаты U16 (2byte) (думаю от 0 до 65 535 предостаточно для координат) объединить в одно число U32 (4byte) внутри функции, а в :labview: обратно разделить, по этому тип функции uint32. Не думаю, что использование дробного типа double (8byte) для каждой координаты оправдано.
Аватара пользователя
violator
interested
interested
Сообщения: 3
Зарегистрирован: 23 май 2022, 15:09
Версия LabVIEW: 2021
Откуда: Москва
Благодарил (а): 4 раза

Re: Tobii 4C (устройство для отслеживания направления взгляда)

Сообщение violator »

Приветствую
Прошу прощения за поздний ответ.
dadreamer писал(а): 23 май 2022, 19:03 Похоже, вот тут ошибка.
Заводить в DLL надо Event Refnum, а не Registration Refnum. Можете посмотреть пример, как передавать события из DLL в :labview: https://lavag.org/topic/13507-labview-q ... ment=81075 Там, правда, выполняется работа с камерой, но принцип похож (коллбэк используется).
Кроме того, я сильно не уверен, что запостив вот это

Код: Выделить всё

static double g_GazePoint[2] = {0.0};
в :labview: , вы получите именно такой массив. Дело в том, что :labview: хранит данные в своей памяти несколько иначе, чем другие языки. Почитайте статью How LabVIEW Stores Data in Memory. Одномерный массив Double будет выглядеть так:

Код: Выделить всё

/* lv_prolog.h and lv_epilog.h set up the correct alignment for LabVIEW data. */
#include "lv_prolog.h"

/* Typedefs */

typedef struct {
	int32_t dimSize;
	double elt[1];
	} TD1;
typedef TD1 **TD1Hdl;

#include "lv_epilog.h"
Поэтому надо сначала массив из Си привести к массиву LV и потом уже постить через PostLVUserEvent.

Да, IvanLis верно написал, что постоянно UE никто не создаёт. Обычно всегда это делается однократно до цикла, а в самом цикле программа просто ждёт данные из другого цикла или библиотеки.
Переделал:
1. В вызываемую функцию dll завожу Event Refnum.
2. Массив в коде dll привел к указанному выше (правда тип данных координат взгляда заменил на int32_t):

Код: Выделить всё

/* lv_prolog.h and lv_epilog.h set up the correct alignment for LabVIEW data. */
#include "lv_prolog.h"
typedef struct
{
    int32_t dimSize;
    int32_t elt[1];
} TobiiGazeSample;
typedef TobiiGazeSample** HTobiiGazeSample;
#include "lv_epilog.h"

typedef MgErr(_FUNCC* lvUserEvent_t)(LVUserEventRef, void*);

int TOBII_WRAPPER_API getGazeData(void *eventRef)
{
    if (eventRef != 0)
    {
        //post LV event
        LVUserEventRef event = *(LVUserEventRef*)eventRef;

        HTobiiGazeSample hTobiiGaze = (TobiiGazeSample**)DSNewHandle(sizeof(TobiiGazeSample));
        (*hTobiiGaze)->dimSize = 1;
        (*hTobiiGaze)->elt[0]  = (int32_t)g_GazePoint[0];
        (*hTobiiGaze)->elt[1]  = (int32_t)g_GazePoint[1];

        PostLVUserEvent(event, (void*)hTobiiGaze);
        return 0;
    }
    return 1;
}
3. Создание и уничтожение события вынес вне цикла, в цикле делаю запрос данных.

Но, к сожалению, почти всегда при вызове getGazeData() появляется ошибка с кодом 1097 (и, как правило, LV вылетает с исключением вида "Exception: Unknown (0x00000000) at EIP=0x5CE76F56").
Хотя ф-ция возвращает только 0 или 1.
Массив HTobiiGazeSample пробовал создавать и через оператор new и с вызовом DSNewHandle().
Вложения
Desktop Screenshot 2022.05.30 - 15.03.15.50.png
Аватара пользователя
dadreamer

Activity Professionalism Автор
professor
professor
Сообщения: 3926
Зарегистрирован: 17 фев 2013, 16:33
Награды: 4
Версия LabVIEW: 2.5 — 2022
Благодарил (а): 11 раз
Поблагодарили: 126 раз
Контактная информация:

Re: Tobii 4C (устройство для отслеживания направления взгляда)

Сообщение dadreamer »

Для массивов вместо DSNewHandle лучше пользоваться NumericArrayResize, эта функция принимает в учёт выравнивание и, если хэндл нулевой, выделяет новый. Пример, как с этим работать: https://lavag.org/topic/22531-using-the ... ent-139922 (см. содержимое функции DataCallBack). Кроме того, постить данные надо по ссылке (&). Ну, и в конце DSDisposeHandle.
Ответить

Вернуться в «Коммуникация с приложениями»