added some functionality for start ready detection
This commit is contained in:
parent
172ba0d1a0
commit
c6af1f8985
6 changed files with 166 additions and 37 deletions
|
@ -128,7 +128,8 @@ public:
|
||||||
InternalError = 950,
|
InternalError = 950,
|
||||||
InternalErrorTimerOperationFailed = 951,
|
InternalErrorTimerOperationFailed = 951,
|
||||||
ApiVersionNotSupportedError = 952,
|
ApiVersionNotSupportedError = 952,
|
||||||
CompetitionModeProhibitsThisError = 953
|
CompetitionModeProhibitsThisError = 953,
|
||||||
|
TimersNotReadyError = 501 /*!< One or more timer is not ready */
|
||||||
};
|
};
|
||||||
Q_ENUM(ScStw::StatusCode)
|
Q_ENUM(ScStw::StatusCode)
|
||||||
|
|
||||||
|
|
|
@ -56,8 +56,7 @@ class ScStwRace : public QObject
|
||||||
public:
|
public:
|
||||||
explicit ScStwRace(QObject *parent = nullptr);
|
explicit ScStwRace(QObject *parent = nullptr);
|
||||||
|
|
||||||
enum RaceState { IDLE, STARTING, WAITING, RUNNING, STOPPED };
|
enum RaceState { IDLE, PREPAIRING, WAITING, STARTING, RUNNING, STOPPED };
|
||||||
// will become IDLE, PREPAIRING, WAITING, STARTING, RUNNING, STOPPED later
|
|
||||||
Q_ENUM(RaceState)
|
Q_ENUM(RaceState)
|
||||||
|
|
||||||
enum StartAction { None = -1, AtYourMarks = 0, Ready = 1, Start = 2 };
|
enum StartAction { None = -1, AtYourMarks = 0, Ready = 1, Start = 2 };
|
||||||
|
@ -80,6 +79,8 @@ private:
|
||||||
|
|
||||||
QTimer *nextActionTimer;
|
QTimer *nextActionTimer;
|
||||||
QEventLoop *nextActionLoop;
|
QEventLoop *nextActionLoop;
|
||||||
|
QTimer *climberReadyWaitTimer;
|
||||||
|
QEventLoop *climberReadyWaitLoop;
|
||||||
|
|
||||||
// sounds
|
// sounds
|
||||||
ScStwSoundPlayer * soundPlayer;
|
ScStwSoundPlayer * soundPlayer;
|
||||||
|
@ -141,7 +142,8 @@ private slots:
|
||||||
void refreshTimerStates();
|
void refreshTimerStates();
|
||||||
void handleTimerWantsToBeDisabledChange(ScStwTimer* timer, bool wantsToBeDisabled);
|
void handleTimerWantsToBeDisabledChange(ScStwTimer* timer, bool wantsToBeDisabled);
|
||||||
int handleFalseStart();
|
int handleFalseStart();
|
||||||
bool playSoundsAndStartTimers(StartAction thisAction);
|
bool playSoundsAndStartTimers();
|
||||||
|
bool doDelayAndSoundOfStartAction(StartAction action, double *timeOfSoundPlaybackStart = nullptr);
|
||||||
void enableAllTimers();
|
void enableAllTimers();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -25,6 +25,9 @@
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include "ScStw.hpp"
|
#include "ScStw.hpp"
|
||||||
|
|
||||||
|
class ScStwRace;
|
||||||
|
class ScStwRemoteRace;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief The ScStwTimer class is used for advanced time measurement.
|
* \brief The ScStwTimer class is used for advanced time measurement.
|
||||||
*
|
*
|
||||||
|
@ -70,6 +73,9 @@ public:
|
||||||
*/
|
*/
|
||||||
explicit ScStwTimer(QObject *parent = nullptr, bool directControlEnabled = false, QString letter = "" );
|
explicit ScStwTimer(QObject *parent = nullptr, bool directControlEnabled = false, QString letter = "" );
|
||||||
|
|
||||||
|
friend class ScStwRace;
|
||||||
|
friend class ScStwRemoteRace;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief The TimerState enum contains all state the timer can be in
|
* \brief The TimerState enum contains all state the timer can be in
|
||||||
*/
|
*/
|
||||||
|
@ -82,7 +88,7 @@ public:
|
||||||
LOST, /*!< Timer has lost */
|
LOST, /*!< Timer has lost */
|
||||||
FAILED, /*!< A false start occured */
|
FAILED, /*!< A false start occured */
|
||||||
CANCELLED, /*!< Timer was cancelled */
|
CANCELLED, /*!< Timer was cancelled */
|
||||||
// INCIDENT, /*!< There was a technical incident */
|
INCIDENT, /*!< There was a technical incident (most likely a disconnected extension) */
|
||||||
DISABLED /*!< Timer is disabled */
|
DISABLED /*!< Timer is disabled */
|
||||||
};
|
};
|
||||||
Q_ENUM(TimerState);
|
Q_ENUM(TimerState);
|
||||||
|
@ -375,6 +381,15 @@ protected slots:
|
||||||
*/
|
*/
|
||||||
void setWantsToBeDisabled(bool wantsToBeDisabled);
|
void setWantsToBeDisabled(bool wantsToBeDisabled);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Function to set the timer into INCIDENT state immidieately
|
||||||
|
*
|
||||||
|
* The current state of the timer will be ignored! It can only get out of this state by calling ScStwTimer::reset
|
||||||
|
*
|
||||||
|
* \see reset
|
||||||
|
*/
|
||||||
|
void technicalIncident();
|
||||||
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
/*!
|
/*!
|
||||||
|
|
|
@ -38,6 +38,8 @@ void ScStwLibraries::init() {
|
||||||
qmlRegisterUncreatableType<ScStwSetting>("de.itsblue.ScStw", 2, 0, "ScStwSetting", "The ScStwSetting is manage by a ScStwSettings instance and therefore not creatable");
|
qmlRegisterUncreatableType<ScStwSetting>("de.itsblue.ScStw", 2, 0, "ScStwSetting", "The ScStwSetting is manage by a ScStwSettings instance and therefore not creatable");
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
qRegisterMetaType<ScStw::PadState>("ScStw::PadState");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
** ScStw Libraries
|
** ScStw Libraries
|
||||||
** Copyright (C) 2020 Itsblue development
|
** Copyright (C) 2020 Itsblue development
|
||||||
**
|
**
|
||||||
|
@ -28,10 +28,12 @@ ScStwRace::ScStwRace(QObject *parent) : QObject(parent)
|
||||||
// configure timer that handles the delay between the start commands
|
// configure timer that handles the delay between the start commands
|
||||||
this->nextActionTimer = new QTimer(this);
|
this->nextActionTimer = new QTimer(this);
|
||||||
nextActionTimer->setSingleShot(true);
|
nextActionTimer->setSingleShot(true);
|
||||||
this->nextActionLoop = new QEventLoop();
|
this->nextActionLoop = new QEventLoop(this);
|
||||||
this->nextStartAction = None;
|
this->nextStartAction = None;
|
||||||
|
this->climberReadyWaitLoop = new QEventLoop(this);
|
||||||
|
|
||||||
connect(this->nextActionTimer, &QTimer::timeout, this->nextActionLoop, &QEventLoop::quit);
|
connect(this->nextActionTimer, &QTimer::timeout, this->nextActionLoop, &QEventLoop::quit);
|
||||||
|
connect(this->nextActionTimer, &QTimer::timeout, this->climberReadyWaitLoop, &QEventLoop::quit);
|
||||||
connect(this, &ScStwRace::nextStartActionChanged, this, &ScStwRace::nextStartActionDetailsChanged);
|
connect(this, &ScStwRace::nextStartActionChanged, this, &ScStwRace::nextStartActionDetailsChanged);
|
||||||
|
|
||||||
// write default settings
|
// write default settings
|
||||||
|
@ -51,15 +53,45 @@ int ScStwRace::start(bool asyncronous) {
|
||||||
return ScStw::CurrentStateNotVaildForOperationError;
|
return ScStw::CurrentStateNotVaildForOperationError;
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "+ [INFO] starting race";
|
qDebug() << "[INFO][RACE] checking timers";
|
||||||
|
foreach (ScStwTimer *timer, this->timers) {
|
||||||
|
if(timer->getState() == ScStwTimer::DISABLED)
|
||||||
|
continue;
|
||||||
|
|
||||||
this->setState(STARTING);
|
if(timer->getReadyState() != ScStwTimer::ClimberIsNotReady) {
|
||||||
|
|
||||||
|
if(this->allowAutomaticTimerDisable) {
|
||||||
|
timer->setDisabled(true);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Timer ready state is: " << timer->getReadyState();
|
||||||
|
|
||||||
|
timer->technicalIncident();
|
||||||
|
|
||||||
|
foreach (ScStwTimer *subTimer, this->timers) {
|
||||||
|
if(timer != subTimer && subTimer->getReadyState() != ScStwTimer::ClimberIsNotReady)
|
||||||
|
subTimer->technicalIncident();
|
||||||
|
else if(timer != subTimer)
|
||||||
|
subTimer->setState(ScStwTimer::CANCELLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->setState(STOPPED);
|
||||||
|
|
||||||
|
qDebug() << "[ERROR][RACE] Could not start due to not-ready timers";
|
||||||
|
|
||||||
|
return ScStw::TimersNotReadyError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "[INFO][RACE] starting race";
|
||||||
|
this->setState(PREPAIRING);
|
||||||
|
|
||||||
if(asyncronous) {
|
if(asyncronous) {
|
||||||
QTimer::singleShot(1, [=](){this->playSoundsAndStartTimers(None);});
|
QTimer::singleShot(1, [=](){this->playSoundsAndStartTimers();});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
this->playSoundsAndStartTimers(None);
|
this->playSoundsAndStartTimers();
|
||||||
|
|
||||||
return ScStw::Success;
|
return ScStw::Success;
|
||||||
}
|
}
|
||||||
|
@ -188,34 +220,70 @@ int ScStwRace::handleFalseStart() {
|
||||||
return returnCode;
|
return returnCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ScStwRace::playSoundsAndStartTimers(StartAction thisAction) {
|
bool ScStwRace::playSoundsAndStartTimers() {
|
||||||
if(this->state != STARTING)
|
if(this->state != PREPAIRING)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
// The check if all timers are ready has already happened at this point
|
||||||
|
if(!this->doDelayAndSoundOfStartAction(AtYourMarks)) {
|
||||||
|
qDebug() << "At marks sound returned false!";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "NOW IN WAITING";
|
||||||
|
this->setState(WAITING);
|
||||||
|
|
||||||
|
// do climber readiness tests
|
||||||
|
this->nextStartAction = Ready;
|
||||||
|
emit this->nextStartActionChanged();
|
||||||
|
|
||||||
|
// if the automatic ready tone is enabled, wait for the climbers to become ready
|
||||||
|
if(this->startActionSettings.contains(Ready) && this->startActionSettings[Ready]["Enabled"].toBool()) {
|
||||||
|
|
||||||
|
qDebug() << "[RACE][INFO] Now waiting for climbers";
|
||||||
|
|
||||||
|
// get delay
|
||||||
|
int minimumReadyDelay = 1000;
|
||||||
|
if(this->startActionSettings[Ready]["Delay"].toInt() > 1000 || !this->allowAutomaticTimerDisable)
|
||||||
|
minimumReadyDelay = this->startActionSettings[Ready]["Delay"].toInt();
|
||||||
|
|
||||||
|
// wait for climbers to become ready initially
|
||||||
|
bool allClimbersReady = false;
|
||||||
|
while (!allClimbersReady) {
|
||||||
|
allClimbersReady = true;
|
||||||
|
foreach (ScStwTimer *timer, this->timers) {
|
||||||
|
if(timer->getReadyState() != ScStwTimer::IsReady)
|
||||||
|
allClimbersReady = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!allClimbersReady)
|
||||||
|
this->climberReadyWaitLoop->exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "[RACE][DEBUG] Initial wait finished";
|
||||||
|
|
||||||
|
// wait for all climbers to be ready for the ReadyActionDelay, but at least one second continuosly
|
||||||
|
// the climber ready wait loop will also quit, if the climber steps of the pad
|
||||||
|
// -> wait for both climbers to stand on the pad for at least one second
|
||||||
|
do {
|
||||||
|
this->nextActionTimer->stop();
|
||||||
|
this->nextActionTimer->start(minimumReadyDelay);
|
||||||
|
this->climberReadyWaitLoop->exec();
|
||||||
|
} while(this->nextActionTimer->remainingTime() > 0);
|
||||||
|
|
||||||
|
qDebug() << "[RACE][DEBUG] Wait finished, starting now!";
|
||||||
|
|
||||||
|
// play ready tone
|
||||||
|
if(!this->soundPlayer->play(Ready, this->soundVolume))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// enter starting state
|
||||||
|
this->setState(STARTING);
|
||||||
|
|
||||||
|
// play start tone
|
||||||
double timeOfSoundPlaybackStart;
|
double timeOfSoundPlaybackStart;
|
||||||
if(this->startActionSettings.contains(thisAction) && this->startActionSettings[thisAction]["Enabled"].toBool()) {
|
this->doDelayAndSoundOfStartAction(Start, &timeOfSoundPlaybackStart);
|
||||||
if(!this->soundPlayer->play(thisAction, this->soundVolume, &timeOfSoundPlaybackStart))
|
|
||||||
return false;
|
|
||||||
if(this->state != STARTING)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(thisAction < Start) {
|
|
||||||
this->nextStartAction = StartAction(thisAction + 1);
|
|
||||||
qDebug() << "next action is: " << nextStartAction;
|
|
||||||
|
|
||||||
int nextActionDelay = -1;
|
|
||||||
if(this->startActionSettings.contains(this->nextStartAction) && this->startActionSettings[this->nextStartAction]["Enabled"].toBool()) {
|
|
||||||
nextActionDelay = this->startActionSettings[this->nextStartAction]["Delay"].toInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
// perform next action
|
|
||||||
this->nextActionTimer->start(nextActionDelay > 0 ? nextActionDelay:1);
|
|
||||||
if(nextActionDelay > 0)
|
|
||||||
emit this->nextStartActionChanged();
|
|
||||||
this->nextActionLoop->exec();
|
|
||||||
return this->playSoundsAndStartTimers(this->nextStartAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
// perform start
|
// perform start
|
||||||
|
|
||||||
|
@ -250,6 +318,32 @@ bool ScStwRace::playSoundsAndStartTimers(StartAction thisAction) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ScStwRace::doDelayAndSoundOfStartAction(ScStwRace::StartAction action, double *timeOfSoundPlaybackStart) {
|
||||||
|
if(this->startActionSettings.contains(action) && this->startActionSettings[action]["Enabled"].toBool()) {
|
||||||
|
|
||||||
|
this->nextStartAction = action;
|
||||||
|
emit this->nextStartActionChanged();
|
||||||
|
|
||||||
|
if(action != Start && this->startActionSettings[action]["Delay"].toInt() > 0) {
|
||||||
|
// perform the delay before the start
|
||||||
|
|
||||||
|
// get delay
|
||||||
|
int thisActionDelay = this->startActionSettings[action]["Delay"].toInt();
|
||||||
|
|
||||||
|
// perform next action
|
||||||
|
if(thisActionDelay > 0) {
|
||||||
|
this->nextActionTimer->start(thisActionDelay);
|
||||||
|
this->nextActionLoop->exec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!this->soundPlayer->play(action, this->soundVolume, timeOfSoundPlaybackStart))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void ScStwRace::setState(RaceState newState) {
|
void ScStwRace::setState(RaceState newState) {
|
||||||
if(newState != this->state) {
|
if(newState != this->state) {
|
||||||
qDebug() << "[INFO][RACE] state changed: " << newState;
|
qDebug() << "[INFO][RACE] state changed: " << newState;
|
||||||
|
@ -401,6 +495,7 @@ bool ScStwRace::addTimer(ScStwTimer *timer) {
|
||||||
connect(timer, &ScStwTimer::wantsToBeDisabledChanged, this, &ScStwRace::handleTimerWantsToBeDisabledChange);
|
connect(timer, &ScStwTimer::wantsToBeDisabledChanged, this, &ScStwRace::handleTimerWantsToBeDisabledChange);
|
||||||
connect(timer, &ScStwTimer::stateChanged, this, &ScStwRace::timersChanged);
|
connect(timer, &ScStwTimer::stateChanged, this, &ScStwRace::timersChanged);
|
||||||
connect(timer, &ScStwTimer::reactionTimeChanged, this, &ScStwRace::timersChanged);
|
connect(timer, &ScStwTimer::reactionTimeChanged, this, &ScStwRace::timersChanged);
|
||||||
|
connect(timer, &ScStwTimer::readyStateChanged, this->climberReadyWaitLoop, &QEventLoop::quit);
|
||||||
|
|
||||||
if(!this->allowAutomaticTimerDisable && timer->getState() == ScStwTimer::DISABLED)
|
if(!this->allowAutomaticTimerDisable && timer->getState() == ScStwTimer::DISABLED)
|
||||||
timer->setDisabled(false);
|
timer->setDisabled(false);
|
||||||
|
|
|
@ -64,6 +64,12 @@ bool ScStwTimer::start(double timeOfStart) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ScStwTimer::technicalIncident() {
|
||||||
|
qDebug() << "[INFO][TIMER] Timer got a technical incident";
|
||||||
|
|
||||||
|
this->setState(INCIDENT);
|
||||||
|
}
|
||||||
|
|
||||||
void ScStwTimer::handleClimberStart(double timeOfStart) {
|
void ScStwTimer::handleClimberStart(double timeOfStart) {
|
||||||
this->reactionTime = timeOfStart - this->startTime;
|
this->reactionTime = timeOfStart - this->startTime;
|
||||||
qDebug() << "+ [INFO][TIMER] reaction time: " << this->reactionTime;
|
qDebug() << "+ [INFO][TIMER] reaction time: " << this->reactionTime;
|
||||||
|
@ -80,7 +86,7 @@ bool ScStwTimer::cancel() {
|
||||||
if(!(this->state == IDLE || this->state == STARTING || this->state == RUNNING))
|
if(!(this->state == IDLE || this->state == STARTING || this->state == RUNNING))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
this->setState(CANCELLED);
|
this->stop(CancelStop, -1);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,6 +125,11 @@ bool ScStwTimer::stop(StopReason reason, double timeOfStop) {
|
||||||
this->setState(FAILED);
|
this->setState(FAILED);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case CancelStop: {
|
||||||
|
qDebug() << "[INFO][TIMER] Timer was cancelled";
|
||||||
|
this->setState(CANCELLED);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -201,6 +212,9 @@ void ScStwTimer::setState(TimerState newState){
|
||||||
if(this->state == DISABLED && newState != IDLE)
|
if(this->state == DISABLED && newState != IDLE)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if(this->state == INCIDENT && newState != IDLE)
|
||||||
|
return;
|
||||||
|
|
||||||
if(this->state != newState) {
|
if(this->state != newState) {
|
||||||
this->state = newState;
|
this->state = newState;
|
||||||
qDebug() << "+ [INFO][TIMER] timer state changed: " << newState;
|
qDebug() << "+ [INFO][TIMER] timer state changed: " << newState;
|
||||||
|
|
Reference in a new issue