changed internal structure completely to be more flexible and reliable

This commit is contained in:
Dorian Zedler 2019-03-07 17:18:24 +01:00
parent 88cf8a4328
commit 9260358e11
18 changed files with 854 additions and 1274 deletions

View file

@ -20,12 +20,6 @@ class BaseConn : public QObject
{
Q_OBJECT
Q_PROPERTY(QString ipAdress WRITE setIP READ getIP)
Q_PROPERTY(QString state READ getState NOTIFY stateChanged)
Q_PROPERTY(int progress READ getProgress NOTIFY progressChanged)
Q_PROPERTY(QStringList connections READ getConnections NOTIFY connectionsChanged)
Q_PROPERTY(QString nextRemoteAction READ getNextRemoteAction NOTIFY nextRemoteActionChanged)
Q_PROPERTY(float nextRemoteActionDelayProg READ getNextRemoteActionDelayProg NOTIFY nextRemoteActionDelayProgChanged)
public:
explicit BaseConn(QObject *parent = nullptr);
@ -43,7 +37,7 @@ public:
// - 'connecting'
// - 'connected'
QStringList connections;
QVariant connections;
QString latestReadReply;
@ -57,9 +51,6 @@ private:
QTcpSocket *socket;
//socket for communication with the extention
QList<SpeedTimer*> speedTimers;
QTimer *refreshTimer;
QSemaphore remoteSessions;
int nextConnectionId;
@ -72,10 +63,6 @@ private:
QList<waitingRequest> waitingRequests;
QString nextRemoteAction;
float nextRemoteActionDelayProg;
signals:
void stateChanged();
//is emitted, when the connection state changes
@ -104,20 +91,12 @@ public slots:
void gotError(QAbstractSocket::SocketError err);
Q_INVOKABLE QVariantMap sendCommand(int header, QJsonValue data);
Q_INVOKABLE QVariantMap sendCommand(int header, QJsonValue data = "");
Q_INVOKABLE int writeRemoteSetting(QString key, QString value);
Q_INVOKABLE bool refreshConnections();
void refreshTimers();
bool startTimers();
bool stopTimers(QString type);
bool resetTimers();
// functions for the qml adapter
QString getIP() const;
void setIP(const QString &ipAdress);
@ -127,14 +106,11 @@ public slots:
int getProgress() const;
QStringList getConnections();
QString getNextRemoteAction();
float getNextRemoteActionDelayProg();
QVariant getConnections();
private slots:
void readyRead();
};
extern BaseConn * pGlobalBaseConn;
#endif // BASECONN_H

View file

@ -1,131 +0,0 @@
#ifndef BUZZERCONN_H
#define BUZZERCONN_H
#include <QObject>
#include <QObject>
#include <QDir>
#include <QUrl>
#include <QtNetwork>
#include <QAuthenticator>
#include <QDesktopServices>
#include <QDateTime>
#include <QtDebug>
#include <QtConcurrent/qtconcurrentthreadengine.h>
#include "appsettings.h"
//typedef struct strReturnData{
// int status_code;
// QString text;
//}ReturnData_t;
class BuzzerConn : public QObject
{
Q_OBJECT
Q_PROPERTY(double lastTriggered READ getLastTriggered NOTIFY triggered)
Q_PROPERTY(QString ipAdress WRITE setIP READ getIP)
Q_PROPERTY(QString state READ getState NOTIFY stateChanged)
Q_PROPERTY(int progress READ getProgress NOTIFY progressChanged)
Q_PROPERTY(double offset READ getOffset NOTIFY offsetChanged)
public:
explicit BuzzerConn(QObject *parent = nullptr, QString ip = "http://192.168.4.1", int port = 80);
// values for the time calculation
double offset;
QList<double> latest_offsets;
double latest_button_pressed;
// double starttime;
// bool connected;
// values for the socket connection
int connection_progress;
QString ip;
int port;
int errors;
int errors_until_disconnect = 4;
// the current state
QString state;
// can be:
// - 'disconnected'
// - 'connecting'
// - 'connected'
private:
QDateTime *date;
//to get the current time
QTcpSocket *socket;
//socket for communication with the extention
QStringList pending_commands;
//commands to send to the extention
//one command is being sent whenever refresh() is called
signals:
void triggered();
//is emitted when the device is triggered
void stateChanged();
//is emitted, when the connection state changes
void progressChanged();
//is emmited during the connection process when the progress changes
void offsetChanged();
public slots:
Q_INVOKABLE signed long sendCommand(QString command, int timeout);
//function to send commands to the sensor
//Can be:
//command - return
//GET_TIMESTAMP - timestamp of the sensor
//GET_LASTPRESSED - timestamp of the sensor when it was triggered the last time
//
//error codes:
//(-1) : timeout
//(-2) : invalid data was recieved
Q_INVOKABLE QList<double> gettimes(int timeout, bool bothTimes = true);
//function to get the times from the buzzer as a list
//if bothTimes is true the current and the last-pressed timestamp will be returned
//else only the current timestamp will be returned
Q_INVOKABLE bool connect();
//function to connect to buzzer
Q_INVOKABLE bool calcoffset(QList<double> times);
//function that calculates the average time offset between the buzzer and the device
Q_INVOKABLE double get(QString key);
//can return some things (offset, lastpressed, currtime, connection_progress, connected)
Q_INVOKABLE bool refresh();
//- refreshes the connection to the buzzer
//- checks if it was triggered, if so 'triggered()' is emitted
//- sends one command from the pending commans list
Q_INVOKABLE void appendCommand(QString command);
//appends a command to the pending commnds list
// functions for the qml adapter
QString getIP() const;
void setIP(const QString &ipAdress);
QString getState() const;
void setState(QString newState);
int getProgress() const;
double getOffset() const;
double getLastTriggered() const;
};
#endif // BUZZERCONN_H

90
headers/climbingrace.h Normal file
View file

@ -0,0 +1,90 @@
#ifndef CLIMBINGRACE_H
#define CLIMBINGRACE_H
#include <QObject>
#include <QSound>
#include <QSoundEffect>
#include <QMediaPlayer>
#include "headers/baseconn.h"
#include "headers/appsettings.h"
#include "headers/speedtimer.h"
class ClimbingRace : public QObject
{
Q_OBJECT
Q_PROPERTY(int state READ getState NOTIFY stateChanged)
Q_PROPERTY(QVariant timers READ getTimerTextList NOTIFY timerTextChanged)
Q_PROPERTY(QString baseStationState READ getBaseStationState NOTIFY baseStationStateChanged)
Q_PROPERTY(QVariant baseStationConnections READ getBaseStationConnections NOTIFY baseStationConnectionsChanged)
Q_PROPERTY(double nextStartActionDelayProgress READ getNextStartActionDelayProgress NOTIFY nextStartActionDelayProgressChanged)
public:
explicit ClimbingRace(QObject *parent = nullptr);
enum raceState { IDLE, STARTING, RUNNING, STOPPED };
raceState state;
enum raceMode { LOCAL, REMOTE };
raceMode mode;
private:
AppSettings * appSettings;
BaseConn * baseConn;
QMediaPlayer * player;
QTimer * baseStationSyncTimer;
QTimer * timerTextRefreshTimer;
QTimer * nextStartActionTimer;
QList<SpeedTimer *> speedTimers;
int nextStartAction;
// 0 : 'at your marks'
// 1 : 'ready'
// 2 : 'start'
double nextStartActionDelayProgress;
// helper vars
QVariantList qmlTimers;
private slots:
// helper functions
void playSoundsAndStartRace();
bool playSound(QString path);
void setState(raceState newState);
void refreshMode();
void refreshTimerText();
signals:
void nextStartActionChanged(int nextStartAction);
void nextStartActionDelayProgressChanged();
void stateChanged(int state);
void timerTextChanged();
void baseStationStateChanged();
void baseStationConnectionsChanged();
public slots:
Q_INVOKABLE int startRace();
Q_INVOKABLE int stopRace(int type);
Q_INVOKABLE int resetRace();
void syncWithBaseStation();
// functions for qml
Q_INVOKABLE int getState();
Q_INVOKABLE QVariant getTimerTextList();
Q_INVOKABLE double getNextStartActionDelayProgress();
Q_INVOKABLE void writeSetting(QString key, QVariant value);
Q_INVOKABLE QString readSetting(QString key);
Q_INVOKABLE bool connectBaseStation();
Q_INVOKABLE QString getBaseStationState();
Q_INVOKABLE QVariant getBaseStationConnections();
};
#endif // CLIMBINGRACE_H

View file

@ -13,7 +13,7 @@ class SpeedTimer : public QObject
public:
explicit SpeedTimer(QObject *parent = nullptr);
enum timerState { IDLE, STARTING, RUNNING, STOPPED };
enum timerState { IDLE, STARTING, RUNNING, STOPPED, FAILED, CANCELLED };
timerState state;
// variables for capturing the time
@ -22,20 +22,19 @@ public:
double stoppedTime;
double reactionTime;
bool remoteControlled;
signals:
void stateChanged(timerState newState);
void startCanceled(bool falseStart);
public slots:
void start();
void stop(QString type);
void reset();
bool start(bool force = false);
bool stop(int type, bool force = false);
bool reset(bool force = false);
void setState(timerState newState);
QString getState();
double getCurrTime();
QString getText();
//void handleStartpadTrigger();
//void handleToppadTrigger();

View file

@ -1,52 +0,0 @@
#ifndef SPEEDTIMERQMLADAPTER_H
#define SPEEDTIMERQMLADAPTER_H
#include <QObject>
#include <QTimer>
#include "speedtimer.h"
class SpeedTimerQmlAdapter : public QObject
{
Q_OBJECT
Q_PROPERTY(QString state READ getState NOTIFY stateChanged)
//Q_PROPERTY(int currtime READ getCurrTime)
Q_PROPERTY(QString text READ getText NOTIFY textChanged)
public:
explicit SpeedTimerQmlAdapter(QObject *parent = nullptr);
SpeedTimer::timerState state;
// variables for capturing the time
double startTime;
double stopTime;
double stoppedTime;
double reactionTime;
QString text;
signals:
void stateChanged(SpeedTimer::timerState newState);
Q_SIGNAL void startCanceled(bool falseStart);
void textChanged();
public slots:
Q_INVOKABLE bool setStarting();
Q_INVOKABLE bool start();
Q_INVOKABLE bool stop(QString type);
Q_INVOKABLE bool reset();
void setState(SpeedTimer::timerState newState);
Q_INVOKABLE QString getState();
Q_INVOKABLE QString getText();
// double getCurrTime();
private:
QTimer * refreshTimer;
private slots:
void refreshValues();
};
#endif // SPEEDTIMERQMLADAPTER_H

View file

@ -124,7 +124,7 @@ Popup {
}
onClicked: {
options_stack.depth > 1 ? options_stack.pop():root.close()
options_stack.depth > 1 ? options_stack.pop():root.close()
}
Behavior on opacity {
@ -169,7 +169,7 @@ Popup {
/*----Connect to external devices----*/
NextPageDelegate {
id: connect_del
text: qsTr("extentions")
text: qsTr("Base Station")
onClicked: {
options_stack.push(connect)
}
@ -226,39 +226,29 @@ Popup {
id: connect
Column {
id: connect_col
property string title: qsTr("extentions")
property string title: qsTr("Base Station")
property int delegateHeight: height*0.18
ConnectionDelegate {
id: connect_base_del
text: "connect"
status: { "status": speedBackend.baseStationState }
connect: speedBackend.connectBaseStation
type: "baseStation"
width: parent.width
font.pixelSize: options_stack.text_pixelSize
}
NextPageDelegate {
id: baseConn_del
text: qsTr("Base Station")
id: baseStationConnections_del
text: qsTr("connected extentions")
onClicked: {
options_stack.push(baseStation)
options_stack.push(baseStationConnections)
}
}
ConnectionDelegate {
id: connect_buzz_del
status: root.connections.buzzer
connect: root.connect
type: "buzzer"
width: parent.width
font.pixelSize: options_stack.text_pixelSize
}
ConnectionDelegate {
id: connect_stap_del
status: root.connections.startpad
connect: root.connect
type: "startpad"
width: parent.width
font.pixelSize: options_stack.text_pixelSize
}
}
}
@ -273,12 +263,7 @@ Popup {
function updateSetting(key, val, del){
del.enabled = false
if(baseConn.state === "connected"){
baseConn.writeRemoteSetting(key, val)
}
else {
_cppAppSettings.writeSetting(key, val)
}
speedBackend.writeSetting(key, val)
del.enabled = true
}
@ -286,12 +271,8 @@ Popup {
function loadSetting(key, del){
del.enabled = false
var val
if(baseConn.state === "connected"){
val = baseConn.sendCommand(3001, key)["data"]
}
else {
val = _cppAppSettings.loadSetting(key)
}
val = speedBackend.readSetting(key)
del.enabled = true
return val
@ -407,70 +388,24 @@ Popup {
}
}
/*-----Page to connect to and manage the Base Station----*/
Component {
id: baseStation
Column {
id: baseStation_col
property string title: qsTr("Base Station")
property int delegateHeight: height*0.18
ConnectionDelegate {
id: connect_base_del
text: "connect"
status: root.connections.baseStation
connect: root.connect
type: "baseStation"
width: parent.width
font.pixelSize: options_stack.text_pixelSize
}
NextPageDelegate {
id: baseStationConnections_del
text: qsTr("connected extentions")
onClicked: {
baseConn.refreshConnections()
options_stack.push(baseStationConnections)
}
}
}
}
/*-----Page to view devices that core connected to the pase startion-----*/
Component{
id: baseStationConnections
ListView {
function getModel(){
var keys = Object.keys(baseConn.connections);
ListView {
id: baseStationConnections_list
property string title: qsTr("connections")
property int delegateHeight: height*0.18
model: speedBackend.baseStationConnections.length
delegate: ConnectionDelegate {
enabled: false
font.pixelSize: options_stack.text_pixelSize
width: parent.width
height: baseStationConnections_list.delegateHeight
var len = keys.length
return(len)
}
function getDetails(index){
var ret = baseConn.connections[index]
var details = ret.split("|")
return(details)
}
id: baseStationConnections_list
property string title: qsTr("connections")
property int delegateHeight: height*0.18
model: getModel()
delegate: ConnectionDelegate {
enabled: false
font.pixelSize: options_stack.text_pixelSize
width: parent.width
height: baseStationConnections_list.delegateHeight
text: baseStationConnections_list.getDetails(index)[2]
status: {'status': baseStationConnections_list.getDetails(index)[4], 'progress': 0}
}
text: speedBackend.baseStationConnections[index]["name"]
status: {'status': speedBackend.baseStationConnections[index]["state"], 'progress': speedBackend.baseStationConnections[index]["progress"]}
}
}
}
/*-----Custom animations-----*/

View file

@ -48,7 +48,7 @@ Item {
}
}
/*
SpeedTimerBackend {
id: timerBackend
@ -60,7 +60,7 @@ Item {
console.log("start cnaceled")
control.startCanceled(falseStart)
}
}
}*/
function getState(){
return(timerBackend.getState())

View file

@ -12,7 +12,7 @@ ItemDelegate {
enabled: status.status === "disconnected"
onClicked: {
connect(type)
connect()
if(status.status !== "connected"){
statusIndicator.color_override = "red"
shortDelay.start()

View file

@ -24,7 +24,7 @@ import "./components"
import "./styles"
//import QtQuick.Layouts 1.11
import com.itsblue.speedclimbingstopwatch 1.0
import com.itsblue.speedclimbingstopwatch 2.0
Window {
visible: true
@ -46,20 +46,6 @@ Window {
property double stoppedTime: 0
property double currTime
property double buzzer_offset: buzzerConn.offset
property double last_button_pressed: buzzerConn.lastTriggered
property var last_run : {
'stop_type': "", 'time': 0, 'react_time': 0
}
//array that contains all connections an their atatus
property var connections: {
'buzzer': buzzerConn.status,
'startpad': startpadConn.status,
'baseStation': baseConn.status
}
//set default state to IDLE
state: "IDLE"
@ -69,174 +55,28 @@ Window {
color: StyleSettings.backgroundColor
}
Item {
id: connections
SpeedBackend {
id: speedBackend
BuzzerConn {
id: buzzerConn
ipAdress: "192.168.4.10"
property var status: {'status': buzzerConn.state, 'progress': buzzerConn.progress}
onLastTriggeredChanged: {
timer_1.handleToppad()
}
}
Timer {
id: buzzerRefreshTimer
running: buzzerConn.state === "connected"
interval: root.state === "RUNNING" ? 1:1000
repeat: false
onTriggered: {
buzzerConn.refresh()
this.start()
}
}
StartpadConn {
id: startpadConn
ipAdress: "192.168.4.11"
property var status: {'status': startpadConn.state, 'progress': startpadConn.progress}
property string color: root.state === "RUNNING" ? "SET_LED_RUNNING":"SET_LED_STARTING"
onColorChanged: {
appendCommand(color)
}
onLastTriggeredChanged: {
timer_1.handleStartpad()
}
}
Timer {
id: startpadRefreshTimer
running: startpadConn.state === "connected"
interval: root.state === "RUNNING" || root.state === "STARTING" ? 1:1000
repeat: false
onTriggered: {
startpadConn.refresh()
this.start()
}
}
BaseStationConn {
id: baseConn
ipAdress: "192.168.4.1"
property var status: {'status': baseConn.state, 'progress': baseConn.progress, 'connections': baseConn.connections}
onNextRemoteActionChanged: {
switch(nextRemoteAction){
case "0":
timer_1.text = "at your\nmarks"
break
case "1":
timer_1.text = "ready"
break
case "2":
timer_1.text = "0.000 sec"
break
}
}
onNextRemoteActionDelayProgChanged: {
prog.progress = baseConn.nextRemoteActionDelayProg * 100
onStateChanged: {
var stateString
switch (state){
case 0:
stateString = "IDLE"
break;
case 1:
stateString = "STARTING"
break;
case 2:
stateString = "RUNNING"
break;
case 3:
stateString = "STOPPED"
}
root.state = stateString
}
}
Timer {
id: next_actionTimer
property string action
property double started_at
running: false
repeat: false
onRunningChanged: {
if(!running){
started_at = 0
if(action == "NONE"){
timer_1.text = "0.000 sec"
}
return
}
if(action === "at_marks"){
started_at = new Date().getTime()
timer_1.text = "at your\nmarks"
}
else if(action === "ready"){
started_at = new Date().getTime()
timer_1.text = "ready"
}
}
onTriggered: {
if(action === "at_marks"){
at_marksSound.play()
}
else if(action === "ready"){
action = "NONE"
readySound.play()
}
}
}
Item {
id: sounds
SoundEffect {
id: at_marksSound
source: "qrc:/sounds/at_marks_1.wav"
onPlayingChanged: {
if(!playing && root.state==="STARTING"){
if(_cppAppSettings.loadSetting("ready_en") === "true"){
next_actionTimer.action = "ready"
next_actionTimer.interval = _cppAppSettings.loadSetting("ready_delay")>0 ? _cppAppSettings.loadSetting("ready_delay"):1
next_actionTimer.start()
}
else{
startSound.play()
}
}
}
}
SoundEffect {
id: readySound
source: "qrc:/sounds/ready_1.wav"
onPlayingChanged: {
if(!playing && root.state==="STARTING"){
startSound.play()
}
}
}
SoundEffect {
//start sound
id: startSound
source: "qrc:/sounds/OFFICAL_IFSC_STARTIGNAL.wav"
onPlayingChanged: {
if(!playing && root.state==="STARTING"){
root.state = "RUNNING"
}
else if(playing) {
console.log("start sound started")
timer_1.start(3100)
}
}
}
SoundEffect {
//false-start sound
id: falseSound
source: "qrc:/sounds/false.wav"
}
}
/*------------------------
Timer text an upper line
------------------------*/
@ -256,84 +96,36 @@ Window {
color: StyleSettings.menuColor
}
SpeedTimer {
Label {
id: timer_1
anchors.centerIn: parent
elide: "ElideRight"
color: StyleSettings.textColor
toppadConn: buzzerConn
baseConn: baseConn
startpadConn: startpadConn
text: "0.000 sec"
font.pixelSize: 100
onStateChanged: {
root.state = timer_1.getState()
}
text: speedBackend.timers[0]["text"]
onStopped: {
root.state = "STOPPED"
}
onStartCanceled: {
root.state = "STOPPED"
next_actionTimer.stop()
at_marksSound.stop()
readySound.stop()
startSound.stop()
if(falseStart && baseConn.state !== "connected"){
falseSound.play()
Behavior on text {
enabled: root.state !== "RUNNING"
FadeAnimation {
target: timer_1
}
}
}
Label {
id: react_time
property int rtime: root.last_run.react_time
property int rtime: speedBackend.timers[0]["reacttime"]
text: qsTr("reaction time (ms): ") + Math.round(rtime)
opacity: (root.state === "RUNNING" || root.sate === "STARTING" || root.state === "STOPPED") && rtime !== 0 ? 1:0
opacity: (root.state === "RUNNING" || root.state === "STOPPED") && rtime !== 0 ? 1:0
color: StyleSettings.textColor
anchors {
horizontalCenter: parent.horizontalCenter
top: timer_1.bottom
topMargin: parent.height * 0.1
}
Timer {
running: root.state === "RUNNING" || root.sate === "STARTING" || root.state === "STOPPED"
repeat: true
interval: 1
onTriggered: {
react_time.rtime = root.last_run.react_time
}
}
}
}
ConnectionIcon {
id: buzzerLogo
source: "qrc:/graphics/icons/buzzer_black.png"
status: root.connections["buzzer"].status
anchors {
top: parent.top
topMargin: 10
left: parent.left
leftMargin: 10
}
height: root.landscape()? root.height*0.1:root.width*0.1
}
ConnectionIcon {
id: startpadLogo
source: "qrc:/graphics/icons/startpad_black.png"
status: root.connections["startpad"].status
anchors {
top: parent.top
topMargin: 10
left: parent.left
leftMargin: 5 + buzzerLogo.width
}
height: root.landscape()? root.height*0.1:root.width*0.1
}
Rectangle {
@ -395,7 +187,7 @@ Window {
root.start()
break
case "RUNNING":
root.stop("manual")
root.stop(0)
break
case "STOPPED":
root.reset()
@ -407,31 +199,22 @@ Window {
ProgressCircle {
id: prog
anchors.fill: startButt
opacity: baseConn.state !== "connected" ?
next_actionTimer.started_at > 0 ? 1:0
:progress > 0 ? 1:0
opacity: root.state === "STARTING" || root.state === "IDLE" ? 1:0
lineWidth: 5
property int progress: 0
arcBegin: 0
arcEnd: baseConn.state !== "connected" ? 360 * (( next_actionTimer.interval - ( new Date().getTime() - next_actionTimer.started_at ) ) / next_actionTimer.interval)
:(360/100) * progress
arcEnd: 360 * speedBackend.nextStartActionDelayProgress
colorCircle: "grey"
onProgressChanged: {
//console.log(progress)
}
animationDuration: baseConn.state === "connected" ? 150:0
Timer {
id: prog_refresh
running: parent.opacity === 1 && baseConn.state !== "connected"
interval: 1
repeat: false
onTriggered: {
prog.arcEnd = 360 * (( next_actionTimer.interval - ( new Date().getTime() - next_actionTimer.started_at ) ) / next_actionTimer.interval)
prog_refresh.start()
Behavior on opacity {
NumberAnimation {
duration: 200
}
}
animationDuration: 0
}
/*----------------------
@ -455,7 +238,7 @@ Window {
enabled: root.state === "STARTING"
onClicked: {
root.stop("cancel")
root.stop(1)
}
Behavior on scale {
@ -634,7 +417,7 @@ Window {
State {
name: "IDLE"
//state for the start page
PropertyChanges { target: timer_1; pixelSize: root.landscape() ? parent.width * 0.1:parent.height * 0.3; scale: 1 }
PropertyChanges { target: timer_1; font.pixelSize: root.landscape() ? parent.width * 0.1:parent.height * 0.3; scale: 1 }
PropertyChanges {
target: time_container;
anchors.bottomMargin: root.landscape() ? undefined:parent.height * 0.1;
@ -656,14 +439,14 @@ Window {
anchors.rightMargin: root.landscape() ? parent.width * 0.05:parent.width * 0.5 - startButt.width * 0.5 //put the button more to the right to hide the menu (only in landscape mode)
anchors.bottomMargin: root.landscape() ? parent.height * 0.5 - startButt.height * 0.5:parent.height * 0.1 //put the button lower to hide the menu (only in portrait mode)
}
PropertyChanges { target: timer_1; pixelSize: root.landscape() ? parent.width * 0.2:parent.height * 0.3; scale: 1 }
PropertyChanges { target: timer_1; font.pixelSize: root.landscape() ? parent.width * 0.2:parent.height * 0.3; scale: 1 }
PropertyChanges { target: cancelButt; scale: 1}
PropertyChanges { target: menu_container; }
},
State {
name: "RUNNING"
//state when the timer is running
PropertyChanges { target: timer_1; pixelSize: root.landscape() ? parent.width * 0.2:parent.height * 0.3; scale: 1 }
PropertyChanges { target: timer_1; font.pixelSize: root.landscape() ? parent.width * 0.2:parent.height * 0.3; scale: 1 }
PropertyChanges { target: startButt; enabled: true;
text: "stop"
anchors.rightMargin: root.landscape() ? parent.width * 0.05:parent.width * 0.5 - startButt.width * 0.5 //put the button more to the right to hide the menu (only in landscape mode)
@ -677,7 +460,7 @@ Window {
//state when the meassuring is over
PropertyChanges {
target: timer_1;
pixelSize: root.landscape() ? parent.width * 0.15:parent.height * 0.1;
font.pixelSize: root.landscape() ? parent.width * 0.15:parent.height * 0.1;
scale: 1
}
PropertyChanges {
@ -726,7 +509,12 @@ Window {
/*----Functions to control the stopwatch----*/
function start(){
if(baseConn.state === "connected"){
var ret = speedBackend.startRace()
if(ret !== 200){
console.log("+ --- error starting race: "+ret)
}
/*if(baseConn.state === "connected"){
baseConn.startTimers()
return;
}
@ -745,10 +533,18 @@ Window {
return
}
startSound.play()
startSound.play()*/
}
function stop(type){
var ret = speedBackend.stopRace(type)
if(ret !== 200){
console.log("+ --- error stopping race: "+ret)
}
/*
if(baseConn.state === "connected"){
baseConn.stopTimers(type)
}
@ -761,9 +557,18 @@ Window {
}
//root.state = "STOPPED"
//timer_1.stop(type)
*/
}
function reset(){
var ret = speedBackend.resetRace()
if(ret !== 200){
console.log("+ --- error resetting race: "+ret)
}
/*
if(baseConn.state === "connected"){
baseConn.resetTimers()
}
@ -773,6 +578,7 @@ Window {
//
//root.state = "IDLE"
*/
}
}
}

View file

@ -113,10 +113,10 @@ Item {
case "Default":
theme = Dark
break
case "Dark":
case "Dark":
theme = Light
break
case "Light":
case "Light":
theme = Default
break
}
@ -142,13 +142,13 @@ Item {
_cppAppSettings.writeSetting("theme", "Dark")
theme = Dark
break
case "Dark":
_cppAppSettings.writeSetting("theme", "Light")
theme = Light
case "Dark":
_cppAppSettings.writeSetting("theme", "Light")
theme = Light
break
case "Light":
_cppAppSettings.writeSetting("theme", "Default")
theme = Default
case "Light":
_cppAppSettings.writeSetting("theme", "Default")
theme = Default
break
}
}

View file

@ -32,6 +32,8 @@ AppSettings::AppSettings(QObject* parent)
this->setDefaultSetting("at_marks_delay", 0);
this->setDefaultSetting("theme", "Default");
pGlobalAppSettings = this;
}
QString AppSettings::loadSetting(const QString &key)

View file

@ -1,7 +1,10 @@
#include "headers/baseconn.h"
BaseConn * pGlobalBaseConn = nullptr;
BaseConn::BaseConn(QObject *parent) : QObject(parent)
{
pGlobalBaseConn = this;
socket = new QTcpSocket();
this->setState("disconnected");
@ -9,14 +12,6 @@ BaseConn::BaseConn(QObject *parent) : QObject(parent)
this, SLOT(gotError(QAbstractSocket::SocketError)));
this->nextConnectionId = 1;
this->speedTimers.append(pGlobalSpeedTimer);
this->refreshTimer = new QTimer();
refreshTimer->setInterval(1);
refreshTimer->setSingleShot(true);
refreshTimer->connect(this->refreshTimer, &QTimer::timeout, this, &BaseConn::refreshTimers);
refreshTimer->start();
}
bool BaseConn::connectToHost() {
@ -53,7 +48,6 @@ bool BaseConn::connectToHost() {
connect(this->socket, &QTcpSocket::readyRead, this, &BaseConn::readyRead);
this->connection_progress = 100;
setState("connected");
this->speedTimers[0]->remoteControlled = true;
return(true);
}
@ -106,7 +100,7 @@ void BaseConn::gotError(QAbstractSocket::SocketError err)
qDebug() << "got socket error: " << strError;
}
QVariantMap BaseConn::sendCommand(int header, QJsonValue data = ""){
QVariantMap BaseConn::sendCommand(int header, QJsonValue data){
if(this->state != "connected"){
return {{"status", 910}, {"data", "not connected"}};
}
@ -230,162 +224,6 @@ int BaseConn::writeRemoteSetting(QString key, QString value) {
return this->sendCommand(3000, requestData)["status"].toInt();
}
void BaseConn::refreshTimers(){
if(this->state != "connected"){
this->refreshTimer->start();
return;
}
QVariantMap tmpReply = this->sendCommand(2000);
if(tmpReply["status"] != 200){
return;
}
int remoteRaceState = tmpReply["data"].toInt();
switch (remoteRaceState) {
case 0:
{
// case IDLE
if(speedTimers[0]->state != 0){
speedTimers[0]->setState(SpeedTimer::IDLE);
}
break;
}
case 1:
{
// case STARTING
if(speedTimers[0]->state != 1){
speedTimers[0]->setState(SpeedTimer::STARTING);
}
tmpReply = sendCommand(2004);
if(tmpReply["status"] != 200){
//handle Error!!
qDebug() << "+--- getting next start action from basestation failed: " << tmpReply["status"];
this->refreshTimer->start();
return;
}
else {
if(this->nextRemoteAction != tmpReply["data"].toString()){
this->nextRemoteAction = tmpReply["data"].toString();
this->nextRemoteActionChanged();
}
}
tmpReply = sendCommand(2005);
if(tmpReply["status"] != 200){
//handle error!!
qDebug() << "+--- getting next start action progress from basestation failed";
this->refreshTimer->start();
return;
}
else {
if(this->nextRemoteActionDelayProg != tmpReply["data"].toFloat()){
this->nextRemoteActionDelayProg = tmpReply["data"].toFloat();
this->nextRemoteActionDelayProgChanged();
}
}
break;
}
case 2:
{
// case RUNNING
if(speedTimers[0]->state != 2){
speedTimers[0]->setState(SpeedTimer::RUNNING);
}
// get current time
tmpReply = sendCommand(2001, 0);
if(tmpReply["status"] != 200){
//handle error!!
qDebug() << "+--- getting current time (timer 0) from basestation failed";
this->refreshTimer->start();
return;
}
else {
speedTimers[0]->stoppedTime = tmpReply["data"].toInt();
}
break;
}
case 3:
{
// case STOPPED
if(speedTimers[0]->state != 3){
speedTimers[0]->setState(SpeedTimer::STOPPED);
}
// get current time
tmpReply = sendCommand(2001, 0);
if(tmpReply["status"] != 200){
//handle error!!
qDebug() << "+--- getting current time (timer 0) from basestation failed";
return;
}
else {
speedTimers[0]->stoppedTime = tmpReply["data"].toInt();
}
break;
}
default:
{
// some error
break;
}
}
this->refreshTimer->start();
}
bool BaseConn::startTimers(){
qDebug() << "starting timers";
QVariantMap reply = this->sendCommand(1000);
if(reply["status"] != 200){
//handle Error!!
return false;
}
this->speedTimers[0]->setState(SpeedTimer::STARTING);
return true;
}
bool BaseConn::stopTimers(QString type){
qDebug() << "stopping timers";
QVariantMap reply = this->sendCommand(1001);
if(reply["status"] != 200){
//handle Error!!
return false;
}
this->speedTimers[0]->stop(type);
qDebug() << "stopped timers";
return true;
}
bool BaseConn::resetTimers(){
qDebug() << "resetting timers";
QVariantMap reply = this->sendCommand(1002);
if(reply["status"] != 200){
//handle Error!!
return false;
}
this->speedTimers[0]->reset();
return true;
}
void BaseConn::setIP(const QString &ipAdress){
this->ip = ipAdress;
}
@ -415,23 +253,18 @@ bool BaseConn::refreshConnections() {
if(reply["status"] != 200){
//handle Error!!
qDebug() << "+--- error refreshing connections: " << reply["status"];
qDebug() << "+ --- error refreshing connections: " << reply["status"];
return false;
}
connections = reply["data"].toString().split("|||");
QVariantList tmpConnections = reply["data"].toList();
this->connections = reply["data"].toList();
return true;
}
QStringList BaseConn::getConnections() {
QVariant BaseConn::getConnections() {
return(connections);
}
QString BaseConn::getNextRemoteAction() {
return this->nextRemoteAction;
}
float BaseConn::getNextRemoteActionDelayProg(){
return this->nextRemoteActionDelayProg;
}

View file

@ -1,360 +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/>.
*/
#include "headers/buzzerconn.h"
BuzzerConn::BuzzerConn(QObject *parent, QString ip, int port) : QObject(parent)
{
// initialize TcpSocket for communication with the extentions
this->socket = new QTcpSocket();
// initialize Qdate
this->date = new QDateTime;
this->latest_button_pressed = 0;
// set ip and port
this->ip = ip;
this->port = port;
//standard state: disconnected
this->setState("disconnected");
}
bool BuzzerConn::connect()
{
// function to connect to buzzer
qDebug() << "connecting...";
setState("connecting");
//setup loop to wait until the connection has finished
QEventLoop loop;
QTimer timer;
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->socket, SIGNAL(connected()), &loop, SLOT(quit()));
// start the timer before starting to connect
timer.start(3000);
//connect
this->socket->connectToHost(this->ip, this->port);
//wait for the connection to finish (programm gets stuck in here)
loop.exec();
//loop finished
if(timer.remainingTime() == -1){
//the time has been triggered -> timeout
setState("disconnected");
return(false);
}
// stop the timer as the connection has been established
timer.stop();
// get the timestamps ( last_pressed and current_timestamp ) from the extention
QList<double> times = gettimes(2000, true);
if(times[0] == 200.0){
//if the request was successfull, set last_triggered and
this->latest_button_pressed = times[2];
for(int i=0;i<=100;i++){
// middle the offset 100 times
this->connection_progress = i;
emit this->progressChanged();
if(!calcoffset(this->gettimes(1000, false))){
// if a request fails, cancle the connection process
this->connection_progress = 100;
setState("disconnected");
return(false);
}
}
// after middeling the offset for 100 times set the state to connected and quit
setState("connected");
return(true);
}
else{
//if not, cancel
setState("disconnected");
return(false);
}
}
bool BuzzerConn::calcoffset(QList<double> times)
{
//function that recieves the current time of the extention,
//puts it into the latest offsets list and
//calculates the avarage offset
//if there are not enoug items in the list (0=status of the request (200 = success ...); 1 = timestamp that has to be added to the list)
if(times.length() != 2){
return(false);
}
if(times[0] == 200.0){
// if the given request was a successfull request, calculate the offset
double offset = date->currentMSecsSinceEpoch() - times[1];
//check if the list contains more than or 100 values
if(this->latest_offsets.length()>=100){
//if so, delete the first (oldest) one
this->latest_offsets.removeFirst();
}
//append the new offset to the list
this->latest_offsets.append(offset);
//variable to store the avarage
double mem = 0;
for(int i=0;i<latest_offsets.length();i++){
//go through the list and add all offsets
mem += latest_offsets[i];
}
//calculate the avarage
this->offset = mem / double(latest_offsets.length());
//emit that the offset has changed
emit offsetChanged();
//qDebug("%20f", this->offset);
return(true);
}
else {
//if the given request was not valid, return false
return(false);
}
}
QList<double> BuzzerConn::gettimes(int timeout, bool bothTimes)
{
// function to recieve the timestamps (last_triggered, current_timestamp) from the extentions
//list to store the return code of the request and the timestamps
QList<double> times;
//variable to store answer of the extention
signed long ret;
//send request to extention
ret = this->sendCommand("GET_TIMESTAMP", timeout);
if(ret >= 0){
// if it is higer than 0 (=success) append the return code
times.append(double(200));
//and the timestamp
times.append(double(ret));
if(bothTimes){
//if both timstamps were requested do the same thing again for the other one (last_triggered)
ret = this->sendCommand("GET_LASTPRESSED", timeout);
if(ret >= 0){
//if the reuest was successfull, append the value to the list
times.append(double(ret));
}
else {
// if not, change the return code
times[0] = ret;
}
}
}
else {
// if not, append the return code
times.append(ret);
}
//return the list
return(times);
}
double BuzzerConn::get(QString key)
{
// function to get all kinds of data
if(key == "offset"){
// get the offset of the extention
return(this->offset);
}
else if (key == "lastpressed") {
// get the last_triggered timestamp of the extention
return(this->latest_button_pressed);
}
else if( key == "currtime") {
// get the current time
return(this->date->currentMSecsSinceEpoch());
}
else if( key == "connection_progress") {
//get the connection progress
return(this->connection_progress);
}
else if( key == "connected") {
// get the state of the connection
if(this->state == "connected"){
return(1);
}
return(0);
}
return(0);
}
bool BuzzerConn::refresh()
{
// function that has to be called frequently to refresh the connection
// check if the extention has been triggered
// sends one command the pending command list
// and calculates the offset once
if(this->state != "connected"){
// if the extention is not connected return
return(false);
}
//send one of the pending commands
if(pending_commands.length() > 0){
//get the irst command in the list
QString command = this->pending_commands.first();
//send the command
signed long retval = this->sendCommand(command, 2000);
if(retval > 0){
//if the request has been successfull, remove the command from the list
this->pending_commands.removeFirst();
}
}
// get the timestamps from the extention
QList<double> ret = this->gettimes(2000);
if(ret[0] >= 0){
//if the request has been successfull check if the last_triggered timestamp has changed
if(ret[2] > this->latest_button_pressed){
// if it has, set it
this->latest_button_pressed = ret[2];
//and emit the trggered signal
emit triggered();
}
// as the requst as been sucessfull, reset the error counter
this->errors = 0;
// calculate the offset on time and return
return(this->calcoffset(ret));
}
else {
// if not add one to the error conter
this->errors ++;
if(this->errors > errors_until_disconnect){
// if the error counter is too high, disconnect
this->socket->disconnectFromHost();
this->setState("disconnected");
}
// return false
return(false);
}
}
signed long BuzzerConn::sendCommand(QString command, int timeout){
//function to send a commnd to the extention
//if there is any data in the storage, clear it
if(this->socket->bytesAvailable() > 0){
this->socket->readAll();
}
//send request to the socket server
QByteArray arrBlock;
QDataStream out(&arrBlock, QIODevice::WriteOnly);
out << quint16(0) << command;
out.device()->seek(0);
out << quint16(arrBlock.size() - sizeof(quint16));
this->socket->write(arrBlock);
//now wait for the extention to answer
QEventLoop loop;
QTimer timer;
timer.setSingleShot(true);
//quit the loop if the timer times out
loop.connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
//or if the request is anwered
loop.connect(socket, SIGNAL(readyRead()), &loop, SLOT(quit()));
//start the timer
timer.start(timeout);
//start the loop
loop.exec();
//loop finished
if(timer.remainingTime() == -1){
//the time has been triggered -> timeout
return(-1);
}
// stop the timer
timer.stop();
//if the data is not 4 bytes long it is invalid -> clear and terminate
if(this->socket->bytesAvailable() != 4){
this->socket->readAll();
return(-2);
}
long data = 0;
// read four bytes and cast them as a double
this->socket->read((char*)&data,4);
//return the data
return data;
}
void BuzzerConn::appendCommand(QString command){
this->pending_commands.append(command);
}
void BuzzerConn::setIP(const QString &ipAdress){
this->ip = ipAdress;
}
QString BuzzerConn::getIP() const
{
return(this->ip);
}
QString BuzzerConn::getState() const
{
return(this->state);
}
void BuzzerConn::setState(QString newState){
this->state = newState;
emit stateChanged();
}
int BuzzerConn::getProgress() const
{
return(connection_progress);
}
double BuzzerConn::getOffset() const
{
return(this->offset);
}
double BuzzerConn::getLastTriggered() const
{
return(this->latest_button_pressed);
}

549
sources/climbingrace.cpp Normal file
View file

@ -0,0 +1,549 @@
#include "headers/climbingrace.h"
/*
* manages:
* - global state
* - timers
* - sounds
* - next start action
* - next start action delay progress
*
*/
ClimbingRace::ClimbingRace(QObject *parent) : QObject(parent)
{
this->state = IDLE;
this->mode = LOCAL;
this->appSettings = new AppSettings;
this->baseConn = new BaseConn;
this->baseConn->setIP("192.168.4.1");
connect(this->baseConn, &BaseConn::stateChanged, this, &ClimbingRace::baseStationStateChanged);
this->speedTimers.append( new SpeedTimer );
this->player = new QMediaPlayer;
this->baseStationSyncTimer = new QTimer();
this->baseStationSyncTimer->setInterval(10);
this->baseStationSyncTimer->setSingleShot(true);
this->baseStationSyncTimer->connect(this->baseStationSyncTimer, &QTimer::timeout, this, &ClimbingRace::syncWithBaseStation);
this->baseStationSyncTimer->start();
this->timerTextRefreshTimer = new QTimer();
this->timerTextRefreshTimer->setInterval(1);
this->timerTextRefreshTimer->setSingleShot(true);
this->timerTextRefreshTimer->connect(this->timerTextRefreshTimer, &QTimer::timeout, this, &ClimbingRace::refreshTimerText);
this->refreshTimerText();
this->nextStartActionTimer = new QTimer(this);
nextStartActionTimer->setSingleShot(true);
}
// --------------------------
// --- Main Functionality ---
// --------------------------
int ClimbingRace::startRace() {
if(this->state != IDLE) {
return 904;
}
this->refreshMode();
qDebug() << "+ --- starting race";
int returnCode = 900;
switch (this->mode) {
case LOCAL:
{
this->setState(STARTING);
this->nextStartAction = -1;
this->playSoundsAndStartRace();
returnCode = 200;
break;
}
case REMOTE:
{
QVariantMap reply = this->baseConn->sendCommand(1000);
if(reply["status"] != 200){
//handle Error!!
returnCode = reply["status"].toInt();
}
else {
returnCode = 200;
}
break;
}
}
return returnCode;
}
int ClimbingRace::stopRace(int type) {
if(this->state != RUNNING && this->state != STARTING) {
return 904;
}
// type can be:
// 0: stopp
// 1: cancel
// 2: fail (fase start)
this->refreshMode();
qDebug() << "+ --- stopping race";
int returnCode = 900;
switch (this->mode) {
case LOCAL:
{
if(type == 1){
this->nextStartActionTimer->stop();
this->player->stop();
this->nextStartAction = -1;
}
returnCode = this->speedTimers[0]->stop(type) ? 200:904;
if(returnCode == 200) {
this->setState(STOPPED);
}
break;
}
case REMOTE:
{
QVariantMap reply = this->baseConn->sendCommand(1001);
if(reply["status"] != 200){
//handle Error!!
returnCode = reply["status"].toInt();
}
else {
returnCode = 200;
}
break;
}
}
return returnCode;
}
int ClimbingRace::resetRace() {
if(this->state != STOPPED) {
return 904;
}
this->refreshMode();
qDebug() << "+ --- resetting race";
int returnCode = 900;
switch (this->mode) {
case LOCAL:
{
returnCode = this->speedTimers[0]->reset() ? 200:904;
if(returnCode == 200){
this->setState(IDLE);
}
break;
}
case REMOTE:
{
QVariantMap reply = this->baseConn->sendCommand(1002);
if(reply["status"] != 200){
//handle Error!!
returnCode = reply["status"].toInt();
}
else {
returnCode = 200;
}
break;
}
}
return returnCode;
}
// -------------------------
// --- Base Station sync ---
// -------------------------
void ClimbingRace::syncWithBaseStation() {
if(this->baseConn->state != "connected"){
this->baseStationSyncTimer->start();
return;
}
this->baseConn->refreshConnections();
emit this->baseStationConnectionsChanged();
QVariantMap tmpReply = this->baseConn->sendCommand(2000);
if(tmpReply["status"] != 200){
this->baseStationSyncTimer->start();
return;
}
this->setState( raceState( tmpReply["data"].toInt() ) );
switch (this->state) {
case 0:
{
// case IDLE
if(speedTimers[0]->state != 0){
speedTimers[0]->setState(SpeedTimer::IDLE);
}
break;
}
case 1:
{
// case STARTING
if(speedTimers[0]->state != 1){
speedTimers[0]->setState(SpeedTimer::STARTING);
}
tmpReply = this->baseConn->sendCommand(2004);
if(tmpReply["status"] != 200){
//handle Error!!
qDebug() << "+ --- getting next start action from basestation failed: " << tmpReply["status"];
this->baseStationSyncTimer->start();
return;
}
else {
if(this->nextStartAction != tmpReply["data"].toInt()){
this->nextStartAction = tmpReply["data"].toInt();
this->nextStartActionChanged(this->nextStartAction);
}
}
tmpReply = this->baseConn->sendCommand(2005);
if(tmpReply["status"] != 200){
//handle error!!
qDebug() << "+ --- getting next start action progress from basestation failed";
this->baseStationSyncTimer->start();
return;
}
else {
this->nextStartActionDelayProgress = tmpReply["data"].toDouble() > 0 ? tmpReply["data"].toDouble():0;
this->nextStartActionDelayProgressChanged();
}
break;
}
case 2:
{
// case RUNNING
if(speedTimers[0]->state != 2){
speedTimers[0]->setState(SpeedTimer::RUNNING);
}
// get current time
tmpReply = this->baseConn->sendCommand(2001, 0);
if(tmpReply["status"] != 200){
//handle error!!
qDebug() << "+ --- getting current time (timer 0) from basestation failed";
this->baseStationSyncTimer->start();
return;
}
else {
speedTimers[0]->stoppedTime = tmpReply["data"].toInt();
}
// get current time
tmpReply = this->baseConn->sendCommand(2003, 0);
if(tmpReply["status"] != 200){
//handle error!!
qDebug() << "+ --- getting reaction time (timer 0) from basestation failed";
this->baseStationSyncTimer->start();
return;
}
else {
speedTimers[0]->reactionTime = tmpReply["data"].toInt();
}
break;
}
case 3:
{
// case STOPPED
if(speedTimers[0]->state != 3){
speedTimers[0]->setState(SpeedTimer::STOPPED);
}
// get current time
tmpReply = this->baseConn->sendCommand(2001, 0);
if(tmpReply["status"] != 200){
//handle error!!
qDebug() << "+ --- getting current time (timer 0) from basestation failed";
return;
}
else {
speedTimers[0]->stoppedTime = tmpReply["data"].toInt();
}
// get current time
tmpReply = this->baseConn->sendCommand(2003, 0);
if(tmpReply["status"] != 200){
//handle error!!
qDebug() << "+ --- getting current time (timer 0) from basestation failed";
this->baseStationSyncTimer->start();
return;
}
else {
speedTimers[0]->reactionTime = tmpReply["data"].toInt();
}
break;
}
}
this->baseStationSyncTimer->start();
}
// ------------------------
// --- helper functions ---
// ------------------------
void ClimbingRace::playSoundsAndStartRace() {
qDebug() << "next Action: " << nextStartAction;
nextStartActionTimer->disconnect(nextStartActionTimer, SIGNAL(timeout()), this, SLOT(playSoundsAndStartRace()));
switch (this->nextStartAction) {
case 0:
{
if(!playSound("qrc:/sounds/at_marks_1.wav")){
return;
}
if(appSettings->loadSetting("ready_en") == "true"){
nextStartAction = 1;
nextStartActionTimer->setInterval(appSettings->loadSetting("ready_delay").toInt() <= 0 ? 1:appSettings->loadSetting("ready_delay").toInt());
}
else{
nextStartAction = 2;
nextStartActionTimer->setInterval(1);
}
break;
}
case 1:
{
if(!playSound("qrc:/sounds/ready_1.wav")){
return;
}
nextStartAction = 2;
nextStartActionTimer->setInterval(1);
break;
}
case 2:
{
if(!playSound("qrc:/sounds/OFFICAL_IFSC_STARTIGNAL.wav")){
return;
}
nextStartAction = -1;
nextStartActionTimer->disconnect(nextStartActionTimer, SIGNAL(timeout()), this, SLOT(playSoundsAndStartRace()));
this->setState(RUNNING);
speedTimers[0]->start();
return;
}
default:
{
this->speedTimers[0]->setState(SpeedTimer::STARTING);
if(appSettings->loadSetting("at_marks_en") == "true"){
nextStartAction = 0;
nextStartActionTimer->setInterval(appSettings->loadSetting("at_marks_delay").toInt() <= 0 ? 1:appSettings->loadSetting("at_marks_delay").toInt());
}
else if(appSettings->loadSetting("ready_en") == "true"){
nextStartAction = 1;
nextStartActionTimer->setInterval(appSettings->loadSetting("ready_delay").toInt() <= 0 ? 1:appSettings->loadSetting("ready_delay").toInt());
}
else{
nextStartAction = 2;
nextStartActionTimer->setInterval(1);
}
break;
}
}
nextStartActionTimer->connect(nextStartActionTimer, SIGNAL(timeout()), this, SLOT(playSoundsAndStartRace()));
nextStartActionTimer->start();
}
bool ClimbingRace::playSound(QString path) {
//connect(player, SIGNAL(positionChanged(qint64)), this, SLOT(positionChanged(qint64)));
player->setMedia(QUrl(path));
player->setVolume(50);
player->play();
QTimer timer;
timer.setInterval(1);
timer.setSingleShot(true);
QEventLoop loop;
loop.connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
while (player->mediaStatus() == QMediaPlayer::LoadingMedia || player->mediaStatus() == QMediaPlayer::BufferingMedia || player->mediaStatus() == QMediaPlayer::BufferedMedia) {
timer.start();
loop.exec();
}
if(player->mediaStatus() == QMediaPlayer::EndOfMedia){
return true;
}
else {
return false;
}
}
void ClimbingRace::setState(raceState newState) {
if(newState != this->state) {
this->state = newState;
this->stateChanged(newState);
}
}
void ClimbingRace::refreshMode() {
if(this->baseConn->state == "connected"){
this->mode = REMOTE;
}
else {
this->mode = LOCAL;
}
}
void ClimbingRace::refreshTimerText() {
// --- refresh timer text ---
QVariantList newTimerTextList;
foreach(SpeedTimer * timer, this->speedTimers){
QVariantMap timerMap = {{"text",timer->getText()}, {"reacttime", timer->reactionTime}};
newTimerTextList.append(timerMap);
}
if(newTimerTextList != this->qmlTimers){
this->qmlTimers = newTimerTextList;
emit timerTextChanged();
}
// --- refresh next start action delay progress ---
if(this->mode == LOCAL){
QString totalStr;
if(nextStartAction == 0){
totalStr = appSettings->loadSetting("at_marks_delay");
}
else if (nextStartAction == 1) {
totalStr = appSettings->loadSetting("ready_delay");
}
double remaining = this->nextStartActionTimer->remainingTime();
double total = totalStr.toDouble();
//qDebug() << "DELAY_PROG: " << "total: " << total << " remaining: " << remaining << " prog: " << remaining / total;
if(remaining > 0){
this->nextStartActionDelayProgress = remaining / total;
emit this->nextStartActionDelayProgressChanged();
}
else {
this->nextStartActionDelayProgress = 0;
emit this->nextStartActionDelayProgressChanged();
}
}
this->timerTextRefreshTimer->start();
}
// -------------------------
// --- functions for qml ---
// -------------------------
int ClimbingRace::getState() {
return this->state;
}
QVariant ClimbingRace::getTimerTextList() {
return this->qmlTimers;
// QVariantList test;
// QVariantMap test2 = {{"text", "1234"}, {"reacttime", 2.0}};
// test.append(test2);
// return test;
}
double ClimbingRace::getNextStartActionDelayProgress() {
return this->nextStartActionDelayProgress;
}
void ClimbingRace::writeSetting(QString key, QVariant value) {
this->refreshMode();
switch (this->mode) {
case LOCAL:
this->appSettings->writeSetting(key, value);
break;
case REMOTE:
this->baseConn->writeRemoteSetting(key, value.toString());
break;
}
}
QString ClimbingRace::readSetting(QString key) {
this->refreshMode();
switch (this->mode) {
case LOCAL:
return this->appSettings->loadSetting(key);
case REMOTE:
QVariantMap reply = this->baseConn->sendCommand(3001, key);
if(reply["status"] != 200){
return "false";
}
return reply["data"].toString();
}
}
bool ClimbingRace::connectBaseStation() {
return this->baseConn->connectToHost();
}
QString ClimbingRace::getBaseStationState() {
return this->baseConn->getState();
}
QVariant ClimbingRace::getBaseStationConnections() {
return baseConn->getConnections();
}

View file

@ -55,6 +55,7 @@
#include "headers/baseconn.h"
#include "headers/speedtimer.h"
#include "headers/speedtimerqmladapter.h"
#include "headers/climbingrace.h"
#include <QTranslator>
static void connectToDatabase()
@ -108,23 +109,20 @@ int main(int argc, char *argv[])
#endif
connectToDatabase();
//BuzzerConn * pBuzzerConn = new BuzzerConn(nullptr, "192.168.4.10", 80);
//BuzzerConn * pStartpadConn = new BuzzerConn(nullptr, "192.168.4.11", 80);
AppSettings * pAppSettings = new AppSettings();
pGlobalAppSettings = pAppSettings;
SpeedTimer * pSpeedTimer = new SpeedTimer();
pGlobalSpeedTimer = pSpeedTimer;
AppSettings * pAppSettings = new AppSettings();
//setup the sql storage model as a qml model
qmlRegisterType<SqlProfileModel>("com.itsblue.speedclimbingstopwatch", 1, 0, "SqlProfileModel");
qmlRegisterType<SqlStorageModel>("com.itsblue.speedclimbingstopwatch", 1, 0, "SqlStorageModel");
//setup the startpad and buzzer conn qml objects
qmlRegisterType<BuzzerConn>("com.itsblue.speedclimbingstopwatch", 1, 0, "BuzzerConn");
qmlRegisterType<BuzzerConn>("com.itsblue.speedclimbingstopwatch", 1, 0, "StartpadConn");
qmlRegisterType<BaseConn>("com.itsblue.speedclimbingstopwatch", 1, 0, "BaseStationConn");
qmlRegisterType<SpeedTimerQmlAdapter>("com.itsblue.speedclimbingstopwatch", 1, 0, "SpeedTimerBackend");
//qmlRegisterType<BuzzerConn>("com.itsblue.speedclimbingstopwatch", 1, 0, "BuzzerConn");
//qmlRegisterType<BuzzerConn>("com.itsblue.speedclimbingstopwatch", 1, 0, "StartpadConn");
//qmlRegisterType<BaseConn>("com.itsblue.speedclimbingstopwatch", 1, 0, "BaseStationConn");
//qmlRegisterType<SpeedTimerQmlAdapter>("com.itsblue.speedclimbingstopwatch", 1, 0, "SpeedTimerBackend");
qmlRegisterType<ClimbingRace>("com.itsblue.speedclimbingstopwatch", 2, 0, "SpeedBackend");
//setup translation engine
//to the language of the system
@ -135,17 +133,15 @@ int main(int argc, char *argv[])
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QQmlContext *context = engine.rootContext();
context->setContextProperty("_cppAppSettings", pAppSettings);
if (engine.rootObjects().isEmpty())
return -1;
engine.rootContext()->setContextProperty("_cppAppSettings", pAppSettings);
engine.rootContext()->setContextProperty("_cppSpeedTimer", pSpeedTimer);
int iRet = 0;
iRet = app.exec();
//call the destructors of all objects
delete pAppSettings;
return iRet;
}

View file

@ -12,15 +12,14 @@ SpeedTimer::SpeedTimer(QObject *parent) : QObject(parent)
this->stoppedTime = 0;
this->reactionTime = 0;
this->state = IDLE;
this->remoteControlled = false;
}
void SpeedTimer::start() {
if(this->state != STARTING){
return;
bool SpeedTimer::start(bool force) {
if(this->state != STARTING && !force){
return false;
}
qDebug() << "starting timer";
if(!this->remoteControlled){
if(!force){
this->stopTime = 0;
this->stoppedTime = 0;
this->reactionTime = 0;
@ -28,49 +27,55 @@ void SpeedTimer::start() {
}
this->setState(RUNNING);
return true;
//this->startPad->appendCommand("SET_LED_RUNNING");
}
void SpeedTimer::stop(QString type) {
if(this->state != SpeedTimer::STARTING && this->state != SpeedTimer::RUNNING){
return;
bool SpeedTimer::stop(int type, bool force) {
// type can be:
// 0: stopped
// 1: cancelled
// 2: failed (fase start)
if( ( this->state != SpeedTimer::STARTING && this->state != SpeedTimer::RUNNING && this->state ) && !force ){
return false;
}
//qDebug() << "Stopping: " << "start Time: " << startTime << " stopTime: " << stopTime << " stoppedTime: " << stoppedTime << " reactionTime: " << reactionTime;
if(this->remoteControlled){
if(type == "cancel"){
emit startCanceled(false);
}
}
else if(type == "cancel"){
emit startCanceled(false);
this->stoppedTime = 0;
}
else if(type == "manual"){
if(this->state == STARTING){
emit startCanceled(false);
}
switch (type) {
case 0:
{
this->stopTime = this->date->currentMSecsSinceEpoch();
this->stoppedTime = this->stopTime - this->startTime;
this->setState(STOPPED);
break;
}
else if(type == "topPad"){
//this->stopTime = this->topPad->latest_button_pressed + this->topPad->offset;
this->stoppedTime = this->stopTime - this->startTime;
case 1:
{
this->stoppedTime = 0;
this->setState(CANCELLED);
break;
}
else if(type == "falseStart"){
emit startCanceled(true);
case 2:
{
this->stoppedTime = this->reactionTime;
this->setState(FAILED);
break;
}
}
this->setState(STOPPED);
qDebug() << "Stopped: " << "start Time: " << startTime << " stopTime: " << stopTime << " stoppedTime: " << stoppedTime << " reactionTime: " << reactionTime;
return true;
//this->startPad->appendCommand("SET_LED_STARTING");
}
void SpeedTimer::reset(){
if(this->state != STOPPED){
return;
bool SpeedTimer::reset(bool force){
if( ( this->state != STOPPED && this->state != FAILED && this->state != CANCELLED ) && !force){
return false;
}
this->startTime = 0;
@ -78,6 +83,8 @@ void SpeedTimer::reset(){
this->stoppedTime = 0;
this->reactionTime = 0;
this->setState(IDLE);
return true;
//this->startPad->appendCommand("SET_LED_STARTING");
}
@ -102,7 +109,7 @@ QString SpeedTimer::getState(){
double SpeedTimer::getCurrTime() {
double currTime;
if(this->state == RUNNING && !this->remoteControlled){
if(this->state == RUNNING && this->startTime > 0){
currTime = this->date->currentMSecsSinceEpoch() - this->startTime;
}
else {
@ -112,6 +119,33 @@ double SpeedTimer::getCurrTime() {
return(currTime);
}
QString SpeedTimer::getText() {
//qDebug() << this->getState();
QString newText;
switch (this->state) {
case SpeedTimer::IDLE:
newText = "Click Start to start";
break;
case SpeedTimer::STARTING:
newText = "0.000 sec";
break;
case SpeedTimer::RUNNING:
newText = QString::number( this->getCurrTime() / 1000.0, 'f', 3 ) + " sec";
break;
case SpeedTimer::STOPPED:
newText = QString::number( this->stoppedTime / 1000.0, 'f', 3 ) + " sec";
break;
case SpeedTimer::FAILED:
newText = "False Start";
break;
case SpeedTimer::CANCELLED:
newText = "Cancelled";
break;
}
return newText;
}
void SpeedTimer::delay(int mSecs){
QEventLoop loop;
QTimer timer;

View file

@ -1,95 +0,0 @@
#include "headers/speedtimerqmladapter.h"
SpeedTimerQmlAdapter::SpeedTimerQmlAdapter(QObject *parent) : QObject(parent)
{
this->state = SpeedTimer::IDLE;
connect(pGlobalSpeedTimer, &SpeedTimer::stateChanged, this, &SpeedTimerQmlAdapter::setState);
connect(pGlobalSpeedTimer, &SpeedTimer::startCanceled, this, &SpeedTimerQmlAdapter::startCanceled);
this->refreshTimer = new QTimer();
refreshTimer->setInterval(1);
refreshTimer->setSingleShot(true);
refreshTimer->connect(refreshTimer, &QTimer::timeout, this, &SpeedTimerQmlAdapter::refreshValues);
refreshTimer->start();
}
QString SpeedTimerQmlAdapter::getState(){
switch(state){
case SpeedTimer::IDLE:
return("IDLE");
case SpeedTimer::STARTING:
return("STARTING");
case SpeedTimer::RUNNING:
return("RUNNING");
case SpeedTimer::STOPPED:
return("STOPPED");
}
}
void SpeedTimerQmlAdapter::setState(SpeedTimer::timerState newState){
this->state = newState;
emit this->stateChanged(newState);
}
QString SpeedTimerQmlAdapter::getText(){
return(this->text);
}
void SpeedTimerQmlAdapter::refreshValues(){
//qDebug() << this->getState();
QString newText;
switch (this->state) {
case SpeedTimer::IDLE:
newText = "Click Start to start";
break;
case SpeedTimer::STARTING:
newText = "0.000 sec";
break;
case SpeedTimer::RUNNING:
newText = QString::number( pGlobalSpeedTimer->getCurrTime() / 1000.0, 'f', 3 ) + " sec";
break;
case SpeedTimer::STOPPED:
newText = QString::number( pGlobalSpeedTimer->stoppedTime / 1000.0, 'f', 3 ) + " sec";
}
if(this->text != newText){
this->text = newText;
emit textChanged();
}
//qDebug() << this->text;
refreshTimer->start();
}
bool SpeedTimerQmlAdapter::setStarting(){
if(pGlobalSpeedTimer->remoteControlled){
return false;
}
pGlobalSpeedTimer->setState(SpeedTimer::STARTING);
return true;
}
bool SpeedTimerQmlAdapter::start(){
if(pGlobalSpeedTimer->remoteControlled){
return false;
}
pGlobalSpeedTimer->start();
return true;
}
bool SpeedTimerQmlAdapter::stop(QString type){
if(pGlobalSpeedTimer->remoteControlled){
return false;
}
pGlobalSpeedTimer->stop(type);
return true;
}
bool SpeedTimerQmlAdapter::reset(){
if(pGlobalSpeedTimer->remoteControlled){
return false;
}
pGlobalSpeedTimer->reset();
return true;
}

View file

@ -1,4 +1,4 @@
QT += quick sql
QT += quick sql multimedia
android {
QT += androidextras
@ -23,20 +23,18 @@ SOURCES += \
sources/main.cpp \
sources/sqlstoragemodel.cpp \
sources/sqlprofilemodel.cpp \
sources/buzzerconn.cpp \
sources/appsettings.cpp \
sources/baseconn.cpp \
sources/speedtimer.cpp \
sources/speedtimerqmladapter.cpp
sources/climbingrace.cpp
HEADERS += \
headers/sqlstoragemodel.h \
headers/sqlprofilemodel.h \
headers/buzzerconn.h \
headers/appsettings.h \
headers/baseconn.h \
headers/speedtimer.h \
headers/speedtimerqmladapter.h
headers/climbingrace.h
RESOURCES += \
shared.qrc \