...
 
Commits (2)
......@@ -12,6 +12,7 @@
#include <QFuture>
#include <QtConcurrent/QtConcurrent>
#include <string.h>
#include <QByteArray>
#include "headers/appsettings.h"
#include "headers/speedtimer.h"
......@@ -30,6 +31,18 @@ public:
int errors;
int errors_until_disconnect = 4;
QVariant connections;
QString latestReadReply;
//---general status values---//
// some meta data of the base
QString firmwareVersion;
bool firmwareUpToDate;
double timeOffset;
// the current state
QString state;
// can be:
......@@ -37,12 +50,6 @@ public:
// - 'connecting'
// - 'connected'
QVariant connections;
QString latestReadReply;
//---general status values---//
private:
QDateTime *date;
......@@ -86,32 +93,41 @@ signals:
void gotError(QString error);
void propertiesChanged();
public slots:
Q_INVOKABLE void connectToHost();
void connectToHost();
//function to connect to the base station
void connectionTimeout();
Q_INVOKABLE bool init();
Q_INVOKABLE void deInit();
bool init();
void deInit();
Q_INVOKABLE void closeConnection();
void closeConnection();
void gotError(QAbstractSocket::SocketError err);
// --- socket communication handling ---
Q_INVOKABLE QVariantMap sendCommand(int header, QJsonValue data = "");
QVariantMap sendCommand(int header, QJsonValue data = "", bool useTerminationKeys = true, int timeout = 3000);
// --- updater functions ---
bool updateTime();
bool updateFirmware();
bool isFirmwareUpToDate();
// --- helper functions ---
Q_INVOKABLE int writeRemoteSetting(QString key, QString value);
int writeRemoteSetting(QString key, QString value);
Q_INVOKABLE bool refreshConnections();
bool refreshConnections();
void setConnections(QVariantList connections);
// functions for the qml adapter
QString getIP() const;
void setIP(const QString &ipAdress);
......
......@@ -20,6 +20,7 @@ class ClimbingRace : public QObject
Q_PROPERTY(QVariant baseStationConnections READ getBaseStationConnections NOTIFY baseStationConnectionsChanged)
Q_PROPERTY(double nextStartActionDelayProgress READ getNextStartActionDelayProgress NOTIFY nextStartActionDelayProgressChanged)
Q_PROPERTY(int nextStartAction READ getNextStartAction NOTIFY nextStartActionChanged)
Q_PROPERTY(QVariantMap baseStationProperties READ getBaseStationProperties NOTIFY baseStationPropertiesChanged)
public:
explicit ClimbingRace(QObject *parent = nullptr);
......@@ -76,6 +77,7 @@ signals:
void timerTextChanged();
void baseStationStateChanged();
void baseStationConnectionsChanged();
void baseStationPropertiesChanged();
public slots:
Q_INVOKABLE int startRace();
......@@ -99,6 +101,10 @@ public slots:
Q_INVOKABLE void disconnectBaseStation();
Q_INVOKABLE QString getBaseStationState();
Q_INVOKABLE QVariant getBaseStationConnections();
Q_INVOKABLE QVariantMap getBaseStationProperties();
Q_INVOKABLE bool updateBasestationFirmware();
Q_INVOKABLE bool updateBasestationTime();
// athlete management
Q_INVOKABLE QVariant getAthletes();
......
This diff is collapsed.
import QtQuick 2.9
import QtMultimedia 5.8
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import QtQuick.Controls.Styles 1.4
import "../components"
ListView {
id: control
property string title: qsTr("connections")
property var parentObj
spacing: parentObj.rowSpacing
boundsBehavior: Flickable.StopAtBounds
model: speedBackend.baseStationConnections.length
delegate: ConnectionDelegate {
opacity: 1
width: parent.width
height: parentObj.delegateHeight
text: speedBackend.baseStationConnections[index]["name"]
status: {'status': speedBackend.baseStationConnections[index]["state"], 'progress': speedBackend.baseStationConnections[index]["progress"]}
}
}
This diff is collapsed.
/*
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.9
import QtMultimedia 5.8
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import QtQuick.Controls.Styles 1.4
import "../components"
Popup {
id: root
modal: true
dim: false
opacity: 0
enter: Transition {
NumberAnimation { properties: "opacity"; to: 1; duration: 300; easing.type: Easing.InOutQuad }
NumberAnimation { properties: "scale"; from: 0.9; to: 1; duration: 300; easing.type: Easing.InOutQuad }
}
exit: Transition {
NumberAnimation { properties: "opacity"; to: 0; duration: 300; easing.type: Easing.InOutQuad }
NumberAnimation { properties: "scale"; from: 1; to: 0.9; duration: 300; easing.type: Easing.InOutQuad }
}
background: Rectangle {
radius: width * 0.5
color: appTheme.style.viewColor
border.color: appTheme.style.lineColor
border.width: 0
Behavior on color {
ColorAnimation {
duration: 200
}
}
RectangularGlow {
id: headerUnderlineEffect
glowRadius: 7
spread: 0.02
color: "black"
opacity: 0.18
anchors.fill: headlineUnderline
scale: 1
}
Canvas {
id: headerBackground
anchors {
left: parent.left
right: parent.right
top: parent.top
bottom: headlineUnderline.bottom
}
height: header.height
width: header.width
property color color: appTheme.style.viewColor
Behavior on color {
ColorAnimation {
duration: 200
}
}
onColorChanged: {
requestPaint()
}
onPaint: {
var ctx = getContext("2d");
ctx.reset();
var centreX = root.width / 2;
var centreY = root.height / 2;
ctx.beginPath();
ctx.fillStyle = headerBackground.color
ctx.moveTo(centreX, centreY);
ctx.arc(centreX, centreY, root.width / 2, 1 * Math.PI, 2*Math.PI, false);
//ctx.lineTo(centreX, centreY);
ctx.fill();
}
}
Item {
id: header
anchors {
left: parent.left
right: parent.right
top: parent.top
bottom: headlineUnderline.bottom
}
Label {
id: head_text
anchors {
centerIn: parent
}
width: headlineUnderline.width * 0.4
height: parent.height
fontSizeMode: Text.Fit
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: options_stack.currentItem.title
font.pixelSize: headlineUnderline.width * 0.1
color: enabled ? appTheme.style.textColor:appTheme.style.disabledTextColor
}
}
Rectangle {
id: headlineUnderline
height: 1
width: parent.width
color: appTheme.style.lineColor
visible: false
anchors {
top: parent.top
left: parent.left
right: parent.right
topMargin: parent.height * 0.15
rightMargin: parent.radius - Math.sqrt(Math.pow(parent.radius,2)-Math.pow(parent.radius-anchors.topMargin,2))
leftMargin: parent.radius - Math.sqrt(Math.pow(parent.radius,2)-Math.pow(parent.radius-anchors.topMargin,2))
}
}
FancyButton {
id: head_back
anchors {
left: parent.left
leftMargin: parent.width * 0.17
top:parent.top
topMargin: parent.height * 0.01
}
height: parent.height * 0.13
width: height
//opacity: root.closePolicy === Popup.NoAutoClose ? 0:1
enabled: opacity > 0
glowOpacity: Math.pow( root.opacity, 100 )
image: appTheme.style.backIcon
onClicked: {
options_stack.depth > 1 ? options_stack.pop():root.close()
}
Behavior on opacity {
NumberAnimation {
duration: 200
}
}
}
}
SettingsStack {
id: options_stack
width: headlineUnderline.width
enabled: opacity !== 0 //disable when not visible
anchors {
top: parent.top
left: parent.left
leftMargin: ( parent.width - headlineUnderline.width ) / 2
topMargin: headlineUnderline.anchors.topMargin * 0.95
bottom: parent.bottom
bottomMargin: anchors.topMargin
}
Behavior on opacity {
NumberAnimation {duration: 200}
}
}
}
import QtQuick 2.9
import QtMultimedia 5.8
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import QtQuick.Controls.Styles 1.4
import "../components"
StackView {
id: control
property int delegateHeight: height * 0.25
property int rowSpacing: height * 0.01
initialItem: settings
/*-----start page of the settings-----*/
Component {
id: settings
StartPage {
id: settings_col
parentObj: control
}
}
/*-----Page to setup automatc start sequence-----*/
Component {
id: autostart
SettingsStartSequencePage {
id: autostart_col
parentObj: control
}
}
/*-----Page to connect to the base station -----*/
Component {
id: connect
SettingsBaseStationPage{
id: connectCol
parentObj: control
}
}
/*-----Page to view devices that core connected to the pase startion-----*/
Component{
id: baseStationConnections
SettingsBaseStationConnectionsPage {
parentObj: control
}
}
/*-----Custom animations-----*/
pushEnter: Transition {
NumberAnimation {
property: "opacity"
from: 0
to: 1
duration: 300
easing.type: Easing.InOutQuad
}
NumberAnimation {
property: "x"
from: width * 0.1
to: 0
duration: 300
}
}
pushExit: Transition {
NumberAnimation {
property: "opacity"
from: 1
to: 0
duration: 300
easing.type: Easing.InOutQuad
}
NumberAnimation {
property: "x"
to: -width * 0.1
from: 0
duration: 300
}
}
popExit: Transition {
NumberAnimation {
property: "opacity"
from: 1
to: 0
duration: 300
easing.type: Easing.InOutQuad
}
NumberAnimation {
property: "x"
to: width * 0.1
from: 0
duration: 300
}
}
popEnter: Transition {
NumberAnimation {
property: "opacity"
from: 0
to: 1
duration: 300
easing.type: Easing.InOutQuad
}
NumberAnimation {
property: "x"
from: -width * 0.1
to: 0
duration: 300
}
}
}
import QtQuick 2.9
import QtMultimedia 5.8
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import QtQuick.Controls.Styles 1.4
import "../components"
Column {
id: control
spacing: parentObj.rowSpacing
property string title: "autostart"
property var parentObj
function updateSetting(key, val, del){
del.busy = true
speedBackend.writeSetting(key, val)
del.busy = false
}
function loadSetting(key, del){
return speedBackend.readSetting(key)
}
SmoothSwitchDelegate {
id: ready_del
property bool busy: false
width: parent.width
height: parentObj.delegateHeight
enabled: !busy
text: qsTr("say 'ready'")
checked: parent.loadSetting("ready_en", ready_del) === "true"
onCheckedChanged: {
parent.updateSetting("ready_en", checked, ready_del)
}
}
InputDelegate {
id: ready_delay_del
property bool busy: false
width: parent.width
height: parentObj.delegateHeight
enabled: !busy && ready_del.checked
text: qsTr("delay (ms)")
inputHint: qsTr("time")
inputMethodHints: Qt.ImhFormattedNumbersOnly
inputText: control.loadSetting("ready_delay", ready_delay_del)
onInputFinished: {
control.updateSetting("ready_delay", inputText, ready_delay_del)
}
}
SmoothSwitchDelegate {
id: at_marks_del
property bool busy: false
width: parent.width
height: parentObj.delegateHeight
enabled: !busy
text: qsTr("say 'at your marks'")
checked: control.loadSetting("at_marks_en", ready_del) === "true"
onCheckedChanged: {
parent.updateSetting("at_marks_en",at_marks_del.checked, at_marks_del)
}
}
InputDelegate {
id: at_marks_delay_del
property bool busy: false
width: parent.width
height: parentObj.delegateHeight
text: qsTr("delay (ms)")
inputHint: qsTr("time")
inputMethodHints: Qt.ImhFormattedNumbersOnly
enabled: !busy && at_marks_del.checked
inputText: control.loadSetting("at_marks_delay", at_marks_delay_del)
onInputFinished: {
control.updateSetting("at_marks_delay", inputText, at_marks_delay_del)
}
}
}
import QtQuick 2.9
import QtMultimedia 5.8
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import QtQuick.Controls.Styles 1.4
import "../components"
Column {
id: control
property string title: qsTr("options")
property var parentObj
spacing: parent.rowSpacing
/*----Connect to external devices----*/
NextPageDelegate {
id: connect_del
height: parentObj.delegateHeight
text: qsTr("base station")
onClicked: {
parentObj.push(connect)
}
}
/*----Automated Start----*/
NextPageDelegate {
id: autostart_del
height: parentObj.delegateHeight
text: qsTr("start sequence")
onClicked: {
parentObj.push(autostart)
}
}
/*----Style Settings----*/
SmoothSwitchDelegate {
id: styleDel
text: qsTr("dark mode")
width: parent.width
height: parentObj.delegateHeight
checked: speedBackend.readSetting("theme") === "Dark"
onCheckedChanged: {
speedBackend.writeSetting("theme", checked ? "Dark":"Light")
appTheme.refreshTheme()
}
}
}
......@@ -5,7 +5,10 @@ import QtQuick.Controls.Styles 1.2
BusyIndicator {
id: control
property double animationSpeed: 0.5
property double animationSpeed: 1000
property double formFactor: 4.5
property color lineColor: "#21be2b"
contentItem: Item {
implicitWidth: 64
......@@ -14,54 +17,20 @@ BusyIndicator {
Item {
id: item
x: parent.width / 2 - 32
y: parent.height / 2 - 32
width: 64
height: 64
opacity: control.running ? 1 : 0
anchors.fill: parent
property int currentHeight: 0
onCurrentHeightChanged: {
}
Behavior on opacity {
OpacityAnimator {
duration: 250
}
}
SequentialAnimation {
running: control.running
loops: Animation.Infinite
running: true
NumberAnimation {
target: item
duration: 2000 * 1/control.animationSpeed
to: 1000
properties: "currentHeight"
easing.type: Easing.InOutQuad
}
NumberAnimation {
target: item
duration: 2000 * 1/control.animationSpeed
to: 0
properties: "currentHeight"
easing.type: Easing.InOutQuad
property: "currentHeight"
from: 0
to: 800
duration: control.animationSpeed
}
}
......@@ -77,7 +46,7 @@ BusyIndicator {
Rectangle {
property double heightMultiplier: Math.abs( Math.sin(( (item.currentHeight + (index*20))*0.01) * (Math.PI/2) ) )
property double heightMultiplier: Math.abs( Math.sin( ( ((item.currentHeight/100) + (index*(control.formFactor/repeater.model)))) * (Math.PI/8) ) )
anchors.verticalCenter: parent.verticalCenter
......@@ -86,7 +55,8 @@ BusyIndicator {
radius: width * 0.5
color: "#21be2b"
color: control.lineColor
}
}
}
......
......@@ -23,6 +23,7 @@ import QtGraphicalEffects 1.0
import "."
import "./components"
import "./ProfilesDialog"
import "./SettingsDialog"
//import QtQuick.Layouts 1.11
import com.itsblue.speedclimbingstopwatch 2.0
......@@ -425,7 +426,7 @@ Window {
cornerRadius: startButtBackground.radius
anchors.fill: startButtBackground
scale: 0.75
opacity: Math.pow( control.opacity, 100 )
opacity: Math.pow( startButt.opacity, 100 )
}
Rectangle {
......
<RCC>
<qresource prefix="/">
<file>main.qml</file>
<file>SettingsDialog.qml</file>
<file>components/ProgressCircle.qml</file>
<file>components/ConnectionDelegate.qml</file>
<file>components/FadeAnimation.qml</file>
......@@ -19,5 +18,11 @@
<file>ProfilesDialog/ProfileListPage.qml</file>
<file>ProfilesDialog/AddProfilePage.qml</file>
<file>ProfilesDialog/ResultListPage.qml</file>
<file>SettingsDialog/SettingsDialog.qml</file>
<file>SettingsDialog/SettingsStack.qml</file>
<file>SettingsDialog/StartPage.qml</file>
<file>SettingsDialog/SettingsStartSequencePage.qml</file>
<file>SettingsDialog/SettingsBaseStationPage.qml</file>
<file>SettingsDialog/SettingsBaseStationConnectionsPage.qml</file>
</qresource>
</RCC>
This diff is collapsed.
......@@ -25,5 +25,6 @@
<file>graphics/icons/user_black.png</file>
<file>graphics/icons/ok.png</file>
<file>sounds/IFSC_STARTSIGNAL_SINE.wav</file>
<file>ScStwBasestation.sb64</file>
</qresource>
</RCC>
......@@ -51,19 +51,28 @@ bool BaseConn::init() {
// init remote session
QJsonArray updateSubs = {"onRaceStateChanged", "onTimersChanged", "onExtensionConnectionsChanged", "onNextStartActionChanged"};
QJsonObject sessionParams = {{"updateSubs", updateSubs}, {"init", true}};
QJsonObject sessionParams = {{"updateSubs", updateSubs}, {"init", true}, {"usingTerminationKeys", true}};
if(this->sendCommand(1, sessionParams)["status"] != 200) {
QVariantMap initResponse = this->sendCommand(1, sessionParams, false);
if(initResponse["status"] != 200) {
return false;
}
this->refreshConnections();
this->firmwareVersion = initResponse["data"].toMap()["version"].toString();
this->timeOffset = initResponse["data"].toMap()["time"].toDouble() - this->date->currentMSecsSinceEpoch();
this->firmwareUpToDate = this->isFirmwareUpToDate();
emit this->propertiesChanged();
qDebug() << "[INFO][BaseStation] Init done! firmware: version: " << this->firmwareVersion << " up-to-date: " << this->firmwareUpToDate << " time offset: " << this->timeOffset;
return true;
}
void BaseConn::deInit() {
this->connections.clear();
this->setState("disconnected");
}
void BaseConn::closeConnection()
......@@ -122,7 +131,7 @@ void BaseConn::socketStateChanged(QAbstractSocket::SocketState socketState) {
switch (socketState) {
case QAbstractSocket::UnconnectedState:
{
this->setState("disconnected");
this->deInit();
break;
}
case QAbstractSocket::ConnectedState:
......@@ -141,7 +150,7 @@ void BaseConn::socketStateChanged(QAbstractSocket::SocketState socketState) {
}
}
QVariantMap BaseConn::sendCommand(int header, QJsonValue data){
QVariantMap BaseConn::sendCommand(int header, QJsonValue data, bool useTerminationKeys, int timeout) {
if(this->state != "connected"){
return {{"status", 910}, {"data", "not connected"}};
}
......@@ -170,10 +179,15 @@ QVariantMap BaseConn::sendCommand(int header, QJsonValue data){
// 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);
timer->start(timeout);
//write data
socket->write(jsonRequest.toLatin1());
if(useTerminationKeys) {
socket->write("<message>" + jsonRequest.toLatin1() + "</message>");
}
else {
socket->write(jsonRequest.toLatin1());
}
//wait for an answer to finish (programm gets stuck in here)
loop->exec();
......@@ -221,7 +235,7 @@ void BaseConn::readyRead() {
processSocketMessage(reply);
}
void BaseConn::processSocketMessage(QString message){
void BaseConn::processSocketMessage(QString message) {
QString startKey = "<message>";
QString endKey = "</message>";
......@@ -307,6 +321,55 @@ void BaseConn::socketReplyRecieved(QString reply) {
emit gotUnexpectedReply(reply);
}
// -------------------------
// --- updater functions ---
// -------------------------
bool BaseConn::updateTime() {
if(abs(this->timeOffset) < 10000000) {
// the time is already up-to-date
return true;
}
// NOT IMPLEMENTED YET
return false;
}
bool BaseConn::updateFirmware() {
QString file = ":/ScStwBasestation.sb64";
QFile f(file);
if (!f.open(QFile::ReadOnly)) return false;
QString fileContents = f.readAll();
if(this->firmwareUpToDate) {
return true;
}
QVariantMap ret = this->sendCommand(5000, fileContents, true, 15000);
return ret["status"].toInt() == 200;
}
bool BaseConn::isFirmwareUpToDate() {
QString file = ":/ScStwBasestation.sb64";
QFile f(file);
if (!f.open(QFile::ReadOnly)) return false;
QString fileContents = f.readAll();
QString newFirmwareVersion = fileContents.split("<VER>")[1].split("</VER>")[0];
int newFirmwareVersionMajor = newFirmwareVersion.split(".")[0].toInt();
int newFirmwareVersionMinor = newFirmwareVersion.split(".")[1].toInt();
int newFirmwareVersionPatch = newFirmwareVersion.split(".")[2].toInt();
QString currentFirmwareVersion = this->firmwareVersion;
int currentFirmwareVersionMajor = currentFirmwareVersion.split(".")[0].toInt();
int currentFirmwareVersionMinor = currentFirmwareVersion.split(".")[1].toInt();
int currentFirmwareVersionPatch = currentFirmwareVersion.split(".")[2].toInt();
return newFirmwareVersionMajor < currentFirmwareVersionMajor || newFirmwareVersionMinor < currentFirmwareVersionMinor || newFirmwareVersionPatch <= currentFirmwareVersionPatch;
}
// ------------------------
// --- helper functions ---
// ------------------------
......
......@@ -23,6 +23,7 @@ ClimbingRace::ClimbingRace(QObject *parent) : QObject(parent)
connect(this->baseConn, &BaseConn::stateChanged, this, &ClimbingRace::refreshMode);
connect(this->baseConn, &BaseConn::connectionsChanged, this, &ClimbingRace::baseStationConnectionsChanged);
connect(this->baseConn, &BaseConn::gotUpdate, this, &ClimbingRace::handleBaseStationUpdate);
connect(this->baseConn, &BaseConn::propertiesChanged, this, &ClimbingRace::baseStationPropertiesChanged);
this->speedTimers.append( new SpeedTimer(this) );
......@@ -620,6 +621,19 @@ QVariant ClimbingRace::getBaseStationConnections() {
return baseConn->getConnections();
}
QVariantMap ClimbingRace::getBaseStationProperties() {
QVariantMap firmware = {{"version", this->baseConn->firmwareVersion}, {"upToDate", this->baseConn->firmwareUpToDate}};
return {{"firmware", firmware}, {"timeOffset", this->baseConn->timeOffset}};
}
bool ClimbingRace::updateBasestationFirmware() {
return this->baseConn->updateFirmware();
}
bool ClimbingRace::updateBasestationTime() {
return this->baseConn->updateTime();
}
bool ClimbingRace::reloadBaseStationIpAdress() {
if(this->baseConn->state == "disconnected"){
this->baseConn->setIP(pGlobalAppSettings->loadSetting("baseStationIpAdress"));
......
......@@ -40,8 +40,8 @@ HEADERS += \
headers/apptheme.h
RESOURCES += \
shared.qrc \
qml/qml.qrc
resources/shared/shared.qrc \
resources/qml/qml.qrc
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =
......