adapted to ScStw shared-libraries
not working: volume setting
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "shared-libraries"]
|
||||||
|
path = shared-libraries
|
||||||
|
url = https://git.itsblue.de/ScStw/shared-libraries/
|
14
ScStwMonitor.pro
Normal file
|
@ -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)
|
||||||
|
}
|
1000
ScStwMonitor.pro.user
Normal file
2
ScStwMonitorSrc/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
*.pro.user
|
||||||
|
*.pro.user*
|
BIN
ScStwMonitorSrc/Banner.png
Normal file
After Width: | Height: | Size: 25 KiB |
72
ScStwMonitorSrc/FadeAnimation.qml
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
61
ScStwMonitorSrc/FancyBusyIndicator.qml
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
4
ScStwMonitorSrc/ScStwMonitor.desktop
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=ScStwMonitor
|
||||||
|
Exec=/opt/ScStwMonitor/bin/ScStwMonitor
|
74
ScStwMonitorSrc/ScStwMonitorSrc.pro
Executable file
|
@ -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
|
||||||
|
}
|
||||||
|
|
BIN
ScStwMonitorSrc/SpeedHold.png
Normal file
After Width: | Height: | Size: 10 KiB |
146
ScStwMonitorSrc/TimerColumn.qml
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
ScStwMonitorSrc/VolumeHigh.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
ScStwMonitorSrc/VolumeLow.png
Normal file
After Width: | Height: | Size: 5.7 KiB |
88
ScStwMonitorSrc/android/AndroidManifest.xml
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<manifest package="de.itsblue.ScStwMonitor" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="-- %%INSERT_VERSION_NAME%% --" android:versionCode="-- %%INSERT_VERSION_CODE%% --" android:installLocation="auto">
|
||||||
|
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="28"/>
|
||||||
|
|
||||||
|
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
|
||||||
|
Remove the comment if you do not require these default permissions. -->
|
||||||
|
<!-- %%INSERT_PERMISSIONS -->
|
||||||
|
|
||||||
|
<!-- The following comment will be replaced upon deployment with default features based on the dependencies of the application.
|
||||||
|
Remove the comment if you do not require these default features. -->
|
||||||
|
<!-- %%INSERT_FEATURES -->
|
||||||
|
|
||||||
|
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
|
||||||
|
|
||||||
|
<application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="ScStwMonitor" android:icon="@drawable/icon">
|
||||||
|
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="de.itsblue.SpeedClimbingStopwatchMonitor.MainActivity" android:label="ScStw Monitor" android:screenOrientation="unspecified" android:launchMode="singleTop">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<!-- Application arguments -->
|
||||||
|
<!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ -->
|
||||||
|
<!-- Application arguments -->
|
||||||
|
|
||||||
|
<meta-data android:name="android.app.lib_name" android:value="ScStwMonitor"/>
|
||||||
|
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
|
||||||
|
<meta-data android:name="android.app.repository" android:value="default"/>
|
||||||
|
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
|
||||||
|
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
|
||||||
|
<!-- Deploy Qt libs as part of package -->
|
||||||
|
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>
|
||||||
|
<meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
|
||||||
|
<meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/>
|
||||||
|
<!-- Run with local libs -->
|
||||||
|
<meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
|
||||||
|
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
|
||||||
|
<meta-data android:name="android.app.load_local_libs" android:value="-- %%INSERT_LOCAL_LIBS%% --"/>
|
||||||
|
<meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
|
||||||
|
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
|
||||||
|
<!-- Used to specify custom system library path to run with local system libs -->
|
||||||
|
<!-- <meta-data android:name="android.app.system_libs_prefix" android:value="/system/lib/"/> -->
|
||||||
|
<!-- Messages maps -->
|
||||||
|
<meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
|
||||||
|
<meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
|
||||||
|
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
|
||||||
|
<meta-data android:value="@string/unsupported_android_version" android:name="android.app.unsupported_android_version"/>
|
||||||
|
<!-- Messages maps -->
|
||||||
|
|
||||||
|
<!-- Splash screen -->
|
||||||
|
<!-- Orientation-specific (portrait/landscape) data is checked first. If not available for current orientation,
|
||||||
|
then android.app.splash_screen_drawable. For best results, use together with splash_screen_sticky and
|
||||||
|
use hideSplashScreen() with a fade-out animation from Qt Android Extras to hide the splash screen when you
|
||||||
|
are done populating your window with content. -->
|
||||||
|
<!-- meta-data android:name="android.app.splash_screen_drawable_portrait" android:resource="@drawable/logo_portrait" / -->
|
||||||
|
<!-- meta-data android:name="android.app.splash_screen_drawable_landscape" android:resource="@drawable/logo_landscape" / -->
|
||||||
|
<!-- meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/ -->
|
||||||
|
<!-- meta-data android:name="android.app.splash_screen_sticky" android:value="true"/ -->
|
||||||
|
<!-- Splash screen -->
|
||||||
|
|
||||||
|
<!-- Background running -->
|
||||||
|
<!-- Warning: changing this value to true may cause unexpected crashes if the
|
||||||
|
application still try to draw after
|
||||||
|
"applicationStateChanged(Qt::ApplicationSuspended)"
|
||||||
|
signal is sent! -->
|
||||||
|
<meta-data android:name="android.app.background_running" android:value="false"/>
|
||||||
|
<!-- Background running -->
|
||||||
|
|
||||||
|
<!-- auto screen scale factor -->
|
||||||
|
<meta-data android:name="android.app.auto_screen_scale_factor" android:value="false"/>
|
||||||
|
<!-- auto screen scale factor -->
|
||||||
|
|
||||||
|
<!-- extract android style -->
|
||||||
|
<!-- available android:values :
|
||||||
|
* default - In most cases this will be the same as "full", but it can also be something else if needed, e.g., for compatibility reasons
|
||||||
|
* full - useful QWidget & Quick Controls 1 apps
|
||||||
|
* minimal - useful for Quick Controls 2 apps, it is much faster than "full"
|
||||||
|
* none - useful for apps that don't use any of the above Qt modules
|
||||||
|
-->
|
||||||
|
<meta-data android:name="android.app.extract_android_style" android:value="default"/>
|
||||||
|
<!-- extract android style -->
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
57
ScStwMonitorSrc/android/build.gradle
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
BIN
ScStwMonitorSrc/android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
5
ScStwMonitorSrc/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -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
|
172
ScStwMonitorSrc/android/gradlew
vendored
Executable file
|
@ -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" "$@"
|
84
ScStwMonitorSrc/android/gradlew.bat
vendored
Normal file
|
@ -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
|
BIN
ScStwMonitorSrc/android/res/drawable-hdpi/icon.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
ScStwMonitorSrc/android/res/drawable-ldpi/icon.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
ScStwMonitorSrc/android/res/drawable-mdpi/icon.png
Normal file
After Width: | Height: | Size: 38 KiB |
25
ScStwMonitorSrc/android/res/values/libs.xml
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<resources>
|
||||||
|
<array name="qt_sources">
|
||||||
|
<item>https://download.qt.io/ministro/android/qt5/qt-5.9</item>
|
||||||
|
</array>
|
||||||
|
|
||||||
|
<!-- The following is handled automatically by the deployment tool. It should
|
||||||
|
not be edited manually. -->
|
||||||
|
|
||||||
|
<array name="bundled_libs">
|
||||||
|
<!-- %%INSERT_EXTRA_LIBS%% -->
|
||||||
|
</array>
|
||||||
|
|
||||||
|
<array name="qt_libs">
|
||||||
|
<!-- %%INSERT_QT_LIBS%% -->
|
||||||
|
</array>
|
||||||
|
|
||||||
|
<array name="bundled_in_lib">
|
||||||
|
<!-- %%INSERT_BUNDLED_IN_LIB%% -->
|
||||||
|
</array>
|
||||||
|
<array name="bundled_in_assets">
|
||||||
|
<!-- %%INSERT_BUNDLED_IN_ASSETS%% -->
|
||||||
|
</array>
|
||||||
|
|
||||||
|
</resources>
|
9
ScStwMonitorSrc/android/src/MainActivity.java
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
504
ScStwMonitorSrc/baseconn.cpp
Executable file
|
@ -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; i<this->waitingRequests.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 = "<message>";
|
||||||
|
QString endKey = "</message>";
|
||||||
|
|
||||||
|
//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.: <message>123456789</message>
|
||||||
|
}
|
||||||
|
else if(!message.contains(endKey) && (!this->readBuffer.isEmpty() || message.startsWith(startKey))){
|
||||||
|
// begin of a split message ( e.g.: <message>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</message> )
|
||||||
|
|
||||||
|
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.: <message>123456789</message><message>987654321</message> )
|
||||||
|
// or multiple message fragments in one message ( e.g.: 56789</message><message>987654321</message> or 56789</message><message>98765 )
|
||||||
|
//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("<message>", "");
|
||||||
|
reply.replace("</message>", "");
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
153
ScStwMonitorSrc/baseconn.h
Executable file
|
@ -0,0 +1,153 @@
|
||||||
|
#ifndef BASECONN_H
|
||||||
|
#define BASECONN_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QTcpSocket>
|
||||||
|
#include <QDataStream>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QEventLoop>
|
||||||
|
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonValue>
|
||||||
|
|
||||||
|
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<waitingRequest> 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
|
BIN
ScStwMonitorSrc/fonts/Arvo-Bold.ttf
Normal file
BIN
ScStwMonitorSrc/fonts/Arvo-BoldItalic.ttf
Normal file
BIN
ScStwMonitorSrc/fonts/Arvo-Regular.ttf
Normal file
BIN
ScStwMonitorSrc/fonts/Arvo-RegularItalic.ttf
Normal file
BIN
ScStwMonitorSrc/fonts/PTMono-Regular.ttf
Normal file
37
ScStwMonitorSrc/headers/scstwmonitorbackend.h
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
#ifndef SCSTWMONITORBACKEND_H
|
||||||
|
#define SCSTWMONITORBACKEND_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <scstwclient.h>
|
||||||
|
#include <scstwremotemonitorrace.h>
|
||||||
|
|
||||||
|
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
|
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/100.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/1024.png
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/114.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/120.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/128.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/144.png
Normal file
After Width: | Height: | Size: 4 KiB |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/152.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/16.png
Normal file
After Width: | Height: | Size: 199 B |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/167.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/172.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/180.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/196.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/20.png
Normal file
After Width: | Height: | Size: 241 B |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/216.png
Normal file
After Width: | Height: | Size: 7.5 KiB |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/256.png
Normal file
After Width: | Height: | Size: 9.7 KiB |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/29.png
Normal file
After Width: | Height: | Size: 428 B |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/32.png
Normal file
After Width: | Height: | Size: 428 B |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/40.png
Normal file
After Width: | Height: | Size: 669 B |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/48.png
Normal file
After Width: | Height: | Size: 817 B |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/50.png
Normal file
After Width: | Height: | Size: 833 B |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/512.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/55.png
Normal file
After Width: | Height: | Size: 954 B |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/57.png
Normal file
After Width: | Height: | Size: 1,014 B |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/58.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/60.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/64.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/72.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/76.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/80.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/87.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
ScStwMonitorSrc/icon/Assets.xcassets/AppIcon.appiconset/88.png
Normal file
After Width: | Height: | Size: 2 KiB |
6
ScStwMonitorSrc/icon/Assets.xcassets/Contents.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
BIN
ScStwMonitorSrc/icon/favicon.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
ScStwMonitorSrc/icon/favicon.xcf
Normal file
BIN
ScStwMonitorSrc/icon/faviconNoOutline.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
ScStwMonitorSrc/icon/icons8-monitor-96.png
Normal file
After Width: | Height: | Size: 545 B |
63
ScStwMonitorSrc/main.cpp
Executable file
|
@ -0,0 +1,63 @@
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QQmlApplicationEngine>
|
||||||
|
#include <QQmlContext>
|
||||||
|
#include <QCursor>
|
||||||
|
#include <QRect>
|
||||||
|
#include <QScreen>
|
||||||
|
|
||||||
|
#include <scstwrace.h>
|
||||||
|
#include <scstwtimer.h>
|
||||||
|
#include <ScStw.hpp>
|
||||||
|
#include <scstwclient.h>
|
||||||
|
|
||||||
|
#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<ScStwMonitorBackend>("de.itsblue.ScStwMonitor", 2, 0, "ScStwMonitorBackend");
|
||||||
|
// setup speed backend and App themes
|
||||||
|
qmlRegisterType<ScStwRace>("de.itsblue.ScStw", 2, 0, "ScStwRace");
|
||||||
|
qmlRegisterType<ScStwTimer>("de.itsblue.ScStw", 2, 0, "ScStwTimer");
|
||||||
|
qmlRegisterType<ScStw>("de.itsblue.ScStw", 2, 0, "ScStw");
|
||||||
|
qmlRegisterType<ScStwClient>("de.itsblue.ScStw", 2, 0, "ScStwClient");
|
||||||
|
//qmlRegisterUncreatableType<ScStwAppTheme>("de.itsblue.ScStw", 2, 0, "ScStwAppTheme", "The ScStwAppTheme has to be managed by a ScStwAppTheme manager and is therefore not creatable");
|
||||||
|
//qmlRegisterType<ScStwAppThemeManager>("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();
|
||||||
|
}
|
417
ScStwMonitorSrc/main.qml
Executable file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
ScStwMonitorSrc/qml.qrc
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
<RCC>
|
||||||
|
<qresource prefix="/">
|
||||||
|
<file>main.qml</file>
|
||||||
|
<file>FancyBusyIndicator.qml</file>
|
||||||
|
<file>TimerColumn.qml</file>
|
||||||
|
<file>FadeAnimation.qml</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
13
ScStwMonitorSrc/shared.qrc
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<RCC>
|
||||||
|
<qresource prefix="/">
|
||||||
|
<file>fonts/Arvo-Bold.ttf</file>
|
||||||
|
<file>fonts/Arvo-BoldItalic.ttf</file>
|
||||||
|
<file>fonts/Arvo-Regular.ttf</file>
|
||||||
|
<file>fonts/Arvo-RegularItalic.ttf</file>
|
||||||
|
<file>fonts/PTMono-Regular.ttf</file>
|
||||||
|
<file>Banner.png</file>
|
||||||
|
<file>SpeedHold.png</file>
|
||||||
|
<file>VolumeHigh.png</file>
|
||||||
|
<file>VolumeLow.png</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
5
ScStwMonitorSrc/sleepprevent.h
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
class SleepPrevent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void setTimerDisabled();
|
||||||
|
};
|
7
ScStwMonitorSrc/sleepprevent.mm
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
#include "sleepprevent.h"
|
||||||
|
|
||||||
|
void SleepPrevent::setTimerDisabled() {
|
||||||
|
[[UIApplication sharedApplication] setIdleTimerDisabled: YES];
|
||||||
|
}
|
54
ScStwMonitorSrc/sources/scstwmonitorbackend.cpp
Normal file
|
@ -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;
|
||||||
|
}
|
1
shared-libraries
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit bd52819a96394d9a59078cf7382453ede1c7b30d
|