added some functionality for start ready detection

This commit is contained in:
Dorian Zedler 2020-09-29 12:53:40 +02:00
parent 172ba0d1a0
commit c6af1f8985
Signed by: dorian
GPG key ID: D3B255CB8BC7CD37
6 changed files with 166 additions and 37 deletions

View file

@ -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)

View file

@ -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();
/** /**

View file

@ -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:
/*! /*!

View file

@ -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");
} }

View file

@ -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,35 +220,71 @@ 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;
double timeOfSoundPlaybackStart; // The check if all timers are ready has already happened at this point
if(this->startActionSettings.contains(thisAction) && this->startActionSettings[thisAction]["Enabled"].toBool()) { if(!this->doDelayAndSoundOfStartAction(AtYourMarks)) {
if(!this->soundPlayer->play(thisAction, this->soundVolume, &timeOfSoundPlaybackStart)) qDebug() << "At marks sound returned false!";
return false; return false;
if(this->state != STARTING)
return true;
} }
if(thisAction < Start) { qDebug() << "NOW IN WAITING";
this->nextStartAction = StartAction(thisAction + 1); this->setState(WAITING);
qDebug() << "next action is: " << nextStartAction;
int nextActionDelay = -1; // do climber readiness tests
if(this->startActionSettings.contains(this->nextStartAction) && this->startActionSettings[this->nextStartAction]["Enabled"].toBool()) { this->nextStartAction = Ready;
nextActionDelay = this->startActionSettings[this->nextStartAction]["Delay"].toInt();
}
// perform next action
this->nextActionTimer->start(nextActionDelay > 0 ? nextActionDelay:1);
if(nextActionDelay > 0)
emit this->nextStartActionChanged(); emit this->nextStartActionChanged();
this->nextActionLoop->exec();
return this->playSoundsAndStartTimers(this->nextStartAction); // 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;
this->doDelayAndSoundOfStartAction(Start, &timeOfSoundPlaybackStart);
// perform start // perform start
// start all timers // start all timers
@ -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);

View file

@ -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;