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,
|
||||
InternalErrorTimerOperationFailed = 951,
|
||||
ApiVersionNotSupportedError = 952,
|
||||
CompetitionModeProhibitsThisError = 953
|
||||
CompetitionModeProhibitsThisError = 953,
|
||||
TimersNotReadyError = 501 /*!< One or more timer is not ready */
|
||||
};
|
||||
Q_ENUM(ScStw::StatusCode)
|
||||
|
||||
|
|
|
@ -56,8 +56,7 @@ class ScStwRace : public QObject
|
|||
public:
|
||||
explicit ScStwRace(QObject *parent = nullptr);
|
||||
|
||||
enum RaceState { IDLE, STARTING, WAITING, RUNNING, STOPPED };
|
||||
// will become IDLE, PREPAIRING, WAITING, STARTING, RUNNING, STOPPED later
|
||||
enum RaceState { IDLE, PREPAIRING, WAITING, STARTING, RUNNING, STOPPED };
|
||||
Q_ENUM(RaceState)
|
||||
|
||||
enum StartAction { None = -1, AtYourMarks = 0, Ready = 1, Start = 2 };
|
||||
|
@ -80,6 +79,8 @@ private:
|
|||
|
||||
QTimer *nextActionTimer;
|
||||
QEventLoop *nextActionLoop;
|
||||
QTimer *climberReadyWaitTimer;
|
||||
QEventLoop *climberReadyWaitLoop;
|
||||
|
||||
// sounds
|
||||
ScStwSoundPlayer * soundPlayer;
|
||||
|
@ -141,7 +142,8 @@ private slots:
|
|||
void refreshTimerStates();
|
||||
void handleTimerWantsToBeDisabledChange(ScStwTimer* timer, bool wantsToBeDisabled);
|
||||
int handleFalseStart();
|
||||
bool playSoundsAndStartTimers(StartAction thisAction);
|
||||
bool playSoundsAndStartTimers();
|
||||
bool doDelayAndSoundOfStartAction(StartAction action, double *timeOfSoundPlaybackStart = nullptr);
|
||||
void enableAllTimers();
|
||||
|
||||
/**
|
||||
|
|
|
@ -25,6 +25,9 @@
|
|||
#include <QTimer>
|
||||
#include "ScStw.hpp"
|
||||
|
||||
class ScStwRace;
|
||||
class ScStwRemoteRace;
|
||||
|
||||
/*!
|
||||
* \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 = "" );
|
||||
|
||||
friend class ScStwRace;
|
||||
friend class ScStwRemoteRace;
|
||||
|
||||
/*!
|
||||
* \brief The TimerState enum contains all state the timer can be in
|
||||
*/
|
||||
|
@ -82,7 +88,7 @@ public:
|
|||
LOST, /*!< Timer has lost */
|
||||
FAILED, /*!< A false start occured */
|
||||
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 */
|
||||
};
|
||||
Q_ENUM(TimerState);
|
||||
|
@ -375,6 +381,15 @@ protected slots:
|
|||
*/
|
||||
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:
|
||||
/*!
|
||||
|
|
|
@ -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");
|
||||
#endif
|
||||
#endif
|
||||
|
||||
qRegisterMetaType<ScStw::PadState>("ScStw::PadState");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/****************************************************************************
|
||||
/****************************************************************************
|
||||
** ScStw Libraries
|
||||
** 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
|
||||
this->nextActionTimer = new QTimer(this);
|
||||
nextActionTimer->setSingleShot(true);
|
||||
this->nextActionLoop = new QEventLoop();
|
||||
this->nextActionLoop = new QEventLoop(this);
|
||||
this->nextStartAction = None;
|
||||
this->climberReadyWaitLoop = new QEventLoop(this);
|
||||
|
||||
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);
|
||||
|
||||
// write default settings
|
||||
|
@ -51,15 +53,45 @@ int ScStwRace::start(bool asyncronous) {
|
|||
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) {
|
||||
QTimer::singleShot(1, [=](){this->playSoundsAndStartTimers(None);});
|
||||
QTimer::singleShot(1, [=](){this->playSoundsAndStartTimers();});
|
||||
}
|
||||
else
|
||||
this->playSoundsAndStartTimers(None);
|
||||
this->playSoundsAndStartTimers();
|
||||
|
||||
return ScStw::Success;
|
||||
}
|
||||
|
@ -188,34 +220,70 @@ int ScStwRace::handleFalseStart() {
|
|||
return returnCode;
|
||||
}
|
||||
|
||||
bool ScStwRace::playSoundsAndStartTimers(StartAction thisAction) {
|
||||
if(this->state != STARTING)
|
||||
bool ScStwRace::playSoundsAndStartTimers() {
|
||||
if(this->state != PREPAIRING)
|
||||
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;
|
||||
if(this->startActionSettings.contains(thisAction) && this->startActionSettings[thisAction]["Enabled"].toBool()) {
|
||||
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);
|
||||
}
|
||||
this->doDelayAndSoundOfStartAction(Start, &timeOfSoundPlaybackStart);
|
||||
|
||||
// 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) {
|
||||
if(newState != this->state) {
|
||||
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::stateChanged, 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)
|
||||
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) {
|
||||
this->reactionTime = timeOfStart - this->startTime;
|
||||
qDebug() << "+ [INFO][TIMER] reaction time: " << this->reactionTime;
|
||||
|
@ -80,7 +86,7 @@ bool ScStwTimer::cancel() {
|
|||
if(!(this->state == IDLE || this->state == STARTING || this->state == RUNNING))
|
||||
return false;
|
||||
|
||||
this->setState(CANCELLED);
|
||||
this->stop(CancelStop, -1);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -119,6 +125,11 @@ bool ScStwTimer::stop(StopReason reason, double timeOfStop) {
|
|||
this->setState(FAILED);
|
||||
break;
|
||||
}
|
||||
case CancelStop: {
|
||||
qDebug() << "[INFO][TIMER] Timer was cancelled";
|
||||
this->setState(CANCELLED);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
|
@ -201,6 +212,9 @@ void ScStwTimer::setState(TimerState newState){
|
|||
if(this->state == DISABLED && newState != IDLE)
|
||||
return;
|
||||
|
||||
if(this->state == INCIDENT && newState != IDLE)
|
||||
return;
|
||||
|
||||
if(this->state != newState) {
|
||||
this->state = newState;
|
||||
qDebug() << "+ [INFO][TIMER] timer state changed: " << newState;
|
||||
|
|
Reference in a new issue