- heavy cleanup
- migrated some stuff to ScStwLibraries
This commit is contained in:
parent
1d70b2637d
commit
759eb60e83
25 changed files with 49 additions and 984 deletions
|
@ -1,72 +0,0 @@
|
|||
/*
|
||||
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]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -18,13 +18,12 @@ DEFINES += QT_DEPRECATED_WARNINGS
|
|||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||
|
||||
SOURCES += \
|
||||
main.cpp \
|
||||
baseconn.cpp \
|
||||
sources/main.cpp \
|
||||
sources/scstwmonitorbackend.cpp
|
||||
|
||||
RESOURCES += \
|
||||
qml.qrc \
|
||||
shared.qrc
|
||||
resources/qml/qml.qrc \
|
||||
resources/shared/shared.qrc
|
||||
|
||||
|
||||
# include submodules
|
||||
|
@ -44,7 +43,6 @@ else: unix:!android: target.path = /home/pi/$${TARGET}/bin
|
|||
!isEmpty(target.path): INSTALLS += target
|
||||
|
||||
HEADERS += \
|
||||
baseconn.h \
|
||||
headers/scstwmonitorbackend.h
|
||||
|
||||
DISTFILES += \
|
||||
|
@ -67,8 +65,8 @@ ios {
|
|||
xcode_product_bundle_identifier_setting.value = "de.itsblue.ScStwMonitor"
|
||||
|
||||
OBJECTIVE_SOURCES += \
|
||||
sleepprevent.mm
|
||||
sources/sleepprevent.mm
|
||||
OBJECTIVE_HEADERS += \
|
||||
sleepprevent.h
|
||||
headers/sleepprevent.h
|
||||
}
|
||||
|
||||
|
|
|
@ -1,146 +0,0 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
Before Width: | Height: | Size: 5.7 KiB |
|
@ -1,504 +0,0 @@
|
|||
#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;
|
||||
}
|
|
@ -1,153 +0,0 @@
|
|||
#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
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,8 +0,0 @@
|
|||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>main.qml</file>
|
||||
<file>FancyBusyIndicator.qml</file>
|
||||
<file>TimerColumn.qml</file>
|
||||
<file>FadeAnimation.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
|
@ -7,6 +7,8 @@ import QtQuick.Layouts 1.0
|
|||
|
||||
import de.itsblue.ScStw 2.0
|
||||
import de.itsblue.ScStwMonitor 2.0
|
||||
import de.itsblue.ScStw.Styling 2.0
|
||||
import ScStwQmlComponents 1.0
|
||||
|
||||
Window {
|
||||
id: window
|
||||
|
@ -21,6 +23,10 @@ Window {
|
|||
id: app
|
||||
anchors.fill: parent
|
||||
|
||||
background: Rectangle {
|
||||
color: appTheme.theme.colors.background
|
||||
}
|
||||
|
||||
function landscape() {
|
||||
return app.width > app.height
|
||||
}
|
||||
|
@ -30,6 +36,14 @@ Window {
|
|||
scStwClient.ipAddress: appSettings.baseStationIp
|
||||
}
|
||||
|
||||
ScStwAppThemeManager {
|
||||
id: appTheme
|
||||
|
||||
Component.onCompleted: {
|
||||
appTheme.setTheme("Light")
|
||||
}
|
||||
}
|
||||
|
||||
Settings {
|
||||
id: appSettings
|
||||
property string baseStationIp: "192.168.4.1"
|
||||
|
@ -52,11 +66,6 @@ Window {
|
|||
}
|
||||
}
|
||||
|
||||
FontLoader {
|
||||
id: timerFont
|
||||
source:"qrc:///fonts/PTMono-Regular.ttf"
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: mainComponentLoader
|
||||
|
||||
|
@ -96,6 +105,10 @@ Window {
|
|||
TimerColumn {
|
||||
anchors.fill: parent
|
||||
|
||||
timers: backend.race.timers
|
||||
colors: appTheme.theme.colors
|
||||
fontName: appTheme.theme.fonts.timers
|
||||
|
||||
opacity: !showControls || [ScStwRace.IDLE,ScStwRace.STARTING].indexOf(backend.race.state) < 0 ? 1:0
|
||||
|
||||
Behavior on opacity {
|
||||
|
@ -136,7 +149,7 @@ Window {
|
|||
|
||||
font.pixelSize: height * 0.3
|
||||
|
||||
color: backend.race.state === ScStwRace.STARTING ? "#e0b928":"grey"
|
||||
color: backend.race.state === ScStwRace.STARTING ? appTheme.theme.colors.warning:appTheme.theme.colors.text
|
||||
|
||||
text: implicitText === "NEXT_START_ACTION" ? ["", "at your \nmarks", "ready", "starting..."][backend.race.nextStartActionDetails[ScStwRace.NextStartAction]+1]:implicitText
|
||||
|
||||
|
@ -245,15 +258,14 @@ Window {
|
|||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
Layout.preferredHeight: parent.height * 0.5
|
||||
Icon {
|
||||
Layout.preferredHeight: parent.height * 0.7
|
||||
Layout.preferredWidth: height * 0.7
|
||||
Layout.alignment: Layout.Center
|
||||
|
||||
mipmap: true
|
||||
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: "qrc:/VolumeLow.png"
|
||||
fontName: appTheme.theme.fonts.icons
|
||||
icon: appTheme.theme.icons.volumeDown
|
||||
color: appTheme.theme.colors.text
|
||||
}
|
||||
|
||||
Slider {
|
||||
|
@ -311,14 +323,14 @@ Window {
|
|||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
Layout.preferredHeight: parent.height * 0.5
|
||||
Layout.preferredWidth: height
|
||||
Icon {
|
||||
Layout.preferredHeight: parent.height * 0.7
|
||||
Layout.preferredWidth: height * 0.7
|
||||
Layout.alignment: Layout.Center
|
||||
|
||||
mipmap: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: "qrc:/VolumeHigh.png"
|
||||
fontName: appTheme.theme.fonts.icons
|
||||
icon: appTheme.theme.icons.volumeUp
|
||||
color: appTheme.theme.colors.text
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -385,7 +397,7 @@ Window {
|
|||
|
||||
anchors.fill: parent
|
||||
|
||||
FancyBusyIndicator {
|
||||
BusyIndicator {
|
||||
id: loadingInd
|
||||
anchors.centerIn: parent
|
||||
|
5
ScStwMonitorSrc/resources/qml/qml.qrc
Executable file
5
ScStwMonitorSrc/resources/qml/qml.qrc
Executable file
|
@ -0,0 +1,5 @@
|
|||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>main.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
7
ScStwMonitorSrc/resources/shared/shared.qrc
Normal file
7
ScStwMonitorSrc/resources/shared/shared.qrc
Normal file
|
@ -0,0 +1,7 @@
|
|||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>fonts/PTMono-Regular.ttf</file>
|
||||
<file>Banner.png</file>
|
||||
<file>SpeedHold.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
|
@ -1,13 +0,0 @@
|
|||
<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>
|
|
@ -1 +1 @@
|
|||
Subproject commit 81e90dddec4cca57ebae4900575f58a6610d4789
|
||||
Subproject commit 305656ab1e06679326a0fe99485dfbdbb88f34b0
|
Reference in a new issue