diff options
| -rw-r--r-- | tracker-tobii/CMakeLists.txt | 16 | ||||
| -rw-r--r-- | tracker-tobii/ftnoir_tracker_tobii.cpp | 60 | ||||
| -rw-r--r-- | tracker-tobii/ftnoir_tracker_tobii.h | 50 | ||||
| -rw-r--r-- | tracker-tobii/ftnoir_tracker_tobii_controls.ui | 56 | ||||
| -rw-r--r-- | tracker-tobii/ftnoir_tracker_tobii_dialog.cpp | 19 | ||||
| -rw-r--r-- | tracker-tobii/lang/nl_NL.ts | 18 | ||||
| -rw-r--r-- | tracker-tobii/lang/ru_RU.ts | 18 | ||||
| -rw-r--r-- | tracker-tobii/lang/stub.ts | 18 | ||||
| -rw-r--r-- | tracker-tobii/lang/zh_CN.ts | 18 | ||||
| -rw-r--r-- | tracker-tobii/thread.cpp | 118 | ||||
| -rw-r--r-- | tracker-tobii/thread.hpp | 37 | 
11 files changed, 428 insertions, 0 deletions
| diff --git a/tracker-tobii/CMakeLists.txt b/tracker-tobii/CMakeLists.txt new file mode 100644 index 00000000..4593ae4e --- /dev/null +++ b/tracker-tobii/CMakeLists.txt @@ -0,0 +1,16 @@ +# https://developer.tobii.com/download-packages/stream-engine-4-1-0-for-windows-x86 +# https://developer.tobii.com/download-packages/stream-engine-4-1-0-for-windows-x64 +if(SDK_TOBII) +    otr_module(tracker-tobii) +    target_include_directories(${self} SYSTEM PRIVATE "${SDK_TOBII}/include") + +    set(dll "${SDK_TOBII}/lib/tobii/tobii_stream_engine.dll") +    set(lib "${SDK_TOBII}/lib/tobii/tobii_stream_engine.lib") + +    message(${self}) +    message(${dll}) +    message(${lib}) + +    install(FILES ${dll} DESTINATION ${opentrack-hier-pfx}) +    target_link_libraries(${self} ${lib}) +endif() diff --git a/tracker-tobii/ftnoir_tracker_tobii.cpp b/tracker-tobii/ftnoir_tracker_tobii.cpp new file mode 100644 index 00000000..f0d66ae4 --- /dev/null +++ b/tracker-tobii/ftnoir_tracker_tobii.cpp @@ -0,0 +1,60 @@ +#include "ftnoir_tracker_tobii.h" +#include "api/plugin-api.hpp" +#include "compat/math.hpp" + +module_status tobii::start_tracker(QFrame*) +{ +    t.start(); +    return status_ok(); +} + +void tobii::data(double *data) +{ +    if (t.head_pose) { +        tobii_head_pose_t p = *t.head_pose; +        if (p.position_validity == TOBII_VALIDITY_VALID) { +            if (center_pose.position_validity == TOBII_VALIDITY_VALID) { +                p.position_xyz[0] = p.position_xyz[0] - center_pose.position_xyz[0]; +                p.position_xyz[1] = p.position_xyz[1] - center_pose.position_xyz[1]; +                p.position_xyz[2] = p.position_xyz[2] - center_pose.position_xyz[2]; +            } +            data[0] = clamp(p.position_xyz[0] * 30.0 / 300.0, -30.0, 30.0); +            data[1] = clamp(p.position_xyz[1] * 30.0 / 300.0, -30.0, 30.0); +            data[2] = clamp(p.position_xyz[2] * 30.0 / 300.0, -30.0, 30.0); +        } + +        double max_yaw = 90.0; +        if (p.rotation_validity_xyz[1] == TOBII_VALIDITY_VALID) { +            if (center_pose.rotation_validity_xyz[1] == TOBII_VALIDITY_VALID) { +                p.rotation_xyz[1] = p.rotation_xyz[1] - center_pose.rotation_xyz[1]; +            } +            data[3] = clamp(p.rotation_xyz[1] * 100.0 * max_yaw / 90.0, -max_yaw, max_yaw); +        } + +        double max_pitch = 90.0; +        if (p.rotation_validity_xyz[0] == TOBII_VALIDITY_VALID) { +            if (center_pose.rotation_validity_xyz[0] == TOBII_VALIDITY_VALID) { +                p.rotation_xyz[0] = p.rotation_xyz[0] - center_pose.rotation_xyz[0]; +            } +            data[4] = clamp(p.rotation_xyz[0] * 100.0 * max_pitch / 90.0, -max_pitch, max_pitch); +        } + +        double max_roll = 90.0; +        if (p.rotation_validity_xyz[2] == TOBII_VALIDITY_VALID) { +            if (center_pose.rotation_validity_xyz[2] == TOBII_VALIDITY_VALID) { +                p.rotation_xyz[2] = p.rotation_xyz[2] - center_pose.rotation_xyz[2]; +            } +            data[5] = clamp(p.rotation_xyz[2] * 100.0 * max_roll / 90.0, -max_roll, max_roll); +        } +    } +} + +bool tobii::center  () +{ +    if (t.head_pose) { +        center_pose = *t.head_pose; +    } +    return false; +} + +OPENTRACK_DECLARE_TRACKER(tobii, dialog_tobii, tobiiDll) diff --git a/tracker-tobii/ftnoir_tracker_tobii.h b/tracker-tobii/ftnoir_tracker_tobii.h new file mode 100644 index 00000000..bd7a04a2 --- /dev/null +++ b/tracker-tobii/ftnoir_tracker_tobii.h @@ -0,0 +1,50 @@ +#pragma once +#include "ui_ftnoir_tracker_tobii_controls.h" +#include <QComboBox> +#include <QCheckBox> +#include <QSpinBox> +#include <QMessageBox> +#include <QSettings> +#include <QList> +#include <QFrame> +#include <QStringList> +#include <cmath> +#include "api/plugin-api.hpp" +#include "options/options.hpp" + +#include "thread.hpp" + +class tobii : public ITracker +{ +public: +    ~tobii(); +    module_status start_tracker(QFrame*) override; +    void data(double* data) override; +    virtual bool center() override; +private: +    tobii_thread t; +    tobii_head_pose_t center_pose; +}; + +class dialog_tobii: public ITrackerDialog +{ +    Q_OBJECT +public: +    dialog_tobii(); +    ~dialog_tobii() = default; +    void register_tracker(ITracker *) {} +    void unregister_tracker() {} +    Ui::UITobiiControls ui; +    tobii* tracker; +private slots: +    void doOK(); +    void doCancel(); +}; + +class tobiiDll : public Metadata +{ +    Q_OBJECT + +    QString name() { return tr("Tobii input"); } +    QIcon icon() { return QIcon(":/images/opentrack.png"); } +}; diff --git a/tracker-tobii/ftnoir_tracker_tobii_controls.ui b/tracker-tobii/ftnoir_tracker_tobii_controls.ui new file mode 100644 index 00000000..6cd00121 --- /dev/null +++ b/tracker-tobii/ftnoir_tracker_tobii_controls.ui @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> +    <class>UITobiiControls</class> +    <widget class="QWidget" name="UITobiiControls"> +        <property name="windowModality"> +            <enum>Qt::NonModal</enum> +        </property> +        <property name="geometry"> +            <rect> +                <x>0</x> +                <y>0</y> +                <width>498</width> +                <height>303</height> +            </rect> +        </property> +        <property name="windowTitle"> +            <string>Tobii settings</string> +        </property> +        <property name="windowIcon"> +            <iconset> +                <normaloff>../gui/images/opentrack.png</normaloff>../gui/images/opentrack.png +            </iconset> +        </property> +        <layout class="QVBoxLayout" name="verticalLayout"> +            <property name="leftMargin"> +                <number>12</number> +            </property> +            <property name="topMargin"> +                <number>6</number> +            </property> +            <property name="rightMargin"> +                <number>12</number> +            </property> +            <property name="bottomMargin"> +                <number>6</number> +            </property> +            <item> +                <widget class="QDialogButtonBox" name="buttonBox"> +                    <property name="standardButtons"> +                        <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> +                    </property> +                </widget> +            </item> +        </layout> +    </widget> +    <tabstops> +        <tabstop>buttonBox</tabstop> +    </tabstops> +    <resources/> +    <connections/> +    <slots> +        <slot>startEngineClicked()</slot> +        <slot>stopEngineClicked()</slot> +        <slot>cameraSettingsClicked()</slot> +    </slots> +</ui> diff --git a/tracker-tobii/ftnoir_tracker_tobii_dialog.cpp b/tracker-tobii/ftnoir_tracker_tobii_dialog.cpp new file mode 100644 index 00000000..5cd4bad0 --- /dev/null +++ b/tracker-tobii/ftnoir_tracker_tobii_dialog.cpp @@ -0,0 +1,19 @@ +#include "ftnoir_tracker_tobii.h" +#include "api/plugin-api.hpp" + +dialog_tobii::dialog_tobii() : tracker(nullptr) +{ +    ui.setupUi( this ); + +    // Connect Qt signals to member-functions +    connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK())); +    connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel())); +} + +void dialog_tobii::doOK() { +    close(); +} + +void dialog_tobii::doCancel() { +    close(); +} diff --git a/tracker-tobii/lang/nl_NL.ts b/tracker-tobii/lang/nl_NL.ts new file mode 100644 index 00000000..45e4974f --- /dev/null +++ b/tracker-tobii/lang/nl_NL.ts @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="nl_NL"> +<context> +    <name>UITobiiControls</name> +    <message> +        <source>Tobii settings</source> +        <translation type="unfinished"></translation> +    </message> +</context> +<context> +    <name>tobiiDll</name> +    <message> +        <source>Tobii input</source> +        <translation type="unfinished"></translation> +    </message> +</context> +</TS> diff --git a/tracker-tobii/lang/ru_RU.ts b/tracker-tobii/lang/ru_RU.ts new file mode 100644 index 00000000..ee73b375 --- /dev/null +++ b/tracker-tobii/lang/ru_RU.ts @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="ru_RU"> +<context> +    <name>UITobiiControls</name> +    <message> +        <source>Tobii settings</source> +        <translation type="unfinished"></translation> +    </message> +</context> +<context> +    <name>tobiiDll</name> +    <message> +        <source>Tobii input</source> +        <translation type="unfinished"></translation> +    </message> +</context> +</TS> diff --git a/tracker-tobii/lang/stub.ts b/tracker-tobii/lang/stub.ts new file mode 100644 index 00000000..cd8a2bdc --- /dev/null +++ b/tracker-tobii/lang/stub.ts @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1"> +<context> +    <name>UITobiiControls</name> +    <message> +        <source>Tobii settings</source> +        <translation type="unfinished"></translation> +    </message> +</context> +<context> +    <name>tobiiDll</name> +    <message> +        <source>Tobii input</source> +        <translation type="unfinished"></translation> +    </message> +</context> +</TS> diff --git a/tracker-tobii/lang/zh_CN.ts b/tracker-tobii/lang/zh_CN.ts new file mode 100644 index 00000000..cd8a2bdc --- /dev/null +++ b/tracker-tobii/lang/zh_CN.ts @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1"> +<context> +    <name>UITobiiControls</name> +    <message> +        <source>Tobii settings</source> +        <translation type="unfinished"></translation> +    </message> +</context> +<context> +    <name>tobiiDll</name> +    <message> +        <source>Tobii input</source> +        <translation type="unfinished"></translation> +    </message> +</context> +</TS> diff --git a/tracker-tobii/thread.cpp b/tracker-tobii/thread.cpp new file mode 100644 index 00000000..f9fd2c01 --- /dev/null +++ b/tracker-tobii/thread.cpp @@ -0,0 +1,118 @@ +#include "thread.hpp" +#include "compat/sleep.hpp" + +tobii_thread::tobii_thread() +{ +    head_pose = new tobii_head_pose_t(); +    connect(this, &tobii_thread::tobii_ready_signal, this, &tobii_thread::tobii_ready_signal_impl, Qt::QueuedConnection); +    connect(this, &tobii_thread::tobii_error_signal, this, &tobii_thread::tobii_error_signal_impl, Qt::QueuedConnection); +} + +tobii_thread::~tobii_thread() +{ +    if (device) tobii_device_destroy(device); +    if (api) tobii_api_destroy(api); +    exit_thread = true; +    terminate(); +    wait(); +} + +void tobii_thread::run() +{ +    /* See https://developer.tobii.com/consumer-eye-trackers/stream-engine/ */ +    if (tobii_api_create(&api, nullptr, nullptr) != TOBII_ERROR_NO_ERROR) +    { +        emit tobii_error_signal("Failed to initialize the Tobii Stream Engine API."); +    } + +    std::vector<std::string> devices; +    if (tobii_enumerate_local_device_urls(api, +        [](char const* url, void* user_data) +        { +            auto list = (std::vector<std::string>*) user_data; +            list->push_back(url); +        }, &devices) != TOBII_ERROR_NO_ERROR) +    { +        emit tobii_error_signal("Failed to enumerate devices."); +    } + +    if (devices.size() == 0) +    { +        tobii_api_destroy(api); +        emit tobii_error_signal("No stream engine compatible device(s) found."); +    } +    std::string selected_device = devices[0]; + +    unsigned int retry = 0; +    tobii_error_t tobii_error = TOBII_ERROR_NO_ERROR; +    do +    { +        tobii_error = tobii_device_create(api, selected_device.c_str(), TOBII_FIELD_OF_USE_INTERACTIVE, &device); +        if ((tobii_error != TOBII_ERROR_CONNECTION_FAILED) && (tobii_error != TOBII_ERROR_FIRMWARE_UPGRADE_IN_PROGRESS)) break; +        portable::sleep(interval); +        ++retry; +    } while (retry < retries); +    if (tobii_error != TOBII_ERROR_NO_ERROR) { +        tobii_api_destroy(api); +        emit tobii_error_signal("Failed to connect to device."); +    } + +    emit tobii_ready_signal(); + +    tobii_error = TOBII_ERROR_NO_ERROR; +    while (!exit_thread) +    { +        tobii_error = tobii_wait_for_callbacks(1, &device); +        if (tobii_error == TOBII_ERROR_TIMED_OUT) continue; +        else if (tobii_error != TOBII_ERROR_NO_ERROR) +        { +            emit tobii_error_signal("tobii_wait_for_callbacks failed."); +        } +         +        tobii_error = tobii_device_process_callbacks(device); +        if (tobii_error == TOBII_ERROR_CONNECTION_FAILED) +        { +            unsigned int retry = 0; +            auto tobii_error = TOBII_ERROR_NO_ERROR; +            do +            { +                tobii_error = tobii_device_reconnect(device); +                if ((tobii_error != TOBII_ERROR_CONNECTION_FAILED) && (tobii_error != TOBII_ERROR_FIRMWARE_UPGRADE_IN_PROGRESS)) break; +                portable::sleep(interval); +                ++retry; +            } while (retry < retries); +            if (tobii_error != TOBII_ERROR_NO_ERROR) +            { +                emit tobii_error_signal("Connection was lost and reconnection failed."); +            } +            continue; +        } +        else if (tobii_error != TOBII_ERROR_NO_ERROR) +        { +            emit tobii_error_signal("tobii_device_process_callbacks failed."); +        } +    } +} + +void tobii_thread::tobii_error_signal_impl(QString error_message) +{ +    //TODO: log? terminate? +} + +void tobii_thread::tobii_ready_signal_impl() +{ +    if (tobii_head_pose_subscribe(device, [](tobii_head_pose_t const* head_pose, void* user_data) { + +        if ((*head_pose).position_validity != TOBII_VALIDITY_VALID +            || (*head_pose).rotation_validity_xyz[0] != TOBII_VALIDITY_VALID +            || (*head_pose).rotation_validity_xyz[1] != TOBII_VALIDITY_VALID +            || (*head_pose).rotation_validity_xyz[2] != TOBII_VALIDITY_VALID) return; + +        tobii_head_pose_t* tobii_head_pose_storage = (tobii_head_pose_t*)user_data; +        *tobii_head_pose_storage = *head_pose; + +        }, head_pose) != TOBII_ERROR_NO_ERROR) +    { +        emit tobii_error_signal("Failed to subscribe to head pose stream."); +    } +} diff --git a/tracker-tobii/thread.hpp b/tracker-tobii/thread.hpp new file mode 100644 index 00000000..a2838272 --- /dev/null +++ b/tracker-tobii/thread.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include <QThread> +#include <QCoreApplication> + +#include <tobii/tobii.h> +#include <tobii/tobii_streams.h> +#include <atomic> +#include <vector> +#include <string> + +class tobii_thread : public QThread +{ +    Q_OBJECT +    void run() override; + +signals: +    void tobii_error_signal(QString error_message); +    void tobii_ready_signal(); + +public: +    tobii_thread(); +    ~tobii_thread() override; + +    tobii_head_pose_t* head_pose; + +private: +    tobii_api_t* api; +    tobii_device_t* device; +     +    const unsigned int retries = 300; +    const unsigned int interval = 100; +    std::atomic<bool> exit_thread = false; + +    void tobii_error_signal_impl(QString error_message); +    void tobii_ready_signal_impl(); +}; | 
