summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorStanisław Halik <sthalik@misaki.pl>2017-07-16 14:31:01 +0200
committerGitHub <noreply@github.com>2017-07-16 14:31:01 +0200
commit08933e46ed419cb40e0d9740d35161d014b0d48c (patch)
tree5122dc790840e896a0df5e847d5744111c2e508e
parentdc2989a8cedcd506bfa13d16bc57b5a702a204b3 (diff)
parent77cc0ce77eaa693a369b3fdd920dd22f1d5783a1 (diff)
Merge pull request #650 from achipa/s2bot
S2bot :+1:
-rw-r--r--AUTHORS.md1
-rw-r--r--OPENTRACK-LICENSING.txt8
-rw-r--r--README.md2
-rw-r--r--tracker-s2bot/CMakeLists.txt1
-rw-r--r--tracker-s2bot/README.md34
-rw-r--r--tracker-s2bot/ftnoir_tracker_s2bot.cpp114
-rw-r--r--tracker-s2bot/ftnoir_tracker_s2bot.h72
-rw-r--r--tracker-s2bot/ftnoir_tracker_s2bot_dialog.cpp30
-rw-r--r--tracker-s2bot/lang/nl_NL.ts121
-rw-r--r--tracker-s2bot/lang/ru_RU.ts121
-rw-r--r--tracker-s2bot/lang/stub.ts121
-rw-r--r--tracker-s2bot/s2bot-controls.ui300
-rw-r--r--tracker-s2bot/s2bot-res.qrc5
-rw-r--r--tracker-s2bot/s2bot.pngbin0 -> 3943 bytes
14 files changed, 930 insertions, 0 deletions
diff --git a/AUTHORS.md b/AUTHORS.md
index ca3ba840..bf02d78e 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -6,5 +6,6 @@ chronological order:
- Donovan Baarda <<abo@minkirri.apana.org.au>>
- Xavier Hallade <<xavier.hallade@intel.com>>
- Michael Welter <<mw.pub@welter-4d.de>>
+- Attila Csipa <<git@csipa.net>>
See OPENTRACK-LICENSING.txt for licensing information.
diff --git a/OPENTRACK-LICENSING.txt b/OPENTRACK-LICENSING.txt
index 068fb101..2402beae 100644
--- a/OPENTRACK-LICENSING.txt
+++ b/OPENTRACK-LICENSING.txt
@@ -73,6 +73,14 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
+# S2Bot tracker
+
+Copyright (c) 2017, Attila Csipa
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
# EOF
# vim: noai:ts=4:sw=4:tw=79
diff --git a/README.md b/README.md
index 78513e8a..3a34c672 100644
--- a/README.md
+++ b/README.md
@@ -32,6 +32,7 @@ For the latest releases visit: <<https://github.com/opentrack/opentrack/releases
- Windows Phone [tracker](https://github.com/ZanderAdam/OpenTrack.WindowsPhone/wiki) over opentrack UDP protocol
- Arduino with custom firmware
- Intel RealSense 3D cameras (Windows)
+- BBC micro:bit, LEGO, sensortag support via [S2Bot](http://www.picaxe.com/Teaching/Other-Software/Scratch-Helper-Apps/)
# Output
@@ -64,6 +65,7 @@ Don't be afraid to submit an issue/feature request if need arises.
- Xavier Hallade (Intel RealSense tracker author and maintainer)
- furax49 (hatire tracker author)
- Michael Welter (contributor)
+- Attila Csipa (Micro:Bit author)
# Thanks
diff --git a/tracker-s2bot/CMakeLists.txt b/tracker-s2bot/CMakeLists.txt
new file mode 100644
index 00000000..6d162828
--- /dev/null
+++ b/tracker-s2bot/CMakeLists.txt
@@ -0,0 +1 @@
+otr_module(tracker-s2bot)
diff --git a/tracker-s2bot/README.md b/tracker-s2bot/README.md
new file mode 100644
index 00000000..140bf006
--- /dev/null
+++ b/tracker-s2bot/README.md
@@ -0,0 +1,34 @@
+# S2Bot plugin for opentrack
+This is a tracker providing 3D head tracking using the S2Bot helper application.
+
+S2Bot is originally a plugin that connect embedded devices with the [Scratch programming environment](https://scratch.mit.edu/). This plugin emulates the Scratch interface to get access to the sensor data from the various boards. It was developed and tested using the [micro:bit](https://www.microbit.co.uk) board, but should work with any of the S2Bot supported sensor boards that have accelerometers and/or magnetometers. S2Bot is available for Windows, Mac and Linux.
+
+More information on S2Bot can be found [here](http://www.picaxe.com/Teaching/Other-Software/Scratch-Helper-Apps/)
+
+# Installation and usage
+
+1. Download & install S2Bot
+2. Connect BL112 USB dongle
+3. Connect micro:bit to computer via USB
+4. Start S2Bot
+5. Flash micro:bit with s2bot hex firmware from the S2Bot app
+6. Select micro:bit from device selection dropdown
+7. Press scan and select device (the device connection LED should go green after a few seconds, and accelerometer data should appear in the S2Bot status window)
+8. Start opentrack
+9. Select S2Bot plugin as input
+10. Press start to start tracking (the Scratch connection LED will go green as S2Bot treats opentract as a Scratch environment)
+
+# ISC License
+Copyright (c) 2017, Attila Csipa
+
+ Author: Attila Csipa <git@csipa.net>
+
+Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# Attribution
+
+S2Bot and PICAXE® products are developed and distributed by Revolution Education Ltd
+BBC micro:bit is created by the British Broadcasting Corporation or BBC partners.
+Scratch is created by the MIT Media Lab
diff --git a/tracker-s2bot/ftnoir_tracker_s2bot.cpp b/tracker-s2bot/ftnoir_tracker_s2bot.cpp
new file mode 100644
index 00000000..63e30075
--- /dev/null
+++ b/tracker-s2bot/ftnoir_tracker_s2bot.cpp
@@ -0,0 +1,114 @@
+#include "ftnoir_tracker_s2bot.h"
+#include "api/plugin-api.hpp"
+
+#include <cinttypes>
+#include <algorithm>
+#include <cmath>
+#include <QNetworkRequest>
+#include <QNetworkReply>
+
+tracker_s2bot::tracker_s2bot() : pose { 0,0,0, 0,0,0 }, m_nam (std::make_unique<QNetworkAccessManager>())
+{
+}
+
+tracker_s2bot::~tracker_s2bot()
+{
+ requestInterruption();
+ wait();
+}
+
+template<typename t>
+static const t bound(t datum, t least, t max)
+{
+ if (datum < least)
+ return least;
+ if (datum > max)
+ return max;
+ return datum;
+}
+
+void tracker_s2bot::run() {
+#pragma pack(push, 1)
+ struct {
+ uint8_t pad1;
+ uint8_t flags;
+ float fl[12];
+ } data;
+#pragma pack(pop)
+ enum F {
+ flag_Raw = 1 << 0,
+ flag_Orient = 1 << 1,
+ Mask = flag_Raw | flag_Orient
+ };
+
+ if (s.freq == 0) s.freq = 10;
+ timer.setInterval(1000.0/s.freq);
+ timer.setSingleShot(false);
+ connect(&timer, &QTimer::timeout, [this]() {
+ auto reply = m_nam->get(QNetworkRequest(QUrl("http://localhost:17317/poll")));
+ connect(reply, &QNetworkReply::finished, [this, reply]() {
+ if (reply->error() == QNetworkReply::NoError) {
+ qDebug() << "Request submitted OK";
+ }
+ else {
+ qWarning() << "Request bounced:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) << reply->errorString();
+ return;
+ }
+ QByteArray ba = reply->readAll();
+ QStringList slist = QString(ba).split(QRegExp("[\r\n]"), QString::SkipEmptyParts);
+ int order[] = {
+ bound<int>(s.idx_x, 0, 3),
+ bound<int>(s.idx_y, 0, 3),
+ bound<int>(s.idx_z, 0, 3)
+ };
+ double orient[4] = { 0, 0, 0, 0 };
+ static const int add_cbx[] = {
+ 0,
+ 90,
+ -90,
+ 180,
+ -180,
+ };
+ int indices[] = { s.add_yaw, s.add_pitch, s.add_roll };
+ for (auto line : slist) {
+ QStringList keyval = line.split(" ");
+ if (keyval.count() < 2) continue;
+ if (keyval[0].startsWith("accelerometerZ")) orient[0] = keyval[1].toInt();
+ else if (keyval[0].startsWith("accelerometerY")) orient[1] = keyval[1].toInt();
+ else if (keyval[0].startsWith("accelerometerX")) orient[2] = keyval[1].toInt();
+ else if (keyval[0].startsWith("bearing")) orient[3] = keyval[1].toInt();
+ }
+ QMutexLocker foo(&mtx);
+ static constexpr double r2d = 180 / M_PI;
+ for (int i = 0; i < 3; i++)
+ {
+ int val = 0;
+ int idx = indices[order[i]];
+ if (idx >= 0 && idx < (int)(sizeof(add_cbx) / sizeof(*add_cbx)))
+ val = add_cbx[idx];
+ pose[Yaw + i] = orient[order[i]] + val; // * r2d if it was radians
+ }
+ reply->close();
+ reply->deleteLater();
+ });
+ });
+ timer.start();
+ exec();
+}
+
+void tracker_s2bot::start_tracker(QFrame*)
+{
+ start();
+ timer.moveToThread(this);
+}
+
+void tracker_s2bot::data(double *data)
+{
+ QMutexLocker foo(&mtx);
+
+ data[Yaw] = pose[Yaw];
+ data[Pitch] = pose[Pitch];
+ data[Roll] = pose[Roll];
+}
+
+OPENTRACK_DECLARE_TRACKER(tracker_s2bot, dialog_s2bot, meta_s2bot)
diff --git a/tracker-s2bot/ftnoir_tracker_s2bot.h b/tracker-s2bot/ftnoir_tracker_s2bot.h
new file mode 100644
index 00000000..d4d3fd2a
--- /dev/null
+++ b/tracker-s2bot/ftnoir_tracker_s2bot.h
@@ -0,0 +1,72 @@
+/* Copyright (c) 2014 Stanislaw Halik <sthalik@misaki.pl>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ */
+#pragma once
+#include <cinttypes>
+#include <QUdpSocket>
+#include <QThread>
+#include <QNetworkAccessManager>
+#include <QTimer>
+#include "ui_s2bot-controls.h"
+#include "api/plugin-api.hpp"
+#include "options/options.hpp"
+using namespace options;
+
+struct settings : opts {
+ value<int> freq, idx_x, idx_y, idx_z;
+ value<int> add_yaw, add_pitch, add_roll;
+ settings() :
+ opts("s2bot-tracker"),
+ freq(b, "freq", 30),
+ idx_x(b, "axis-index-x", 0),
+ idx_y(b, "axis-index-y", 1),
+ idx_z(b, "axis-index-z", 2),
+ add_yaw(b, "add-yaw-degrees", 0),
+ add_pitch(b, "add-pitch-degrees", 0),
+ add_roll(b, "add-roll-degrees", 0)
+ {}
+};
+
+class tracker_s2bot : public ITracker, private QThread
+{
+public:
+ tracker_s2bot();
+ ~tracker_s2bot() override;
+ void start_tracker(QFrame *) override;
+ void data(double *data) override;
+protected:
+ void run() override;
+private:
+ double pose[6];
+ QTimer timer;
+ settings s;
+ QMutex mtx;
+ std::unique_ptr<QNetworkAccessManager> m_nam;
+
+};
+
+class dialog_s2bot : public ITrackerDialog
+{
+ Q_OBJECT
+public:
+ dialog_s2bot();
+ void register_tracker(ITracker *) override {}
+ void unregister_tracker() override {}
+private:
+ Ui::UI_s2bot_dialog ui;
+ settings s;
+private slots:
+ void doOK();
+ void doCancel();
+};
+
+class meta_s2bot : public Metadata
+{
+public:
+ QString name() { return QString(QCoreApplication::translate("meta_s2bot", "S2Bot receiver")); }
+ QIcon icon() { return QIcon(":/s2bot.png"); }
+};
+
diff --git a/tracker-s2bot/ftnoir_tracker_s2bot_dialog.cpp b/tracker-s2bot/ftnoir_tracker_s2bot_dialog.cpp
new file mode 100644
index 00000000..2255156b
--- /dev/null
+++ b/tracker-s2bot/ftnoir_tracker_s2bot_dialog.cpp
@@ -0,0 +1,30 @@
+#include "ftnoir_tracker_s2bot.h"
+#include "api/plugin-api.hpp"
+
+dialog_s2bot::dialog_s2bot()
+{
+ ui.setupUi(this);
+
+ connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK()));
+ connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel()));
+
+ tie_setting(s.freq, ui.freq);
+ tie_setting(s.idx_x, ui.input_x);
+ tie_setting(s.idx_y, ui.input_y);
+ tie_setting(s.idx_z, ui.input_z);
+
+ tie_setting(s.add_yaw, ui.add_yaw);
+ tie_setting(s.add_pitch, ui.add_pitch);
+ tie_setting(s.add_roll, ui.add_roll);
+}
+
+void dialog_s2bot::doOK() {
+ s.b->save();
+ close();
+}
+
+void dialog_s2bot::doCancel()
+{
+ close();
+}
+
diff --git a/tracker-s2bot/lang/nl_NL.ts b/tracker-s2bot/lang/nl_NL.ts
new file mode 100644
index 00000000..dc59f914
--- /dev/null
+++ b/tracker-s2bot/lang/nl_NL.ts
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="nl_NL">
+<context>
+ <name>UI_s2bot_dialog</name>
+ <message>
+ <location filename="../s2bot-controls.ui" line="+17"/>
+ <source>Tracker settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+31"/>
+ <source>UDP port</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+20"/>
+ <source>Axis order</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+6"/>
+ <source>output yaw</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+8"/>
+ <location line="+19"/>
+ <location line="+26"/>
+ <source>input yaw</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-40"/>
+ <location line="+19"/>
+ <location line="+26"/>
+ <source>input pitch</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-40"/>
+ <location line="+19"/>
+ <location line="+26"/>
+ <source>input roll</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-18"/>
+ <source>output pitch</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+26"/>
+ <source>output roll</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+10"/>
+ <source>Add to axis</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+6"/>
+ <source>yaw</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+8"/>
+ <location line="+43"/>
+ <location line="+29"/>
+ <source>0</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-67"/>
+ <location line="+43"/>
+ <location line="+29"/>
+ <source>+90</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-67"/>
+ <location line="+43"/>
+ <location line="+29"/>
+ <source>-90</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-67"/>
+ <location line="+43"/>
+ <location line="+29"/>
+ <source>+180</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-67"/>
+ <location line="+43"/>
+ <location line="+29"/>
+ <source>-180</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-64"/>
+ <source>pitch</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+7"/>
+ <source>roll</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>meta_freepie</name>
+ <message>
+ <location filename="../ftnoir_tracker_s2bot.h" line="+66"/>
+ <source>FreePIE UDP receiver</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-s2bot/lang/ru_RU.ts b/tracker-s2bot/lang/ru_RU.ts
new file mode 100644
index 00000000..0505d28d
--- /dev/null
+++ b/tracker-s2bot/lang/ru_RU.ts
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="ru_RU">
+<context>
+ <name>UI_s2bot_dialog</name>
+ <message>
+ <location filename="../s2bot-controls.ui" line="+17"/>
+ <source>Tracker settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+31"/>
+ <source>UDP port</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+20"/>
+ <source>Axis order</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+6"/>
+ <source>output yaw</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+8"/>
+ <location line="+19"/>
+ <location line="+26"/>
+ <source>input yaw</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-40"/>
+ <location line="+19"/>
+ <location line="+26"/>
+ <source>input pitch</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-40"/>
+ <location line="+19"/>
+ <location line="+26"/>
+ <source>input roll</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-18"/>
+ <source>output pitch</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+26"/>
+ <source>output roll</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+10"/>
+ <source>Add to axis</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+6"/>
+ <source>yaw</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+8"/>
+ <location line="+43"/>
+ <location line="+29"/>
+ <source>0</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-67"/>
+ <location line="+43"/>
+ <location line="+29"/>
+ <source>+90</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-67"/>
+ <location line="+43"/>
+ <location line="+29"/>
+ <source>-90</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-67"/>
+ <location line="+43"/>
+ <location line="+29"/>
+ <source>+180</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-67"/>
+ <location line="+43"/>
+ <location line="+29"/>
+ <source>-180</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-64"/>
+ <source>pitch</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+7"/>
+ <source>roll</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>meta_freepie</name>
+ <message>
+ <location filename="../ftnoir_tracker_s2bot.h" line="+66"/>
+ <source>FreePIE UDP receiver</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-s2bot/lang/stub.ts b/tracker-s2bot/lang/stub.ts
new file mode 100644
index 00000000..3550bdae
--- /dev/null
+++ b/tracker-s2bot/lang/stub.ts
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1">
+<context>
+ <name>UI_s2bot_dialog</name>
+ <message>
+ <location filename="../s2bot-controls.ui" line="+17"/>
+ <source>Tracker settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+31"/>
+ <source>Frequency</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+20"/>
+ <source>Axis order</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+6"/>
+ <source>output yaw</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+8"/>
+ <location line="+19"/>
+ <location line="+26"/>
+ <source>input yaw</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-40"/>
+ <location line="+19"/>
+ <location line="+26"/>
+ <source>input pitch</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-40"/>
+ <location line="+19"/>
+ <location line="+26"/>
+ <source>input roll</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-18"/>
+ <source>output pitch</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+26"/>
+ <source>output roll</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+10"/>
+ <source>Add to axis</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+6"/>
+ <source>yaw</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+8"/>
+ <location line="+43"/>
+ <location line="+29"/>
+ <source>0</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-67"/>
+ <location line="+43"/>
+ <location line="+29"/>
+ <source>+90</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-67"/>
+ <location line="+43"/>
+ <location line="+29"/>
+ <source>-90</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-67"/>
+ <location line="+43"/>
+ <location line="+29"/>
+ <source>+180</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-67"/>
+ <location line="+43"/>
+ <location line="+29"/>
+ <source>-180</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-64"/>
+ <source>pitch</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+7"/>
+ <source>roll</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>meta_freepie</name>
+ <message>
+ <location filename="../ftnoir_tracker_freepie-udp.h" line="+66"/>
+ <source>FreePIE UDP receiver</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-s2bot/s2bot-controls.ui b/tracker-s2bot/s2bot-controls.ui
new file mode 100644
index 00000000..9124ddd9
--- /dev/null
+++ b/tracker-s2bot/s2bot-controls.ui
@@ -0,0 +1,300 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>UI_s2bot_dialog</class>
+ <widget class="QWidget" name="UI_s2bot_dialog">
+ <property name="windowModality">
+ <enum>Qt::NonModal</enum>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>227</width>
+ <height>372</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Tracker settings</string>
+ </property>
+ <property name="windowIcon">
+ <iconset>
+ <normaloff>../gui/images/facetracknoir.png</normaloff>../gui/images/facetracknoir.png</iconset>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QFrame" name="frame">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="label_5">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Update frequency (Hz)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="freq">
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>120</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Axis order</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0" rowspan="2">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>output yaw</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="input_x">
+ <item>
+ <property name="text">
+ <string>input yaw</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>input pitch</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>input roll</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>input bearing</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="1" column="1" rowspan="2">
+ <widget class="QComboBox" name="input_y">
+ <item>
+ <property name="text">
+ <string>input yaw</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>input pitch</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>input roll</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>input bearing</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="2" column="0" rowspan="2">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>output pitch</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1" rowspan="2">
+ <widget class="QComboBox" name="input_z">
+ <item>
+ <property name="text">
+ <string>input yaw</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>input pitch</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>input roll</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>input bearing</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>output roll</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="title">
+ <string>Add to axis</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>yaw</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="add_yaw">
+ <item>
+ <property name="text">
+ <string>0</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>+90</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>-90</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>+180</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>-180</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_6">
+ <property name="text">
+ <string>pitch</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_9">
+ <property name="text">
+ <string>roll</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="add_pitch">
+ <item>
+ <property name="text">
+ <string>0</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>+90</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>-90</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>+180</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>-180</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QComboBox" name="add_roll">
+ <item>
+ <property name="text">
+ <string>0</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>+90</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>-90</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>+180</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>-180</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+ <slots>
+ <slot>startEngineClicked()</slot>
+ <slot>stopEngineClicked()</slot>
+ <slot>cameraSettingsClicked()</slot>
+ </slots>
+</ui>
diff --git a/tracker-s2bot/s2bot-res.qrc b/tracker-s2bot/s2bot-res.qrc
new file mode 100644
index 00000000..40fc3aa9
--- /dev/null
+++ b/tracker-s2bot/s2bot-res.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>s2bot.png</file>
+ </qresource>
+</RCC>
diff --git a/tracker-s2bot/s2bot.png b/tracker-s2bot/s2bot.png
new file mode 100644
index 00000000..1501bf13
--- /dev/null
+++ b/tracker-s2bot/s2bot.png
Binary files differ