diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..3b5685d
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "shared-libraries"]
+ path = shared-libraries
+ url = https://git.itsblue.de/ScStw/shared-libraries/
diff --git a/ScStwMonitor.pro b/ScStwMonitor.pro
new file mode 100644
index 0000000..0d46679
--- /dev/null
+++ b/ScStwMonitor.pro
@@ -0,0 +1,14 @@
+TEMPLATE = subdirs
+CONFIG += ordered console
+
+SUBDIRS += \
+ ScStwLibraries \
+ ScStwMonitorSrc
+
+ScStwMonitorSrc.depends = Qt-Secret
+ScStwLibraries.file = shared-libraries/ScStwLibraries/ScStwLibraries.pro
+
+contains(QMAKE_CXX, .*raspbian.*arm.*):{
+ GLOBAL_TARGET_PATH = "/home/pi/ScStwMonitor"
+ cache(GLOBAL_TARGET_PATH, set)
+}
diff --git a/ScStwMonitor.pro.user b/ScStwMonitor.pro.user
new file mode 100644
index 0000000..71c8ca4
--- /dev/null
+++ b/ScStwMonitor.pro.user
@@ -0,0 +1,1000 @@
+
+
+
+
+
+ EnvironmentId
+ {8b2b329f-2b96-47e0-8e3b-213b44b4afec}
+
+
+ ProjectExplorer.Project.ActiveTarget
+ 0
+
+
+ ProjectExplorer.Project.EditorSettings
+
+ true
+ false
+ true
+
+ Cpp
+
+ CppGlobal
+
+
+
+ QmlJS
+
+ QmlJSGlobal
+
+
+ 2
+ UTF-8
+ false
+ 4
+ false
+ 80
+ true
+ true
+ 1
+ true
+ false
+ 0
+ true
+ true
+ 0
+ 8
+ true
+ 1
+ true
+ true
+ true
+ false
+
+
+
+ ProjectExplorer.Project.PluginSettings
+
+
+ true
+ Builtin.Questionable
+
+ true
+ Builtin.DefaultTidyAndClazy
+ 4
+
+
+
+ true
+
+
+
+
+ ProjectExplorer.Project.Target.0
+
+ Android for armeabi-v7a (Clang Qt 5.12.6 for Android ARMv7)
+ Android for armeabi-v7a (Clang Qt 5.12.6 for Android ARMv7)
+ {9a47ee8f-1702-4ae9-96f9-7c13c7a1f9b3}
+ 0
+ 0
+ 0
+
+ true
+ 0
+ /home/dorian/Qt/builds/build-ScStwMonitor-Android_for_armeabi_v7a_Clang_Qt_5_12_6_for_Android_ARMv7-Debug
+ /home/dorian/Qt/builds/build-ScStwMonitor-Android_for_armeabi_v7a_Clang_Qt_5_12_6_for_Android_ARMv7-Debug
+
+
+ true
+ QtProjectManager.QMakeBuildStep
+
+ false
+
+
+
+ true
+ Qt4ProjectManager.MakeStep
+
+ false
+
+
+ false
+
+
+ true
+ Qt4ProjectManager.AndroidPackageInstallationStep
+
+
+ android-29
+
+ true
+ QmakeProjectManager.AndroidBuildApkStep
+ false
+ false
+
+ 4
+ Build
+ Build
+ ProjectExplorer.BuildSteps.Build
+
+
+
+ true
+ Qt4ProjectManager.MakeStep
+
+ true
+ clean
+
+ false
+
+ 1
+ Clean
+ Clean
+ ProjectExplorer.BuildSteps.Clean
+
+ 2
+ false
+
+ Debug
+ Qt4ProjectManager.Qt4BuildConfiguration
+ 2
+ 2
+ 2
+
+
+ true
+ 2
+ /home/dorian/Qt/builds/build-ScStwMonitor-Android_for_armeabi_v7a_Clang_Qt_5_12_6_for_Android_ARMv7-Release
+ /home/dorian/Qt/builds/build-ScStwMonitor-Android_for_armeabi_v7a_Clang_Qt_5_12_6_for_Android_ARMv7-Release
+
+
+ true
+ QtProjectManager.QMakeBuildStep
+
+ false
+
+
+
+ true
+ Qt4ProjectManager.MakeStep
+
+ false
+
+
+ false
+
+
+ true
+ Qt4ProjectManager.AndroidPackageInstallationStep
+
+
+ android-29
+
+ true
+ QmakeProjectManager.AndroidBuildApkStep
+ false
+ false
+
+ 4
+ Build
+ Build
+ ProjectExplorer.BuildSteps.Build
+
+
+
+ true
+ Qt4ProjectManager.MakeStep
+
+ true
+ clean
+
+ false
+
+ 1
+ Clean
+ Clean
+ ProjectExplorer.BuildSteps.Clean
+
+ 2
+ false
+
+ Release
+ Qt4ProjectManager.Qt4BuildConfiguration
+ 0
+ 0
+ 2
+
+
+ true
+ 0
+ /home/dorian/Qt/builds/build-ScStwMonitor-Android_for_armeabi_v7a_Clang_Qt_5_12_6_for_Android_ARMv7-Profile
+ /home/dorian/Qt/builds/build-ScStwMonitor-Android_for_armeabi_v7a_Clang_Qt_5_12_6_for_Android_ARMv7-Profile
+
+
+ true
+ QtProjectManager.QMakeBuildStep
+
+ false
+
+
+
+ true
+ Qt4ProjectManager.MakeStep
+
+ false
+
+
+ false
+
+
+ true
+ Qt4ProjectManager.AndroidPackageInstallationStep
+
+
+ android-29
+
+ true
+ QmakeProjectManager.AndroidBuildApkStep
+ false
+ false
+
+ 4
+ Build
+ Build
+ ProjectExplorer.BuildSteps.Build
+
+
+
+ true
+ Qt4ProjectManager.MakeStep
+
+ true
+ clean
+
+ false
+
+ 1
+ Clean
+ Clean
+ ProjectExplorer.BuildSteps.Clean
+
+ 2
+ false
+
+ Profile
+ Qt4ProjectManager.Qt4BuildConfiguration
+ 0
+ 0
+ 0
+
+ 3
+
+
+
+ true
+ Qt4ProjectManager.AndroidDeployQtStep
+ false
+
+ 1
+ Deploy
+ Deploy
+ ProjectExplorer.BuildSteps.Deploy
+
+ 1
+
+ false
+ Qt4ProjectManager.AndroidDeployConfiguration2
+
+ 1
+
+
+ dwarf
+
+ cpu-cycles
+
+
+ 250
+
+ -e
+ cpu-cycles
+ --call-graph
+ dwarf,4096
+ -F
+ 250
+
+ -F
+ true
+ 4096
+ false
+ false
+ 1000
+
+ true
+
+ false
+ false
+ false
+ false
+ true
+ 0.01
+ 10
+ true
+ kcachegrind
+ 1
+ 25
+
+ 1
+ true
+ false
+ true
+ valgrind
+
+ 0
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+
+
+
+
+ 0
+
+ ScStwMonitorSrc
+ Qt4ProjectManager.AndroidRunConfiguration:/home/dorian/Documents/git/ScStw/monitor/ScStwMonitorSrc/ScStwMonitorSrc.pro
+ /home/dorian/Documents/git/ScStw/monitor/ScStwMonitorSrc/ScStwMonitorSrc.pro
+
+ false
+
+ false
+ true
+ false
+ false
+ true
+
+ 1
+
+
+
+ ProjectExplorer.Project.Target.1
+
+ Desktop Qt 5.12.6 GCC 64bit
+ Desktop Qt 5.12.6 GCC 64bit
+ qt.qt5.5126.gcc_64_kit
+ 0
+ 0
+ 0
+
+ true
+ 0
+ /home/dorian/Qt/builds/build-ScStwMonitor-Desktop_Qt_5_12_6_GCC_64bit-Debug
+ /home/dorian/Qt/builds/build-ScStwMonitor-Desktop_Qt_5_12_6_GCC_64bit-Debug
+
+
+ true
+ QtProjectManager.QMakeBuildStep
+
+ false
+
+
+
+ true
+ Qt4ProjectManager.MakeStep
+
+ false
+
+
+ false
+
+ 2
+ Build
+ Build
+ ProjectExplorer.BuildSteps.Build
+
+
+
+ true
+ Qt4ProjectManager.MakeStep
+
+ true
+ clean
+
+ false
+
+ 1
+ Clean
+ Clean
+ ProjectExplorer.BuildSteps.Clean
+
+ 2
+ false
+
+ Debug
+ Qt4ProjectManager.Qt4BuildConfiguration
+ 2
+ 2
+ 2
+
+
+ true
+ 2
+ /home/dorian/Qt/builds/build-ScStwMonitor-Desktop_Qt_5_12_6_GCC_64bit-Release
+ /home/dorian/Qt/builds/build-ScStwMonitor-Desktop_Qt_5_12_6_GCC_64bit-Release
+
+
+ true
+ QtProjectManager.QMakeBuildStep
+
+ false
+
+
+
+ true
+ Qt4ProjectManager.MakeStep
+
+ false
+
+
+ false
+
+ 2
+ Build
+ Build
+ ProjectExplorer.BuildSteps.Build
+
+
+
+ true
+ Qt4ProjectManager.MakeStep
+
+ true
+ clean
+
+ false
+
+ 1
+ Clean
+ Clean
+ ProjectExplorer.BuildSteps.Clean
+
+ 2
+ false
+
+ Release
+ Qt4ProjectManager.Qt4BuildConfiguration
+ 0
+ 0
+ 2
+
+
+ true
+ 0
+ /home/dorian/Qt/builds/build-ScStwMonitor-Desktop_Qt_5_12_6_GCC_64bit-Profile
+ /home/dorian/Qt/builds/build-ScStwMonitor-Desktop_Qt_5_12_6_GCC_64bit-Profile
+
+
+ true
+ QtProjectManager.QMakeBuildStep
+
+ false
+
+
+
+ true
+ Qt4ProjectManager.MakeStep
+
+ false
+
+
+ false
+
+ 2
+ Build
+ Build
+ ProjectExplorer.BuildSteps.Build
+
+
+
+ true
+ Qt4ProjectManager.MakeStep
+
+ true
+ clean
+
+ false
+
+ 1
+ Clean
+ Clean
+ ProjectExplorer.BuildSteps.Clean
+
+ 2
+ false
+
+ Profile
+ Qt4ProjectManager.Qt4BuildConfiguration
+ 0
+ 0
+ 0
+
+ 3
+
+
+ 0
+ Deploy
+ Deploy
+ ProjectExplorer.BuildSteps.Deploy
+
+ 1
+
+ false
+ ProjectExplorer.DefaultDeployConfiguration
+
+ 1
+
+
+ dwarf
+
+ cpu-cycles
+
+
+ 250
+
+ -e
+ cpu-cycles
+ --call-graph
+ dwarf,4096
+ -F
+ 250
+
+ -F
+ true
+ 4096
+ false
+ false
+ 1000
+
+ true
+
+ false
+ false
+ false
+ false
+ true
+ 0.01
+ 10
+ true
+ kcachegrind
+ 1
+ 25
+
+ 1
+ true
+ false
+ true
+ valgrind
+
+ 0
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+
+ 2
+
+ Qt4ProjectManager.Qt4RunConfiguration:/home/dorian/Documents/git/ScStw/monitor/ScStwMonitorSrc/ScStwMonitorSrc.pro
+ /home/dorian/Documents/git/ScStw/monitor/ScStwMonitorSrc/ScStwMonitorSrc.pro
+
+ false
+
+ false
+ true
+ true
+ false
+ false
+ true
+
+ /home/dorian/Qt/builds/build-ScStwMonitor-Desktop_Qt_5_12_6_GCC_64bit-Debug/ScStwMonitorSrc
+
+ 1
+
+
+
+ ProjectExplorer.Project.Target.2
+
+ Raspberry Pi
+ Raspberry Pi
+ {78391e56-a762-420f-834b-044152e6b610}
+ 0
+ 0
+ 0
+
+ true
+ 0
+ /home/dorian/Qt/builds/build-ScStwMonitor-Raspberry_Pi-Debug
+ /home/dorian/Qt/builds/build-ScStwMonitor-Raspberry_Pi-Debug
+
+
+ true
+ QtProjectManager.QMakeBuildStep
+
+ false
+
+
+
+ true
+ Qt4ProjectManager.MakeStep
+
+ false
+
+
+ false
+
+ 2
+ Build
+ Build
+ ProjectExplorer.BuildSteps.Build
+
+
+
+ true
+ Qt4ProjectManager.MakeStep
+
+ true
+ clean
+
+ false
+
+ 1
+ Clean
+ Clean
+ ProjectExplorer.BuildSteps.Clean
+
+ 2
+ false
+
+ Debug
+ Qt4ProjectManager.Qt4BuildConfiguration
+ 2
+ 2
+ 2
+
+
+ true
+ 2
+ /home/dorian/Qt/builds/build-ScStwMonitor-Raspberry_Pi-Release
+ /home/dorian/Qt/builds/build-ScStwMonitor-Raspberry_Pi-Release
+
+
+ true
+ QtProjectManager.QMakeBuildStep
+
+ false
+
+
+
+ true
+ Qt4ProjectManager.MakeStep
+
+ false
+
+
+ false
+
+ 2
+ Build
+ Build
+ ProjectExplorer.BuildSteps.Build
+
+
+
+ true
+ Qt4ProjectManager.MakeStep
+
+ true
+ clean
+
+ false
+
+ 1
+ Clean
+ Clean
+ ProjectExplorer.BuildSteps.Clean
+
+ 2
+ false
+
+ Release
+ Qt4ProjectManager.Qt4BuildConfiguration
+ 0
+ 0
+ 2
+
+
+ true
+ 0
+ /home/dorian/Qt/builds/build-ScStwMonitor-Raspberry_Pi-Profile
+ /home/dorian/Qt/builds/build-ScStwMonitor-Raspberry_Pi-Profile
+
+
+ true
+ QtProjectManager.QMakeBuildStep
+
+ false
+
+
+
+ true
+ Qt4ProjectManager.MakeStep
+
+ false
+
+
+ false
+
+ 2
+ Build
+ Build
+ ProjectExplorer.BuildSteps.Build
+
+
+
+ true
+ Qt4ProjectManager.MakeStep
+
+ true
+ clean
+
+ false
+
+ 1
+ Clean
+ Clean
+ ProjectExplorer.BuildSteps.Clean
+
+ 2
+ false
+
+ Profile
+ Qt4ProjectManager.Qt4BuildConfiguration
+ 0
+ 0
+ 0
+
+ 3
+
+
+
+ true
+ RemoteLinux.CheckForFreeDiskSpaceStep
+
+
+
+
+ /
+ 5242880
+
+
+
+
+ true
+ RemoteLinux.KillAppStep
+
+
+
+
+
+
+
+
+ true
+ RemoteLinux.DirectUploadStep
+
+
+
+
+ false
+ true
+
+
+
+ 3
+ Deploy
+ Deploy
+ ProjectExplorer.BuildSteps.Deploy
+
+ 1
+
+ false
+ DeployToGenericLinux
+
+ 1
+
+
+ dwarf
+
+ cpu-cycles
+
+
+ 250
+
+ -e
+ cpu-cycles
+ --call-graph
+ dwarf,4096
+ -F
+ 250
+
+ -F
+ true
+ 4096
+ false
+ false
+ 1000
+
+ true
+
+ false
+ false
+ false
+ false
+ true
+ 0.01
+ 10
+ true
+ kcachegrind
+ 1
+ 25
+
+ 1
+ true
+ false
+ true
+ valgrind
+
+ 0
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+
+ 2
+
+
+ ProjectExplorer.CustomExecutableRunConfiguration
+
+
+ false
+
+ false
+ true
+ false
+ false
+ true
+
+
+
+
+ dwarf
+
+ cpu-cycles
+
+
+ 250
+
+ -e
+ cpu-cycles
+ --call-graph
+ dwarf,4096
+ -F
+ 250
+
+ -F
+ true
+ 4096
+ false
+ false
+ 1000
+
+ true
+
+ false
+ false
+ false
+ false
+ true
+ 0.01
+ 10
+ true
+ kcachegrind
+ 1
+ 25
+
+ 1
+ true
+ false
+ true
+ valgrind
+
+ 0
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+
+ 1
+
+ RemoteLinux.CustomRunConfig
+
+
+
+ 1
+
+ false
+
+ false
+ true
+ false
+ false
+ true
+ false
+
+
+ :0
+
+ 2
+
+
+
+ ProjectExplorer.Project.TargetCount
+ 3
+
+
+ ProjectExplorer.Project.Updater.FileVersion
+ 22
+
+
+ Version
+ 22
+
+
diff --git a/ScStwMonitorSrc/.gitignore b/ScStwMonitorSrc/.gitignore
new file mode 100644
index 0000000..39f809d
--- /dev/null
+++ b/ScStwMonitorSrc/.gitignore
@@ -0,0 +1,2 @@
+*.pro.user
+*.pro.user*
diff --git a/ScStwMonitorSrc/Banner.png b/ScStwMonitorSrc/Banner.png
new file mode 100644
index 0000000..4361a3e
Binary files /dev/null and b/ScStwMonitorSrc/Banner.png differ
diff --git a/ScStwMonitorSrc/FadeAnimation.qml b/ScStwMonitorSrc/FadeAnimation.qml
new file mode 100644
index 0000000..df750b1
--- /dev/null
+++ b/ScStwMonitorSrc/FadeAnimation.qml
@@ -0,0 +1,72 @@
+/*
+ Speed Climbing Stopwatch - Simple Stopwatch for Climbers
+ Copyright (C) 2018 Itsblue Development - Dorian Zeder
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, version 3 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+import QtQuick 2.0
+
+SequentialAnimation {
+ id: root
+ property QtObject target
+ property int fadeDuration: 150
+ property int fadeDuration_in: fadeDuration
+ property int fadeDuration_out: fadeDuration
+
+ property alias outValueScale: outAnimationScale.to
+ property alias inValueScale: inAnimationScale.to
+
+ property alias outValueOpacity: outAnimationOpacity.to
+ property alias inValueOpacity: inAnimationOpacity.to
+
+ property string easingType: "Quad"
+ ParallelAnimation {
+ NumberAnimation { // in the default case, fade scale to 0
+ id: outAnimationScale
+ target: root.target
+ property: "scale"
+ duration: root.fadeDuration_in
+ to: 0.9
+ easing.type: Easing["In"+root.easingType]
+ }
+ NumberAnimation { // in the default case, fade scale to 0
+ id: outAnimationOpacity
+ target: root.target
+ property: "opacity"
+ duration: root.fadeDuration_in
+ to: 0
+ easing.type: Easing["In"+root.easingType]
+ }
+ }
+ PropertyAction { } // actually change the property targeted by the Behavior between the 2 other animations
+ ParallelAnimation {
+ NumberAnimation { // in the default case, fade scale back to 1
+ id: inAnimationScale
+ target: root.target
+ property: "scale"
+ duration: root.fadeDuration_out
+ to: 1
+ easing.type: Easing["Out"+root.easingType]
+ }
+ NumberAnimation { // in the default case, fade scale to 0
+ id: inAnimationOpacity
+ target: root.target
+ property: "opacity"
+ duration: root.fadeDuration_in
+ to: 1
+ easing.type: Easing["In"+root.easingType]
+ }
+ }
+
+}
diff --git a/ScStwMonitorSrc/FancyBusyIndicator.qml b/ScStwMonitorSrc/FancyBusyIndicator.qml
new file mode 100644
index 0000000..0d15d80
--- /dev/null
+++ b/ScStwMonitorSrc/FancyBusyIndicator.qml
@@ -0,0 +1,61 @@
+import QtQuick 2.1
+import QtQuick.Controls 2.2
+
+BusyIndicator {
+ id: control
+
+ property double speed: 1
+ property color lineColor: "#21be2b"
+
+ width: 100
+ height: 100
+
+ contentItem: Canvas {
+ id: spinnerCanvas
+ anchors.fill: parent
+
+ property double progress: 0
+
+ function drawSpinner(ctx, width, height, progress){
+ var margins = width * 0.01
+ var lineWidth = width * 0.1
+
+ ctx.clearRect(0,0,width,height)
+
+ ctx.beginPath();
+ ctx.arc(width * 0.5 + margins, height * 0.5 + margins, height*0.5 - margins*2 - lineWidth , 0, 2*Math.PI);
+
+ ctx.strokeStyle = "#dedede";
+ ctx.lineWidth = lineWidth
+ ctx.stroke();
+
+ ctx.beginPath();
+ ctx.arc(width * 0.5 + margins, height * 0.5 + margins, height*0.5 - margins*2 - lineWidth, 2*Math.PI * progress, 2*Math.PI * progress + 0.5*Math.PI);
+
+ ctx.strokeStyle = "#48db09";
+ ctx.stroke();
+ }
+
+
+ Timer {
+ interval: Math.floor(20 * 1/control.speed)
+ running: control.opacity > 0 && control.visible && control.running
+ repeat: true
+
+ onTriggered: {
+ spinnerCanvas.progress += 0.0027*6
+ if(spinnerCanvas.progress >= 1){
+ spinnerCanvas.progress = 0
+ }
+
+ spinnerCanvas.requestPaint()
+ }
+ }
+
+ onPaint: {
+ var ctx = getContext("2d");
+ spinnerCanvas.drawSpinner(ctx, spinnerCanvas.height, spinnerCanvas.width, spinnerCanvas.progress)
+ }
+
+ }
+}
diff --git a/ScStwMonitorSrc/ScStwMonitor.desktop b/ScStwMonitorSrc/ScStwMonitor.desktop
new file mode 100644
index 0000000..acbbd09
--- /dev/null
+++ b/ScStwMonitorSrc/ScStwMonitor.desktop
@@ -0,0 +1,4 @@
+[Desktop Entry]
+Type=Application
+Name=ScStwMonitor
+Exec=/opt/ScStwMonitor/bin/ScStwMonitor
diff --git a/ScStwMonitorSrc/ScStwMonitorSrc.pro b/ScStwMonitorSrc/ScStwMonitorSrc.pro
new file mode 100755
index 0000000..489a945
--- /dev/null
+++ b/ScStwMonitorSrc/ScStwMonitorSrc.pro
@@ -0,0 +1,74 @@
+QT += quick widgets multimedia
+
+CONFIG += c++11
+CONFIG -= app_bundle
+
+TARGET = ScStwMonitor
+VERSION = 1.1
+
+# The following define makes your compiler emit warnings if you use
+# any feature of Qt which as been marked deprecated (the exact warnings
+# depend on your compiler). Please consult the documentation of the
+# deprecated API in order to know how to port your code away from it.
+DEFINES += QT_DEPRECATED_WARNINGS
+
+# You can also make your code fail to compile if you use deprecated APIs.
+# In order to do so, uncomment the following line.
+# You can also select to disable deprecated APIs only up to a certain version of Qt.
+#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
+
+SOURCES += \
+ main.cpp \
+ baseconn.cpp \
+ sources/scstwmonitorbackend.cpp
+
+RESOURCES += \
+ qml.qrc \
+ shared.qrc
+
+
+# include submodules
+include($$PWD/../shared-libraries/ScStwLibraries/ScStwLibraries.pri)
+#include($$PWD/../shared-libraries/ScStwStyling/ScStwStyling.pri)
+
+# Additional import path used to resolve QML modules in Qt Creator's code model
+QML_IMPORT_PATH =
+
+# Additional import path used to resolve QML modules just for Qt Quick Designer
+QML_DESIGNER_IMPORT_PATH =
+
+# Default rules for deployment.
+qnx: target.path = /tmp/$${TARGET}/bin
+# target path for raspi
+else: unix:!android: target.path = /home/pi/$${TARGET}/bin
+!isEmpty(target.path): INSTALLS += target
+
+HEADERS += \
+ baseconn.h \
+ headers/scstwmonitorbackend.h
+
+DISTFILES += \
+ android/AndroidManifest.xml \
+ android/build.gradle \
+ android/gradle/wrapper/gradle-wrapper.jar \
+ android/gradle/wrapper/gradle-wrapper.properties \
+ android/gradlew \
+ android/gradlew.bat \
+ android/res/values/libs.xml \
+ android/src/MainActivity.java
+
+android {
+ ANDROID_PACKAGE_SOURCE_DIR = \
+ $$PWD/android
+}
+
+ios {
+ QMAKE_ASSET_CATALOGS += icon/Assets.xcassets
+ xcode_product_bundle_identifier_setting.value = "de.itsblue.ScStwMonitor"
+
+ OBJECTIVE_SOURCES += \
+ sleepprevent.mm
+ OBJECTIVE_HEADERS += \
+ sleepprevent.h
+}
+
diff --git a/ScStwMonitorSrc/SpeedHold.png b/ScStwMonitorSrc/SpeedHold.png
new file mode 100644
index 0000000..87738b4
Binary files /dev/null and b/ScStwMonitorSrc/SpeedHold.png differ
diff --git a/ScStwMonitorSrc/TimerColumn.qml b/ScStwMonitorSrc/TimerColumn.qml
new file mode 100644
index 0000000..5bfc078
--- /dev/null
+++ b/ScStwMonitorSrc/TimerColumn.qml
@@ -0,0 +1,146 @@
+import QtQuick 2.0
+import QtQuick.Controls 2.0
+import de.itsblue.ScStw 2.0
+import de.itsblue.ScStwMonitor 2.0
+
+Column {
+ id: timerCol
+
+ opacity: backend.scStwClient.state === ScStwClient.CONNECTED ? 1:0
+
+ spacing: 0
+
+ Repeater {
+ id: timerRep
+
+ property var clearedTimers: removeDisabledTimers(backend.race.timers)
+
+ function removeDisabledTimers(timers) {
+ var ret = []
+ for(var i = 0; i < timers.length; i++) {
+ if(timers[i]["state"] !== ScStwTimer.DISABLED)
+ ret.push(timers[i])
+ }
+ return ret
+ }
+
+ model: clearedTimers.length
+
+ delegate: Item {
+ id: timerDel
+
+ width: parent.width
+ height: timerCol.height / timerRep.model
+
+ Label {
+ id: laneNameLa
+
+ anchors {
+ left: parent.left
+ }
+
+ leftPadding: parent.width * 0.03
+
+ width: parent.width * 0.15
+ height: parent.height * 0.5
+
+ fontSizeMode: Text.Fit
+
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignLeft
+
+ text: ""//index === 0 ? "A":"B"
+
+ color: "#2a5266"
+
+ font.pixelSize: height
+ font.family: timerFont.name
+
+ Rectangle {
+ anchors.fill: parent
+ color: "red"
+ opacity: 0
+ }
+ }
+
+ Label {
+ id: timerTextLa
+
+ anchors.centerIn: parent
+ anchors.horizontalCenterOffset: laneNameLa.text !== "" ? parent.width * 0.06:0
+ anchors.verticalCenterOffset: -(parent.height * 0.04 * reactTimeLa.opacity)
+
+ width: parent.width * 0.8
+ height: parent.height * 0.8
+
+ elide: "ElideRight"
+ color: ([ScStwTimer.WON].indexOf(timerRep.clearedTimers[index]["state"]) >= 0 ? "#6bd43b" :
+ [ScStwTimer.FAILED,ScStwTimer.LOST].indexOf(timerRep.clearedTimers[index]["state"]) >= 0 ? "#e03b2f":
+ "black")
+
+ text: timerRep.clearedTimers[index]["text"]
+
+ fontSizeMode: Text.Fit
+
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+
+ font.pixelSize: height
+ font.family: timerFont.name
+ minimumPixelSize: 1
+ }
+
+ Label {
+ id: reactTimeLa
+
+ property int rtime: timerRep.clearedTimers[index]["reactionTime"]
+
+ anchors {
+ centerIn: parent
+ verticalCenterOffset: timerTextLa.contentHeight * 0.4 + reactTimeLa.contentHeight * 0.4 + timerTextLa.anchors.verticalCenterOffset
+ horizontalCenterOffset: parent.width * 0.06
+ }
+
+ width: parent.width * 0.6
+ height: parent.height * 0.15
+
+ scale: enabled ? 1:0.9
+ opacity: enabled ? 1:0
+
+ enabled: timerRep.clearedTimers[index]["state"] >= ScStwTimer.STARTING && rtime !== 0
+
+ fontSizeMode: Text.Fit
+
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+
+ text: "reaction time (ms): " + Math.round(rtime)
+
+ color: "black"//appTheme.style.textColor
+
+ font.pixelSize: timerTextLa.font.pixelSize * 0.5
+ font.family: timerFont.name
+ minimumPixelSize: 1
+
+ Behavior on opacity {
+ NumberAnimation {
+ duration: 200
+ }
+ }
+
+ Behavior on scale {
+ NumberAnimation {
+ duration: 200
+ }
+ }
+
+ }
+ }
+ }
+
+ Behavior on opacity {
+ NumberAnimation {
+ duration: 200
+ }
+ }
+}
diff --git a/ScStwMonitorSrc/VolumeHigh.png b/ScStwMonitorSrc/VolumeHigh.png
new file mode 100644
index 0000000..0ed4293
Binary files /dev/null and b/ScStwMonitorSrc/VolumeHigh.png differ
diff --git a/ScStwMonitorSrc/VolumeLow.png b/ScStwMonitorSrc/VolumeLow.png
new file mode 100644
index 0000000..038f967
Binary files /dev/null and b/ScStwMonitorSrc/VolumeLow.png differ
diff --git a/ScStwMonitorSrc/android/AndroidManifest.xml b/ScStwMonitorSrc/android/AndroidManifest.xml
new file mode 100644
index 0000000..ad3c7d2
--- /dev/null
+++ b/ScStwMonitorSrc/android/AndroidManifest.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ScStwMonitorSrc/android/build.gradle b/ScStwMonitorSrc/android/build.gradle
new file mode 100644
index 0000000..989d079
--- /dev/null
+++ b/ScStwMonitorSrc/android/build.gradle
@@ -0,0 +1,57 @@
+buildscript {
+ repositories {
+ google()
+ jcenter()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.2.0'
+ }
+}
+
+repositories {
+ google()
+ jcenter()
+}
+
+apply plugin: 'com.android.application'
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
+}
+
+android {
+ /*******************************************************
+ * The following variables:
+ * - androidBuildToolsVersion,
+ * - androidCompileSdkVersion
+ * - qt5AndroidDir - holds the path to qt android files
+ * needed to build any Qt application
+ * on Android.
+ *
+ * are defined in gradle.properties file. This file is
+ * updated by QtCreator and androiddeployqt tools.
+ * Changing them manually might break the compilation!
+ *******************************************************/
+
+ compileSdkVersion androidCompileSdkVersion.toInteger()
+
+ buildToolsVersion androidBuildToolsVersion
+
+ sourceSets {
+ main {
+ manifest.srcFile 'AndroidManifest.xml'
+ java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
+ aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
+ res.srcDirs = [qt5AndroidDir + '/res', 'res']
+ resources.srcDirs = ['src']
+ renderscript.srcDirs = ['src']
+ assets.srcDirs = ['assets']
+ jniLibs.srcDirs = ['libs']
+ }
+ }
+
+ lintOptions {
+ abortOnError false
+ }
+}
diff --git a/ScStwMonitorSrc/android/gradle/wrapper/gradle-wrapper.jar b/ScStwMonitorSrc/android/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..f6b961f
Binary files /dev/null and b/ScStwMonitorSrc/android/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/ScStwMonitorSrc/android/gradle/wrapper/gradle-wrapper.properties b/ScStwMonitorSrc/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..bf3de21
--- /dev/null
+++ b/ScStwMonitorSrc/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/ScStwMonitorSrc/android/gradlew b/ScStwMonitorSrc/android/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/ScStwMonitorSrc/android/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/ScStwMonitorSrc/android/gradlew.bat b/ScStwMonitorSrc/android/gradlew.bat
new file mode 100644
index 0000000..f955316
--- /dev/null
+++ b/ScStwMonitorSrc/android/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/ScStwMonitorSrc/android/res/drawable-hdpi/icon.png b/ScStwMonitorSrc/android/res/drawable-hdpi/icon.png
new file mode 100644
index 0000000..a26d006
Binary files /dev/null and b/ScStwMonitorSrc/android/res/drawable-hdpi/icon.png differ
diff --git a/ScStwMonitorSrc/android/res/drawable-ldpi/icon.png b/ScStwMonitorSrc/android/res/drawable-ldpi/icon.png
new file mode 100644
index 0000000..a26d006
Binary files /dev/null and b/ScStwMonitorSrc/android/res/drawable-ldpi/icon.png differ
diff --git a/ScStwMonitorSrc/android/res/drawable-mdpi/icon.png b/ScStwMonitorSrc/android/res/drawable-mdpi/icon.png
new file mode 100644
index 0000000..a26d006
Binary files /dev/null and b/ScStwMonitorSrc/android/res/drawable-mdpi/icon.png differ
diff --git a/ScStwMonitorSrc/android/res/values/libs.xml b/ScStwMonitorSrc/android/res/values/libs.xml
new file mode 100644
index 0000000..4009a77
--- /dev/null
+++ b/ScStwMonitorSrc/android/res/values/libs.xml
@@ -0,0 +1,25 @@
+
+
+
+ - https://download.qt.io/ministro/android/qt5/qt-5.9
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ScStwMonitorSrc/android/src/MainActivity.java b/ScStwMonitorSrc/android/src/MainActivity.java
new file mode 100644
index 0000000..2984682
--- /dev/null
+++ b/ScStwMonitorSrc/android/src/MainActivity.java
@@ -0,0 +1,9 @@
+package de.itsblue.SpeedClimbingStopwatchMonitor;
+public class MainActivity extends org.qtproject.qt5.android.bindings.QtActivity {
+ @Override
+ public void onCreate(android.os.Bundle savedInstanceState){
+ super.onCreate(savedInstanceState);
+ this.getWindow().addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ //this.getWindow().setVolumeControlStream(android.view.AudioManager.STREAM_MUSIC);
+ }
+}
diff --git a/ScStwMonitorSrc/baseconn.cpp b/ScStwMonitorSrc/baseconn.cpp
new file mode 100755
index 0000000..332e83d
--- /dev/null
+++ b/ScStwMonitorSrc/baseconn.cpp
@@ -0,0 +1,504 @@
+#include "baseconn.h"
+
+BaseConn::BaseConn(QObject *parent) : QObject(parent)
+{
+ socket = new QTcpSocket(this);
+
+ this->timeoutTimer = new QTimer(this);
+ this->timeoutTimer->setSingleShot(true);
+
+ this->state = "disconnected";
+
+ connect(this->socket, SIGNAL(error(QAbstractSocket::SocketError)),
+ this, SLOT(gotError(QAbstractSocket::SocketError)));
+
+ connect(this->socket, &QAbstractSocket::stateChanged, this, &BaseConn::socketStateChanged);
+ connect(this, &BaseConn::gotUpdate, this, &BaseConn::handleUpdate);
+
+ this->nextConnectionId = 1;
+
+ // init refresh timers
+ this->autoConnectRetryTimer = new QTimer(this);
+ this->autoConnectRetryTimer->setInterval(1000);
+ this->autoConnectRetryTimer->setSingleShot(true);
+ connect(this->autoConnectRetryTimer, &QTimer::timeout, this, &BaseConn::doConnectionAttempt);
+ this->autoConnectRetryTimer->start();
+
+ this->timerTextRefreshTimer = new QTimer(this);
+ this->timerTextRefreshTimer->setInterval(1);
+ this->timerTextRefreshTimer->setSingleShot(true);
+ connect(this->timerTextRefreshTimer, &QTimer::timeout, this, &BaseConn::refreshTimerTextList);
+ this->timerTextRefreshTimer->start();
+}
+
+void BaseConn::connectToHost() {
+ qDebug() << "+--- connecting";
+ setState("connecting");
+
+ connect(this->timeoutTimer, SIGNAL(timeout()), this, SLOT(connectionTimeout()));
+
+ //connect
+ this->socket->connectToHost(this->ip, this->port);
+
+ timeoutTimer->start(3000);
+}
+
+void BaseConn::connectionTimeout() {
+ this->socket->abort();
+ disconnect(this->timeoutTimer, SIGNAL(timeout()), this, SLOT(connectionTimeout()));
+}
+
+bool BaseConn::init() {
+ disconnect(this->timeoutTimer, SIGNAL(timeout()), this, SLOT(connectionTimeout()));
+ this->timeoutTimer->stop();
+
+ connect(this->socket, &QTcpSocket::readyRead, this, &BaseConn::readyRead);
+
+ this->setState("connected");
+
+ // init remote session
+ QJsonArray updateSubs = {"onTimersChanged", "onRaceStateChanged", "onNextStartActionChanged"};
+ QJsonObject sessionParams = {{"updateSubs", updateSubs}, {"init", true}};
+
+ if(this->sendCommand(1, sessionParams)["status"] != 200) {
+ return false;
+ }
+
+ return true;
+}
+
+void BaseConn::deInit() {
+}
+
+void BaseConn::closeConnection()
+{
+ qDebug() << "+--- closing connection";
+ switch (socket->state())
+ {
+ case 0:
+ socket->disconnectFromHost();
+ break;
+ case 2:
+ socket->abort();
+ break;
+ default:
+ socket->abort();
+ }
+
+ setState("disconnected");
+}
+
+void BaseConn::gotError(QAbstractSocket::SocketError err)
+{
+ //qDebug() << "got error";
+ QString strError = "unknown";
+ switch (err)
+ {
+ case 0:
+ strError = "Connection was refused";
+ break;
+ case 1:
+ strError = "Remote host closed the connection";
+ this->closeConnection();
+ break;
+ case 2:
+ strError = "Host address was not found";
+ break;
+ case 5:
+ strError = "Connection timed out";
+ break;
+ default:
+ strError = "Unknown error";
+ }
+
+ emit gotError(strError);
+}
+
+// -------------------------------------
+// --- socket communication handling ---
+// -------------------------------------
+
+void BaseConn::socketStateChanged(QAbstractSocket::SocketState socketState) {
+ switch (socketState) {
+ case QAbstractSocket::UnconnectedState:
+ {
+ this->setState("disconnected");
+ break;
+ }
+ case QAbstractSocket::ConnectedState:
+ {
+ if(this->init()) {
+ this->setState("connected");
+ }
+ else {
+ this->closeConnection();
+ }
+
+ break;
+ }
+ default:
+ {
+ //qDebug() << "+ --- UNKNOWN SOCKET STATE: " << socketState;
+ break;
+ }
+ }
+}
+
+QVariantMap BaseConn::sendCommand(int header, QJsonValue data){
+ if(this->state != "connected"){
+ return {{"status", 910}, {"data", "not connected"}};
+ }
+
+ // generate id and witing requests entry
+ int thisId = nextConnectionId;
+ //qDebug() << "sending command: " << header << " with data: " << data << " and id: " << thisId;
+ nextConnectionId ++;
+
+ QEventLoop *loop = new QEventLoop(this);
+ QTimer *timer = new QTimer(this);
+ QJsonObject reply;
+
+ this->waitingRequests.append({thisId, loop, reply});
+
+ QJsonObject requestObj;
+ requestObj.insert("id", thisId);
+ requestObj.insert("header", header);
+ requestObj.insert("data", data);
+
+ QString jsonRequest = QJsonDocument(requestObj).toJson();
+
+ timer->setSingleShot(true);
+ // quit the loop when the timer times out
+ loop->connect(timer, SIGNAL(timeout()), loop, SLOT(quit()));
+ // quit the loop when the connection was established
+ // loop.connect(this, &BaseConn::gotReply, &loop, &QEventLoop::quit);
+ // start the timer before starting to connect
+ timer->start(3000);
+
+ //write data
+ socket->write(jsonRequest.toLatin1());
+
+ //wait for an answer to finish (programm gets stuck in here)
+ loop->exec();
+
+ bool replyFound = false;
+
+ // find reply and delete the request from waiting list
+ for(int i = 0; iwaitingRequests.length(); i++){
+ if(this->waitingRequests[i].id == thisId){
+ // request was found
+ replyFound = true;
+ // delete event loop
+ if(this->waitingRequests[i].loop != nullptr) {
+ delete this->waitingRequests[i].loop;
+ }
+ // store reply
+ reply = this->waitingRequests[i].reply;
+ // remove reply from waiting list
+ this->waitingRequests.removeAt(i);
+ }
+ }
+
+ if(!replyFound) {
+ // some internal error occured
+ return {{"status", 900}, {"data", ""}};
+ }
+
+ if(timer->remainingTime() == -1){
+ //the time has been triggered -> timeout
+ return {{"status", 911}, {"data", ""}};
+ }
+
+ delete timer;
+
+ return {{"status", reply.value("header").toInt()}, {"data", reply.value("data").toVariant()}};
+
+}
+
+void BaseConn::readyRead() {
+
+ //qDebug() << "ready to ready " << socket->bytesAvailable() << " bytes" ;
+ QString reply = socket->readAll();
+
+ //qWarning() << "socket read: " << reply;
+
+ processSocketMessage(reply);
+}
+
+void BaseConn::processSocketMessage(QString message){
+ QString startKey = "";
+ QString endKey = "";
+
+ //qWarning() << "... processing message now ... : " << message;
+
+ if(message == ""){
+ return;
+ }
+
+ if((message.startsWith(startKey) && message.endsWith(endKey)) && (message.count(startKey) == 1 && message.count(endKey) == 1)){
+ // non-split message ( e.g.: 123456789
+ }
+ else if(!message.contains(endKey) && (!this->readBuffer.isEmpty() || message.startsWith(startKey))){
+ // begin of a split message ( e.g.: 123 )
+ // or middle of a split message ( e.g.: 456 )
+ //qWarning() << "this is a begin or middle of split a message";
+ this->readBuffer += message;
+ return;
+ }
+ else if(!message.contains(startKey) && message.endsWith(endKey)) {
+ // end of a split message ( e.g.: 789 )
+
+ if(!this->readBuffer.isEmpty()){
+ message = readBuffer + message;
+ readBuffer.clear();
+ }
+ }
+ else if((message.count(startKey) > 1 || message.count(endKey) > 1) || (message.contains(endKey) && !message.endsWith(endKey) && message.contains(startKey) && !message.startsWith(startKey))) {
+ // multiple messages in one packet ( e.g.: 123456789987654321 )
+ // or multiple message fragments in one message ( e.g.: 56789987654321 or 5678998765 )
+ //qDebug() << "detected multiple messages";
+
+ int startOfSecondMessage = message.lastIndexOf(startKey);
+ // process first part of message
+ QString firstMessage = message.left(startOfSecondMessage);
+ this->processSocketMessage(firstMessage);
+ // process second part of message
+ QString secondMessage = message.right(message.length() - startOfSecondMessage);
+ this->processSocketMessage(secondMessage);
+
+ return;
+ }
+ else {
+ // invalid message
+ return;
+ }
+
+ //qWarning() << "... done processing, message: " << message;
+ this->socketReplyRecieved(message);
+}
+
+void BaseConn::socketReplyRecieved(QString reply) {
+ reply.replace("", "");
+ reply.replace("", "");
+
+ int id = 0;
+
+ QJsonDocument jsonReply = QJsonDocument::fromJson(reply.toUtf8());
+ QJsonObject replyObj = jsonReply.object();
+
+ //qDebug() << "got: " << reply;
+
+ if(!replyObj.isEmpty()){
+ id = replyObj.value("id").toInt();
+
+ if(id == -1) {
+ // this message is an update!!
+ emit this->gotUpdate(replyObj.toVariantMap());
+ return;
+ }
+
+ for(int i = 0; i < this->waitingRequests.length(); i++){
+ if(this->waitingRequests[i].id == id){
+ this->waitingRequests[i].reply = replyObj;
+ if(this->waitingRequests[i].loop != nullptr){
+ this->waitingRequests[i].loop->quit();
+ }
+ return;
+ }
+ }
+ }
+
+ latestReadReply = reply;
+ emit gotUnexpectedReply(reply);
+}
+
+// ------------------------
+// --- helper functions ---
+// ------------------------
+
+void BaseConn::doConnectionAttempt()
+{
+ if(this->state == "disconnected") {
+ qDebug() << "+--- trying to connect";
+ this->connectToHost();
+ }
+
+ this->autoConnectRetryTimer->start();
+}
+
+void BaseConn::setState(QString newState){
+ if(this->state != newState) {
+ qDebug() << "+--- BaseConn state changed: " << newState;
+ this->state = newState;
+ emit stateChanged();
+ if(this->state == "disconnected") {
+ this->deInit();
+ }
+ }
+}
+
+int BaseConn::writeRemoteSetting(QString key, QString value) {
+ QJsonArray requestData;
+ requestData.append(key);
+ requestData.append(value);
+ return this->sendCommand(3000, requestData)["status"].toInt();
+}
+
+QString BaseConn::readRemoteSetting(QString key)
+{
+ QVariantMap reply = this->sendCommand(3001, key);
+ if(reply["status"] != 200){
+ return "false";
+ }
+ return reply["data"].toString();
+}
+
+// ------------------
+// - for timer sync -
+// ------------------
+
+void BaseConn::handleUpdate(QVariantMap data) {
+ int header = data["header"].toInt();
+ switch (header) {
+ case 9000:
+ {
+ // the remote race state changed
+ this->remoteRaceState = data["data"].toInt();
+ this->raceStateChanged();
+ break;
+ }
+ case 9001:
+ {
+ // the remote timers have changed
+ this->refreshRemoteTimers(data["data"].toList());
+ break;
+ }
+ case 9003:
+ {
+ // the next start action has changed
+ this->nextStartActionTotalDelay = data["data"].toMap()["nextActionDelay"].toDouble();
+ this->nextStartActionDelayStartedAt = this->date->currentMSecsSinceEpoch() - (this->nextStartActionTotalDelay * data["data"].toMap()["nextActionDelayProg"].toDouble());
+ this->nextStartAction = NextStartAction( data["data"].toMap()["nextAction"].toInt() );
+
+ emit this->nextStartActionChanged();
+ }
+
+ }
+}
+
+void BaseConn::refreshRemoteTimers(QVariantList timers) {
+ QVariantList remoteTimers;
+
+ for (int i = 0; i < timers.length(); i++) {
+ QVariantMap thisTimer = timers[i].toMap();
+ if(thisTimer["state"].toInt() != DISABLED ) {
+ thisTimer.insert("startTime", this->date->currentMSecsSinceEpoch() - thisTimer["currTime"].toDouble());
+ remoteTimers.append(thisTimer);
+ }
+ }
+
+ this->remoteTimers = remoteTimers;
+}
+
+void BaseConn::refreshTimerTextList() {
+ QVariantList tmpTimerTextList;
+
+ for (int i = 0; i < this->remoteTimers.toList().length(); i++) {
+
+ QString newText;
+
+ switch (this->remoteTimers.toList()[i].toMap()["state"].toInt()) {
+ case IDLE:
+ newText = "00.000";
+ break;
+ case STARTING:
+ newText = "00.000";
+ break;
+ case WAITING:
+ newText = "False Start";
+ break;
+ case RUNNING: {
+ double currTime = this->date->currentMSecsSinceEpoch() - this->remoteTimers.toList()[i].toMap()["startTime"].toDouble();
+ QString currTimeString = (currTime < 10000 ? "0":"") + QString::number( currTime / 1000.0, 'f', 3 );//QString::number( (currTime) / 1000.0, 'f', 1 );
+ newText = currTimeString;
+ break;
+ }
+ case WON: {
+ double currTime = this->remoteTimers.toList()[i].toMap()["currTime"].toDouble();
+ newText = (currTime < 10000 ? "0":"") + QString::number( currTime / 1000.0, 'f', 3 );
+ break;
+ }
+ case LOST: {
+ double currTime = this->remoteTimers.toList()[i].toMap()["currTime"].toDouble();
+ newText = (currTime < 10000 ? "0":"") + QString::number( currTime / 1000.0, 'f', 3 );
+ break;
+ }
+ case FAILED:
+ newText = "False Start";
+ break;
+ case CANCELLED:
+ newText = "Cancelled";
+ break;
+ case DISABLED:
+ newText = "---";
+ break;
+ }
+
+ QVariantMap timerMap = {{"text", newText}, {"reactTime", this->remoteTimers.toList()[i].toMap()["reactTime"].toInt()}, {"state", this->remoteTimers.toList()[i].toMap()["state"].toInt()}};
+ tmpTimerTextList.append(timerMap);
+ }
+
+ if(tmpTimerTextList != this->timerTextList) {
+ this->timerTextList = tmpTimerTextList;
+ emit this->timerTextChanged();
+ }
+
+ // calculate next start action delay progress
+ double nextStartActionRemainingDelay = this->nextStartActionTotalDelay - ( this->date->currentMSecsSinceEpoch() - this->nextStartActionDelayStartedAt );
+ if(nextStartActionRemainingDelay > 0){
+ this->nextStartActionDelayProgress = nextStartActionRemainingDelay / this->nextStartActionTotalDelay;
+ emit this->nextStartActionDelayProgressChanged();
+ }
+ else {
+ this->nextStartActionDelayProgress = 0;
+ emit this->nextStartActionDelayProgressChanged();
+ }
+
+ this->timerTextRefreshTimer->start();
+}
+
+// -----------
+// - for qml -
+// -----------
+
+void BaseConn::setIP(const QString &ipAdress){
+ this->ip = ipAdress;
+}
+
+QString BaseConn::getIP() const
+{
+ return(this->ip);
+}
+
+QString BaseConn::getState() const
+{
+ return(this->state);
+}
+
+QVariant BaseConn::getTimerTextList()
+{
+ return this->timerTextList;
+}
+
+int BaseConn::getRaceState()
+{
+ return this->remoteRaceState;
+}
+
+double BaseConn::getNextStartActionDelayProgress() {
+ return this->nextStartActionDelayProgress;
+}
+
+int BaseConn::getNextStartAction() {
+ return this->nextStartAction;
+}
diff --git a/ScStwMonitorSrc/baseconn.h b/ScStwMonitorSrc/baseconn.h
new file mode 100755
index 0000000..d7bf76f
--- /dev/null
+++ b/ScStwMonitorSrc/baseconn.h
@@ -0,0 +1,153 @@
+#ifndef BASECONN_H
+#define BASECONN_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+class BaseConn : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString ipAddress READ getIP WRITE setIP)
+ Q_PROPERTY(QString state READ getState NOTIFY stateChanged)
+ Q_PROPERTY(QVariant timers READ getTimerTextList NOTIFY timerTextChanged)
+ Q_PROPERTY(int raceState READ getRaceState NOTIFY raceStateChanged)
+
+ Q_PROPERTY(double nextStartActionDelayProgress READ getNextStartActionDelayProgress NOTIFY nextStartActionDelayProgressChanged)
+ Q_PROPERTY(int nextStartAction READ getNextStartAction NOTIFY nextStartActionChanged)
+
+public:
+ explicit BaseConn(QObject *parent = nullptr);
+
+ // values for the socket connection
+ QString ip;
+ ushort port = 3563;
+ int errors;
+ int errors_until_disconnect = 4;
+
+ // the current state
+ QString state;
+ // can be:
+ // - 'disconnected'
+ // - 'connecting'
+ // - 'connected'
+
+ QString latestReadReply;
+
+ //---general status values---//
+
+ // stuff for storing the timers
+ enum timerState { IDLE, STARTING, WAITING, RUNNING, WON, LOST, FAILED, CANCELLED, DISABLED };
+
+ QVariant remoteTimers;
+ QVariant timerTextList;
+ int remoteRaceState;
+
+ // for next start action
+ enum NextStartAction { AtYourMarks, Ready, Start, None };
+ NextStartAction nextStartAction;
+ double nextStartActionDelayProgress;
+ // only used in remote mode:
+ double nextStartActionDelayStartedAt;
+ double nextStartActionTotalDelay;
+
+private:
+ QDateTime *date;
+ //to get the current time
+
+ QTcpSocket *socket;
+ //socket for communication with the extention
+
+ QTimer *autoConnectRetryTimer; // timer to frequently trigger a connection attempt to the base station
+ QTimer *timeoutTimer; // timer to trigger connection timeout
+ QTimer *timerTextRefreshTimer; // timer to refresh the text of the timers on the frontend
+
+ QString readBuffer;
+
+ int nextConnectionId;
+
+ struct waitingRequest {
+ int id;
+ QEventLoop * loop;
+ QJsonObject reply;
+ };
+
+ QList waitingRequests;
+
+signals:
+ void stateChanged();
+ //is emitted, when the connection state changes
+
+ void gotUnexpectedReply(QString reply);
+
+ void gotUpdate(QVariantMap data);
+
+ void gotError(QString error);
+
+ // for qml
+ void timerTextChanged();
+ void raceStateChanged();
+
+ void nextStartActionChanged();
+ void nextStartActionDelayProgressChanged();
+
+public slots:
+
+ Q_INVOKABLE void connectToHost();
+ //function to connect to the base station
+
+ void connectionTimeout();
+
+ Q_INVOKABLE bool init();
+ Q_INVOKABLE void deInit();
+
+ Q_INVOKABLE void closeConnection();
+
+ void gotError(QAbstractSocket::SocketError err);
+
+ // --- socket communication handling ---
+
+ Q_INVOKABLE QVariantMap sendCommand(int header, QJsonValue data = "");
+
+ // helper functions
+ void doConnectionAttempt();
+ void setState(QString newState);
+ int writeRemoteSetting(QString key, QString value);
+ QString readRemoteSetting(QString key);
+
+ // for timer sync
+ void handleUpdate(QVariantMap data);
+ void refreshRemoteTimers(QVariantList timers);
+ void refreshTimerTextList();
+
+ // for qml
+ QString getIP() const;
+ void setIP(const QString &ipAdress);
+
+ QString getState() const;
+
+ QVariant getTimerTextList();
+ int getRaceState();
+
+ Q_INVOKABLE double getNextStartActionDelayProgress();
+ Q_INVOKABLE int getNextStartAction();
+
+private slots:
+ void readyRead();
+
+ void processSocketMessage(QString message);
+
+ void socketReplyRecieved(QString reply);
+
+ void socketStateChanged(QAbstractSocket::SocketState socketState);
+};
+
+#endif // BASECONN_H
diff --git a/ScStwMonitorSrc/fonts/Arvo-Bold.ttf b/ScStwMonitorSrc/fonts/Arvo-Bold.ttf
new file mode 100644
index 0000000..38341b1
Binary files /dev/null and b/ScStwMonitorSrc/fonts/Arvo-Bold.ttf differ
diff --git a/ScStwMonitorSrc/fonts/Arvo-BoldItalic.ttf b/ScStwMonitorSrc/fonts/Arvo-BoldItalic.ttf
new file mode 100644
index 0000000..b87118b
Binary files /dev/null and b/ScStwMonitorSrc/fonts/Arvo-BoldItalic.ttf differ
diff --git a/ScStwMonitorSrc/fonts/Arvo-Regular.ttf b/ScStwMonitorSrc/fonts/Arvo-Regular.ttf
new file mode 100644
index 0000000..d8d0ec8
Binary files /dev/null and b/ScStwMonitorSrc/fonts/Arvo-Regular.ttf differ
diff --git a/ScStwMonitorSrc/fonts/Arvo-RegularItalic.ttf b/ScStwMonitorSrc/fonts/Arvo-RegularItalic.ttf
new file mode 100644
index 0000000..1a19337
Binary files /dev/null and b/ScStwMonitorSrc/fonts/Arvo-RegularItalic.ttf differ
diff --git a/ScStwMonitorSrc/fonts/PTMono-Regular.ttf b/ScStwMonitorSrc/fonts/PTMono-Regular.ttf
new file mode 100644
index 0000000..13a8004
Binary files /dev/null and b/ScStwMonitorSrc/fonts/PTMono-Regular.ttf differ
diff --git a/ScStwMonitorSrc/headers/scstwmonitorbackend.h b/ScStwMonitorSrc/headers/scstwmonitorbackend.h
new file mode 100644
index 0000000..80255d0
--- /dev/null
+++ b/ScStwMonitorSrc/headers/scstwmonitorbackend.h
@@ -0,0 +1,37 @@
+#ifndef SCSTWMONITORBACKEND_H
+#define SCSTWMONITORBACKEND_H
+
+#include
+#include
+#include
+
+class ScStwMonitorBackend : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(ScStwRace* race READ getRace NOTIFY raceChanged)
+ Q_PROPERTY(ScStwClient *scStwClient READ getScStwClient NOTIFY scStwClientChanged)
+public:
+ explicit ScStwMonitorBackend(QObject *parent = nullptr);
+
+private:
+ ScStwClient * scStwClient;
+ QTimer *autoConnectRetryTimer; // timer to frequently trigger a connection attempt to the base station
+ QTimer * timerTextRefreshTimer;
+ ScStwRemoteMonitorRace * remoteRace;
+
+public slots:
+ // functions for qml
+ Q_INVOKABLE ScStwRace *getRace();
+ Q_INVOKABLE ScStwClient *getScStwClient();
+
+private slots:
+ void refreshTimerText();
+ void doConnectionAttempt();
+
+signals:
+ void raceChanged();
+ void scStwClientChanged();
+};
+
+#endif // SCSTWMONITORBACKEND_H
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/100.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/100.png
new file mode 100644
index 0000000..972f35a
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/100.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/1024.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/1024.png
new file mode 100644
index 0000000..9d13b94
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/1024.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/114.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/114.png
new file mode 100644
index 0000000..39a25aa
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/114.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/120.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/120.png
new file mode 100644
index 0000000..014d1ec
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/120.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/128.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/128.png
new file mode 100644
index 0000000..72cfa33
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/128.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/144.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/144.png
new file mode 100644
index 0000000..7562f0f
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/144.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/152.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/152.png
new file mode 100644
index 0000000..41643ef
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/152.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/16.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/16.png
new file mode 100644
index 0000000..a2719ae
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/16.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/167.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/167.png
new file mode 100644
index 0000000..8f24c2e
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/167.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/172.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/172.png
new file mode 100644
index 0000000..032c5f4
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/172.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/180.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/180.png
new file mode 100644
index 0000000..3fa5afc
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/180.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/196.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/196.png
new file mode 100644
index 0000000..63feac7
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/196.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/20.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/20.png
new file mode 100644
index 0000000..b9b4d4d
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/20.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/216.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/216.png
new file mode 100644
index 0000000..2a017a8
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/216.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/256.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/256.png
new file mode 100644
index 0000000..4ae8a84
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/256.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/29.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/29.png
new file mode 100644
index 0000000..7e2784b
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/29.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/32.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/32.png
new file mode 100644
index 0000000..92f455c
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/32.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/40.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/40.png
new file mode 100644
index 0000000..d4b653d
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/40.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/48.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/48.png
new file mode 100644
index 0000000..b0624af
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/48.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/50.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/50.png
new file mode 100644
index 0000000..fb1bf7b
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/50.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/512.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/512.png
new file mode 100644
index 0000000..56915ac
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/512.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/55.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/55.png
new file mode 100644
index 0000000..7a59a85
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/55.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/57.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/57.png
new file mode 100644
index 0000000..9a04433
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/57.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/58.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/58.png
new file mode 100644
index 0000000..428f192
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/58.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/60.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/60.png
new file mode 100644
index 0000000..b9533b0
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/60.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/64.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/64.png
new file mode 100644
index 0000000..feb066c
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/64.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/72.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/72.png
new file mode 100644
index 0000000..192afce
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/72.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/76.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/76.png
new file mode 100644
index 0000000..7cc238e
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/76.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/80.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/80.png
new file mode 100644
index 0000000..742dd26
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/80.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/87.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/87.png
new file mode 100644
index 0000000..6e55f23
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/87.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/88.png b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/88.png
new file mode 100644
index 0000000..e42d3e8
Binary files /dev/null and b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/88.png differ
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/Contents.json b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..e138c0b
--- /dev/null
+++ b/ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1 @@
+{"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"idiom":"watch","filename":"172.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"86x86","expected-size":"172","role":"quickLook"},{"idiom":"watch","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"40x40","expected-size":"80","role":"appLauncher"},{"idiom":"watch","filename":"88.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"40mm","scale":"2x","size":"44x44","expected-size":"88","role":"appLauncher"},{"idiom":"watch","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"44mm","scale":"2x","size":"50x50","expected-size":"100","role":"appLauncher"},{"idiom":"watch","filename":"196.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"42mm","scale":"2x","size":"98x98","expected-size":"196","role":"quickLook"},{"idiom":"watch","filename":"216.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"44mm","scale":"2x","size":"108x108","expected-size":"216","role":"quickLook"},{"idiom":"watch","filename":"48.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"24x24","expected-size":"48","role":"notificationCenter"},{"idiom":"watch","filename":"55.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"42mm","scale":"2x","size":"27.5x27.5","expected-size":"55","role":"notificationCenter"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch","role":"companionSettings","scale":"3x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch","role":"companionSettings","scale":"2x"},{"size":"1024x1024","expected-size":"1024","filename":"1024.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch-marketing","scale":"1x"},{"size":"128x128","expected-size":"128","filename":"128.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"256x256","expected-size":"256","filename":"256.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"128x128","expected-size":"256","filename":"256.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"256x256","expected-size":"512","filename":"512.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"32","filename":"32.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"512x512","expected-size":"512","filename":"512.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"16","filename":"16.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"32","filename":"32.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"64","filename":"64.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"512x512","expected-size":"1024","filename":"1024.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"}]}
\ No newline at end of file
diff --git a/ScStwMonitorSrc/icon/Assets.xcassets/Contents.json b/ScStwMonitorSrc/icon/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/ScStwMonitorSrc/icon/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/ScStwMonitorSrc/icon/favicon.png b/ScStwMonitorSrc/icon/favicon.png
new file mode 100644
index 0000000..a26d006
Binary files /dev/null and b/ScStwMonitorSrc/icon/favicon.png differ
diff --git a/ScStwMonitorSrc/icon/favicon.xcf b/ScStwMonitorSrc/icon/favicon.xcf
new file mode 100644
index 0000000..53636a6
Binary files /dev/null and b/ScStwMonitorSrc/icon/favicon.xcf differ
diff --git a/ScStwMonitorSrc/icon/faviconNoOutline.png b/ScStwMonitorSrc/icon/faviconNoOutline.png
new file mode 100644
index 0000000..e75c63a
Binary files /dev/null and b/ScStwMonitorSrc/icon/faviconNoOutline.png differ
diff --git a/ScStwMonitorSrc/icon/icons8-monitor-96.png b/ScStwMonitorSrc/icon/icons8-monitor-96.png
new file mode 100644
index 0000000..e6c9434
Binary files /dev/null and b/ScStwMonitorSrc/icon/icons8-monitor-96.png differ
diff --git a/ScStwMonitorSrc/main.cpp b/ScStwMonitorSrc/main.cpp
new file mode 100755
index 0000000..ab55a3e
--- /dev/null
+++ b/ScStwMonitorSrc/main.cpp
@@ -0,0 +1,63 @@
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include "headers/scstwmonitorbackend.h"
+#if defined(Q_OS_IOS)
+#include "sleepprevent.h"
+#endif
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+
+ QGuiApplication app(argc, argv);
+
+ app.setOrganizationName("itsblue");
+ app.setOrganizationDomain("itsblue.de");
+
+ qmlRegisterType("de.itsblue.ScStwMonitor", 2, 0, "ScStwMonitorBackend");
+ // setup speed backend and App themes
+ qmlRegisterType("de.itsblue.ScStw", 2, 0, "ScStwRace");
+ qmlRegisterType("de.itsblue.ScStw", 2, 0, "ScStwTimer");
+ qmlRegisterType("de.itsblue.ScStw", 2, 0, "ScStw");
+ qmlRegisterType("de.itsblue.ScStw", 2, 0, "ScStwClient");
+ //qmlRegisterUncreatableType("de.itsblue.ScStw", 2, 0, "ScStwAppTheme", "The ScStwAppTheme has to be managed by a ScStwAppTheme manager and is therefore not creatable");
+ //qmlRegisterType("de.itsblue.ScStw", 2, 0, "ScStwAppThemeManager");
+
+
+ QQmlApplicationEngine engine;
+
+ QSize size = app.screens().first()->size();
+ engine.rootContext()->setContextProperty("XscreenHeight", size.height());
+ engine.rootContext()->setContextProperty("XscreenWidth", size.width());
+
+ if(argc > 1 && QString(argv[1]) == "--noControls")
+ engine.rootContext()->setContextProperty("showControls", false);
+ else
+ engine.rootContext()->setContextProperty("showControls", true);
+
+ engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
+ if (engine.rootObjects().isEmpty())
+ return -1;
+
+ #if defined(Q_OS_IOS)
+ SleepPrevent sp;
+ sp.setTimerDisabled();
+ #endif
+
+ // move the cursor into an invisible position
+ QScreen *screen = QGuiApplication::primaryScreen();
+ QRect rect = screen->geometry();
+ QCursor::setPos(rect.width(),rect.height());
+
+ return app.exec();
+}
diff --git a/ScStwMonitorSrc/main.qml b/ScStwMonitorSrc/main.qml
new file mode 100755
index 0000000..14b0ebf
--- /dev/null
+++ b/ScStwMonitorSrc/main.qml
@@ -0,0 +1,417 @@
+import QtQuick 2.9
+import QtQuick.Window 2.2
+import QtQuick.Controls 2.2
+import QtGraphicalEffects 1.0
+import Qt.labs.settings 1.0
+import QtQuick.Layouts 1.0
+
+import de.itsblue.ScStw 2.0
+import de.itsblue.ScStwMonitor 2.0
+
+Window {
+ id: window
+ visible: true
+ width: XscreenWidth / 2
+ height: XscreenHeight / 2
+ title: qsTr("ScStwMonitor")
+
+ //visibility: Window.FullScreen
+
+ Page {
+ id: app
+ anchors.fill: parent
+
+ function landscape() {
+ return app.width > app.height
+ }
+
+ ScStwMonitorBackend {
+ id: backend
+ scStwClient.ipAddress: appSettings.baseStationIp
+ }
+
+ Settings {
+ id: appSettings
+ property string baseStationIp: "192.168.4.1"
+ }
+
+ Shortcut {
+ sequences: ["Ctrl+Q", StandardKey.Back]
+ onActivated: Qt.quit()
+ }
+
+ Shortcut {
+ sequences: ["F11", "Esc"]
+ onActivated: {
+ if(window.visibility === Window.FullScreen) {
+ window.visibility = Window.Windowed
+ }
+ else {
+ window.visibility = Window.FullScreen
+ }
+ }
+ }
+
+ FontLoader {
+ id: timerFont
+ source:"qrc:///fonts/PTMono-Regular.ttf"
+ }
+
+ Loader {
+ id: mainComponentLoader
+
+ anchors.fill: parent
+
+ sourceComponent: backend.scStwClient.state === ScStwClient.CONNECTED ? displayComp:loadingComp
+ }
+
+ Component {
+ id: displayComp
+
+ Item {
+ id: displayItm
+
+ anchors.fill: parent
+
+ Image {
+ id: bannerImg
+
+ anchors {
+ top: parent.top
+ left: parent.left
+ right: parent.right
+ margins: app.landscape() ? app.height * 0.01:app.width * 0.1
+ }
+
+ height: app.landscape() ? app.height * 0.25:app.height * 0.2
+
+ visible: showControls
+
+ fillMode: Image.PreserveAspectFit
+ mipmap: true
+
+ source: "qrc:/Banner.png"
+ }
+
+ TimerColumn {
+ anchors.fill: parent
+
+ opacity: !showControls || [ScStwRace.IDLE,ScStwRace.STARTING].indexOf(backend.race.state) < 0 ? 1:0
+
+ Behavior on opacity {
+ NumberAnimation {
+ duration: 200
+ }
+ }
+ }
+
+ Item {
+ id: controlsItm
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ bottom: parent.bottom
+ }
+
+ visible: showControls
+
+ Label {
+ id: clickHintLabel
+
+ property string implicitText:[
+ "tap anywhere\nto start",
+ "NEXT_START_ACTION",
+ "please wait...",
+ "running\ntap anywhere to stop",
+ "tap anywhere to reset"
+ ][backend.race.state]
+
+ anchors.fill: parent
+
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+
+ fontSizeMode: Text.Fit
+
+ font.pixelSize: height * 0.3
+
+ color: backend.race.state === ScStwRace.STARTING ? "#e0b928":"grey"
+
+ text: implicitText === "NEXT_START_ACTION" ? ["", "at your \nmarks", "ready", "starting..."][backend.race.nextStartActionDetails[ScStwRace.NextStartAction]+1]:implicitText
+
+ Behavior on text {
+ FadeAnimation {
+ target: clickHintLabel
+ fadeDuration: 150
+ }
+ }
+ }
+
+ ProgressBar {
+ id: nextActiondelayProgressBar
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ bottom: parent.bottom
+ }
+
+ height: app.landscape() ? app.height * 0.1:app.width * 0.1
+
+ opacity: backend.race.nextStartActionDetails[ScStwRace.NextStartAction] < 3 && backend.race.state === ScStwRace.STARTING ? 1:0
+
+ value: backend.race.nextStartActionDetails[ScStwRace.NextStartActionDelayProgress]
+
+
+ background: Rectangle {
+ implicitWidth: 200
+ implicitHeight: parent.height
+ color: "lightgrey"
+ }
+
+ contentItem: Item {
+ implicitWidth: 200
+ implicitHeight: parent.height
+
+ Rectangle {
+ width: nextActiondelayProgressBar.visualPosition * parent.width
+ height: parent.height
+ color: "grey"
+ }
+ }
+
+ Behavior on opacity {
+ NumberAnimation {
+ duration: 200
+ }
+ }
+ }
+
+ MouseArea {
+
+ parent: app
+
+ anchors.fill: parent
+
+ visible: controlsItm.visible
+
+ enabled: visible
+
+ onClicked: {
+ switch (backend.race.state) {
+ case ScStwRace.IDLE:
+ // IDLE
+ backend.race.start()
+ break;
+ case ScStwRace.STARTING:
+ // STARTING
+ backend.race.cancel()
+ break;
+ case ScStwRace.WAITING:
+ // WAITING
+ break;
+ case ScStwRace.RUNNING:
+ // RUNNING
+ backend.race.stop()
+ break;
+ case ScStwRace.STOPPED:
+ // STOPPED
+ backend.race.reset()
+ break;
+
+ }
+ }
+
+ RowLayout {
+ id: volumeSliderRow
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ bottom: parent.bottom
+ margins: 5
+ }
+
+ height: app.landscape() ? app.height * 0.1:app.width * 0.12
+ spacing: 0
+
+ opacity: backend.race.state === ScStwRace.IDLE ? 1:0
+ visible: opacity > 0
+
+ Behavior on opacity {
+ NumberAnimation {
+ duration: 200
+ }
+ }
+
+ Image {
+ Layout.preferredHeight: parent.height * 0.5
+ Layout.preferredWidth: height * 0.7
+ Layout.alignment: Layout.Center
+
+ mipmap: true
+
+ fillMode: Image.PreserveAspectFit
+ source: "qrc:/VolumeLow.png"
+ }
+
+ Slider {
+ id: volumeSlider
+
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+
+ implicitWidth: 200
+
+ value: parseFloat(backend.scStwClient.readRemoteSetting(ScStw.SoundVolumeSetting))
+
+ onPressedChanged: {
+ if(!pressed){
+ volumeSlider.enabled = false
+ backend.scStwClient.writeRemoteSetting(ScStw.SoundVolumeSetting, value)
+ volumeSlider.enabled = true
+ }
+ }
+
+ leftPadding: width * (app.landscape() ? 0.02:0.05)
+ rightPadding: leftPadding
+
+ background: Rectangle {
+ x: volumeSlider.leftPadding
+ y: volumeSlider.topPadding + volumeSlider.availableHeight / 2 - height / 2
+ implicitWidth: 200
+ implicitHeight: volumeSliderRow.height * 0.1
+ width: volumeSlider.availableWidth
+ height: implicitHeight
+ radius: 2
+ color: "#bdbebf"
+
+ Rectangle {
+ width: volumeSlider.visualPosition * parent.width
+ height: parent.height
+ color: "grey"
+ radius: 2
+ }
+ }
+
+ handle: Item {
+ x: volumeSlider.leftPadding - width * 0.15 + volumeSlider.visualPosition * (volumeSlider.availableWidth - width * 0.55)
+ y: volumeSlider.topPadding + volumeSlider.availableHeight / 2 - height / 2
+ implicitWidth: volumeSliderRow.height
+ implicitHeight: implicitWidth
+
+ Image {
+ anchors.fill: parent
+
+ fillMode: Image.PreserveAspectFit
+
+ source: "qrc:/SpeedHold.png"
+ }
+ }
+ }
+
+ Image {
+ Layout.preferredHeight: parent.height * 0.5
+ Layout.preferredWidth: height
+ Layout.alignment: Layout.Center
+
+ mipmap: true
+ fillMode: Image.PreserveAspectFit
+ source: "qrc:/VolumeHigh.png"
+ }
+
+ }
+ }
+
+ states: [
+ State {
+ when: [ScStwRace.IDLE,ScStwRace.STARTING].indexOf(backend.race.state) >= 0
+ name: "big"
+ PropertyChanges {
+ target: clickHintLabel
+
+ anchors.margins: app.landscape() ? app.height * 0.2:app.width * 0.1
+
+ width: parent.width * 0.7
+ height: parent.height * 0.7
+ }
+
+ PropertyChanges {
+ target: controlsItm
+
+ height: app.height
+ }
+ },
+ State {
+ when: [ScStwRace.IDLE,ScStwRace.STARTING].indexOf(backend.race.state) < 0
+ name: "small"
+
+ PropertyChanges {
+ target: clickHintLabel
+
+ anchors.margins: app.landscape() ? app.height * 0.01:app.width * 0.1
+ }
+
+ PropertyChanges {
+ target: controlsItm
+
+ height: app.landscape() ? app.height * 0.2:app.height * 0.4
+ }
+ }
+ ]
+
+ transitions: [
+ Transition {
+ from: "*"
+ to: "*"
+
+ PauseAnimation {
+ duration: 150
+ }
+ }
+ ]
+
+ }
+
+ }
+ }
+
+ Component {
+ id: loadingComp
+
+ Item {
+ id: loadingItm
+
+ anchors.fill: parent
+
+ FancyBusyIndicator {
+ id: loadingInd
+ anchors.centerIn: parent
+
+ width: app.landscape() ? parent.height * 0.2 : parent.width *0.2
+ height: width
+ }
+
+ TextField {
+ id: ipAddrInputTf
+
+ anchors {
+ bottom: parent.bottom
+ horizontalCenter: parent.horizontalCenter
+ }
+
+ opacity: backend.scStwClient.state === ScStwClient.CONNECTED ? 0:1
+
+ text: appSettings.baseStationIp
+
+ onEditingFinished: {
+ appSettings.baseStationIp = text
+ backend.scStwClient.ipAddress = text
+ }
+ }
+ }
+
+ }
+ }
+}
diff --git a/ScStwMonitorSrc/qml.qrc b/ScStwMonitorSrc/qml.qrc
new file mode 100755
index 0000000..e80de3b
--- /dev/null
+++ b/ScStwMonitorSrc/qml.qrc
@@ -0,0 +1,8 @@
+
+
+ main.qml
+ FancyBusyIndicator.qml
+ TimerColumn.qml
+ FadeAnimation.qml
+
+
diff --git a/ScStwMonitorSrc/shared.qrc b/ScStwMonitorSrc/shared.qrc
new file mode 100644
index 0000000..fb10f14
--- /dev/null
+++ b/ScStwMonitorSrc/shared.qrc
@@ -0,0 +1,13 @@
+
+
+ fonts/Arvo-Bold.ttf
+ fonts/Arvo-BoldItalic.ttf
+ fonts/Arvo-Regular.ttf
+ fonts/Arvo-RegularItalic.ttf
+ fonts/PTMono-Regular.ttf
+ Banner.png
+ SpeedHold.png
+ VolumeHigh.png
+ VolumeLow.png
+
+
diff --git a/ScStwMonitorSrc/sleepprevent.h b/ScStwMonitorSrc/sleepprevent.h
new file mode 100644
index 0000000..1934484
--- /dev/null
+++ b/ScStwMonitorSrc/sleepprevent.h
@@ -0,0 +1,5 @@
+class SleepPrevent
+{
+public:
+ void setTimerDisabled();
+};
diff --git a/ScStwMonitorSrc/sleepprevent.mm b/ScStwMonitorSrc/sleepprevent.mm
new file mode 100644
index 0000000..3c16e48
--- /dev/null
+++ b/ScStwMonitorSrc/sleepprevent.mm
@@ -0,0 +1,7 @@
+#import
+#import
+#include "sleepprevent.h"
+
+void SleepPrevent::setTimerDisabled() {
+ [[UIApplication sharedApplication] setIdleTimerDisabled: YES];
+}
diff --git a/ScStwMonitorSrc/sources/scstwmonitorbackend.cpp b/ScStwMonitorSrc/sources/scstwmonitorbackend.cpp
new file mode 100644
index 0000000..973b9d7
--- /dev/null
+++ b/ScStwMonitorSrc/sources/scstwmonitorbackend.cpp
@@ -0,0 +1,54 @@
+#include "../headers/scstwmonitorbackend.h"
+
+ScStwMonitorBackend::ScStwMonitorBackend(QObject *parent) : QObject(parent)
+{
+ this->scStwClient = new ScStwClient();
+ this->remoteRace = new ScStwRemoteMonitorRace(this->scStwClient, this);
+
+ // init refresh timers
+ this->autoConnectRetryTimer = new QTimer(this);
+ this->autoConnectRetryTimer->setInterval(1000);
+ this->autoConnectRetryTimer->setSingleShot(true);
+ connect(this->autoConnectRetryTimer, &QTimer::timeout, this, &ScStwMonitorBackend::doConnectionAttempt);
+ this->autoConnectRetryTimer->start();
+
+ this->timerTextRefreshTimer = new QTimer(this);
+ this->timerTextRefreshTimer->setInterval(1);
+ this->timerTextRefreshTimer->setSingleShot(true);
+ this->timerTextRefreshTimer->connect(this->timerTextRefreshTimer, &QTimer::timeout, this, &ScStwMonitorBackend::refreshTimerText);
+ this->refreshTimerText();
+
+}
+
+void ScStwMonitorBackend::refreshTimerText() {
+
+ // --- refresh timer text ---
+ if(this->getRace()->getState() == ScStwRace::RUNNING) {
+ emit this->getRace()->timersChanged();
+ }
+
+ // --- refresh next start action delay progress ---
+ if(this->getRace()->getState() == ScStwRace::STARTING) {
+ emit this->getRace()->nextStartActionDetailsChanged();
+ }
+
+ this->timerTextRefreshTimer->start();
+}
+
+void ScStwMonitorBackend::doConnectionAttempt()
+{
+ if(this->scStwClient->getState() == ScStwClient::DISCONNECTED) {
+ qDebug() << "+--- trying to connect";
+ this->scStwClient->connectToHost();
+ }
+
+ this->autoConnectRetryTimer->start();
+}
+
+ScStwRace* ScStwMonitorBackend::getRace() {
+ return this->remoteRace;
+}
+
+ScStwClient* ScStwMonitorBackend::getScStwClient() {
+ return this->scStwClient;
+}
diff --git a/shared-libraries b/shared-libraries
new file mode 160000
index 0000000..bd52819
--- /dev/null
+++ b/shared-libraries
@@ -0,0 +1 @@
+Subproject commit bd52819a96394d9a59078cf7382453ede1c7b30d