diff --git a/ScStwLibraries/headers/ScStw.hpp b/ScStwLibraries/headers/ScStw.hpp index 705b12d..47f6ba7 100644 --- a/ScStwLibraries/headers/ScStw.hpp +++ b/ScStwLibraries/headers/ScStw.hpp @@ -61,7 +61,7 @@ public: RaceStateChanged = 9000, TimersChanged = 9001, ExtensionsChanged = 9002, - NextStartActionChanged = 9003, /*, ProfilesChanged*/ + CurrentStartDelayChanged = 9003, /*, ProfilesChanged*/ SettingChanged = 9004 }; Q_ENUM(SignalKey) @@ -84,7 +84,7 @@ public: GetNextStartActionCommand = 2005, GetExtensionsCommand = 2006, GetTimersCommand = 2007, - GetNextStartActionDetailsCommand = 2009, + GetCurrentStartDelayCommand = 2009, WriteSettingCommand = 3000, ReadSettingCommand = 3001, @@ -160,8 +160,8 @@ public: */ enum ExtensionBatteryState { BatteryUnknown = -1, - BatteryFine = 1, - BatteryCritical = 0 + BatteryCritical = 0, + BatteryFine = 1 }; Q_ENUM(ExtensionBatteryState); diff --git a/ScStwLibraries/headers/client/scstwremotemonitorrace.h b/ScStwLibraries/headers/client/scstwremotemonitorrace.h index c36db54..281c53e 100644 --- a/ScStwLibraries/headers/client/scstwremotemonitorrace.h +++ b/ScStwLibraries/headers/client/scstwremotemonitorrace.h @@ -42,7 +42,7 @@ public slots: int stop(); int reset(); bool addTimer(ScStwTimer *timer); - QVariantList getNextStartActionDetails(); + QVariantList getCurrentStartDelay(); private slots: void handleClientStateChanged(); diff --git a/ScStwLibraries/headers/scstwrace.h b/ScStwLibraries/headers/scstwrace.h index 7f1b061..3f4cafb 100644 --- a/ScStwLibraries/headers/scstwrace.h +++ b/ScStwLibraries/headers/scstwrace.h @@ -51,7 +51,7 @@ class ScStwRace : public QObject Q_OBJECT Q_PROPERTY(RaceState state READ getState NOTIFY stateChanged) Q_PROPERTY(QVariantList timers READ getTimerDetailList NOTIFY timersChanged) - Q_PROPERTY(QVariantList nextStartActionDetails READ getNextStartActionDetails NOTIFY nextStartActionDetailsChanged) + Q_PROPERTY(QVariantList currentStartDelay READ getCurrentStartDelay NOTIFY currentStartDelayChanged) public: explicit ScStwRace(QObject *parent = nullptr); @@ -59,42 +59,43 @@ public: enum RaceState { IDLE, PREPAIRING, WAITING, STARTING, RUNNING, STOPPED, INCIDENT }; Q_ENUM(RaceState) - enum StartAction { None = -1, AtYourMarks = 0, Ready = 1, Start = 2 }; - Q_ENUM(StartAction) - - enum NextStartActionDetailAttributes { - NextStartAction = 0, - NextStartActionTotalDelay = 1, - NextStartActionDelayProgress = 2 + enum CurrentStartDetailAttributes { + CurrentStartStateTotalDelay = 0, + CurrentStartStateDelayProgress = 1 }; - Q_ENUM(NextStartActionDetailAttributes); + Q_ENUM(CurrentStartDetailAttributes); protected: - StartAction nextStartAction; QList timers; void setState(RaceState newState); private: RaceState state; - QTimer *nextActionTimer; - QEventLoop *nextActionLoop; - QEventLoop *climberReadyWaitLoop; + QTimer *startDelayTimer; + QEventLoop *startWaitLoop; // sounds ScStwSoundPlayer * soundPlayer; // some settings double soundVolume; - bool allowAutomaticTimerDisable; - bool allowAutomaticTimerDisableChanged; + bool competitionMode; + bool competitionModeChanged; /*! * \brief stores the start action settings * * \details Stores the settings for the action ScStwRace::AtYourMarks and ScStwRace::Ready. The settings keys are: "Enabled" and "Delay" */ - QMap startActionSettings; + QMap startSoundSettings; + + + enum LoopExitTypes { + LoopAutomaticExit = 0, + LoopManualExit = 2, + LoopCancelExit = 3 + }; public slots: @@ -122,15 +123,14 @@ public slots: virtual int cancel(); // setters - bool writeStartActionSetting(StartAction action, bool enabled, int delay); + bool writeStartSoundSetting(ScStwSoundPlayer::StartSound sound, bool enabled, int delay); bool setSoundVolume(double volume); virtual bool addTimer(ScStwTimer *timer); - void setAllowAutomaticTimerDisable(bool allow); + void setCompetitionMode(bool competitionMode); // getters RaceState getState(); - StartAction getNextStartAction(); - virtual QVariantList getNextStartActionDetails(); + virtual QVariantList getCurrentStartDelay(); QList getTimers(); QVariantList getTimerDetailList(); @@ -142,7 +142,7 @@ private slots: void handleTimerWantsToBeDisabledChange(ScStwTimer* timer, bool wantsToBeDisabled); int handleFalseStart(); bool playSoundsAndStartTimers(); - bool doDelayAndSoundOfStartAction(StartAction action, double *timeOfSoundPlaybackStart = nullptr); + bool doDelayAndSoundOfCurrentStartState(double *timeOfSoundPlaybackStart = nullptr); void enableAllTimers(); /** @@ -151,14 +151,14 @@ private slots: void handleTimerStop(); bool isStarting(); + bool isReadyForNextState(); signals: void startTimers(); void stopTimers(int type); void resetTimers(); void stateChanged(RaceState state); - void nextStartActionChanged(); - void nextStartActionDetailsChanged(); + void currentStartDelayChanged(); void timersChanged(); diff --git a/ScStwLibraries/headers/scstwsoundplayer.h b/ScStwLibraries/headers/scstwsoundplayer.h index d158725..561e995 100644 --- a/ScStwLibraries/headers/scstwsoundplayer.h +++ b/ScStwLibraries/headers/scstwsoundplayer.h @@ -41,6 +41,12 @@ public: */ explicit ScStwSoundPlayer(QObject *parent = nullptr); + enum StartSound { + Start, + Ready, + AtYourMarks + }; + private: /*! * \brief A map containing all sound files diff --git a/ScStwLibraries/sources/scstwrace.cpp b/ScStwLibraries/sources/scstwrace.cpp index 22af64d..1321918 100644 --- a/ScStwLibraries/sources/scstwrace.cpp +++ b/ScStwLibraries/sources/scstwrace.cpp @@ -26,22 +26,18 @@ ScStwRace::ScStwRace(QObject *parent) : QObject(parent) this->soundPlayer = new ScStwSoundPlayer(); // configure timer that handles the delay between the start commands - this->nextActionTimer = new QTimer(this); - nextActionTimer->setSingleShot(true); - this->nextActionLoop = new QEventLoop(this); - this->nextStartAction = None; - this->climberReadyWaitLoop = new QEventLoop(this); + this->startDelayTimer = new QTimer(this); + startDelayTimer->setSingleShot(true); + this->startWaitLoop = 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); + connect(this->startDelayTimer, &QTimer::timeout, this->startWaitLoop, &QEventLoop::quit); // write default settings - this->startActionSettings.insert(Start, {{"Enabled", true}, {"Delay", 1}}); - this->writeStartActionSetting(AtYourMarks, false, 0); - this->writeStartActionSetting(Ready, false, 0); + this->startSoundSettings.insert(ScStwSoundPlayer::Start, {{"Enabled", true}, {"Delay", 1}}); + this->writeStartSoundSetting(ScStwSoundPlayer::AtYourMarks, false, 0); + this->writeStartSoundSetting(ScStwSoundPlayer::Ready, false, 0); this->setSoundVolume(1.0); - this->allowAutomaticTimerDisable = false; + this->competitionMode = false; } // -------------------------- @@ -49,40 +45,22 @@ ScStwRace::ScStwRace(QObject *parent) : QObject(parent) // -------------------------- int ScStwRace::start(bool asyncronous) { - if(this->state != IDLE) { + if(this->state == WAITING) { + if(this->isReadyForNextState()) { + this->startWaitLoop->exit(LoopManualExit); + return ScStw::Success; + } + else { + return ScStw::TimersNotReadyError; + } + } + else if(this->state != IDLE) { return ScStw::CurrentStateNotVaildForOperationError; } qDebug() << "[INFO][RACE] checking timers"; - foreach (ScStwTimer *timer, this->timers) { - if(timer->getState() == ScStwTimer::DISABLED) - continue; - - if(timer->getReadyState() == ScStwTimer::ExtensionIsNotConnected || timer->getReadyState() == ScStwTimer::ExtensionBatteryNotFine) { - - if(this->allowAutomaticTimerDisable) { - timer->setDisabled(true); - continue; - } - - qDebug() << "Timer ready state is: " << timer->getReadyState(); - - timer->technicalIncident(); - - foreach (ScStwTimer *subTimer, this->timers) { - if(timer != subTimer && (timer->getReadyState() == ScStwTimer::ExtensionIsNotConnected || timer->getReadyState() == ScStwTimer::ExtensionBatteryNotFine)) - subTimer->technicalIncident(); - else if(timer != subTimer) - subTimer->setState(ScStwTimer::CANCELLED); - } - - this->setState(INCIDENT); - - qDebug() << "[ERROR][RACE] Could not start due to not-ready timers"; - - return ScStw::TimersNotReadyError; - } - } + if(!this->isReadyForNextState()) + return ScStw::TimersNotReadyError; qDebug() << "[INFO][RACE] starting race"; this->setState(PREPAIRING); @@ -199,11 +177,9 @@ int ScStwRace::cancel() { this->setState(STOPPED); + this->startWaitLoop->exit(LoopCancelExit); this->soundPlayer->cancel(this->soundVolume); - this->nextActionTimer->stop(); - this->nextActionLoop->quit(); - this->climberReadyWaitLoop->quit(); - this->nextStartAction = None; + this->startDelayTimer->stop(); return returnCode; } @@ -230,9 +206,8 @@ bool ScStwRace::playSoundsAndStartTimers() { return true; // The check if all timers are ready has already happened at this point - if(!this->doDelayAndSoundOfStartAction(AtYourMarks)) { + if(!this->doDelayAndSoundOfCurrentStartState()) return false; - } // check if the start was cancelled if(!this->isStarting()) @@ -241,65 +216,84 @@ bool ScStwRace::playSoundsAndStartTimers() { this->setState(WAITING); // do climber readiness tests - this->nextStartAction = Ready; - emit this->nextStartActionChanged(); + + // wait until both climbers are ready // if the automatic ready tone is enabled, wait for the climbers to become ready - if(this->startActionSettings.contains(Ready) && this->startActionSettings[Ready]["Enabled"].toBool()) { + if(this->startSoundSettings.contains(ScStwSoundPlayer::Ready) && this->startSoundSettings[ScStwSoundPlayer::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(); + if(this->startSoundSettings[ScStwSoundPlayer::Ready]["Delay"].toInt() > 1000) + minimumReadyDelay = this->startSoundSettings[ScStwSoundPlayer::Ready]["Delay"].toInt(); + + this->startDelayTimer->setInterval(minimumReadyDelay); // 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; + bool timerTriggered = true; + do { + + if(!this->isReadyForNextState()) { + this->startDelayTimer->stop(); + timerTriggered = false; + } + else if(this->startDelayTimer->isActive()) { + timerTriggered = true; + } + else { + this->startDelayTimer->stop(); + this->startDelayTimer->start(); } - if(!allClimbersReady) - this->climberReadyWaitLoop->exec(); + emit this->currentStartDelayChanged(); - // check if the start was cancelled - if(!this->isStarting()) + int loopExitCode = this->startWaitLoop->exec(); + + switch (loopExitCode) { + case LoopAutomaticExit: + break; + case LoopManualExit: + // prevent manual stop + timerTriggered = false; + break; + case LoopCancelExit: return false; - } + } + + } while(this->startDelayTimer->remainingTime() > 0 || !timerTriggered || !this->isReadyForNextState()); 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(); - - // check if the start was cancelled - if(!this->isStarting()) - return false; - - } while(this->nextActionTimer->remainingTime() > 0); qDebug() << "[RACE][DEBUG] Wait finished, starting now!"; // play ready tone - if(!this->soundPlayer->play(Ready, this->soundVolume)) + if(!this->soundPlayer->play(ScStwSoundPlayer::Ready, this->soundVolume)) return false; } + else { + // wait for climbers and manual start + int loopExitCode; + do { + loopExitCode = this->startWaitLoop->exec(); + + if(loopExitCode == LoopCancelExit) + return false; + + } while(loopExitCode != LoopManualExit || !this->isReadyForNextState()); + } // enter starting state this->setState(STARTING); // play start tone double timeOfSoundPlaybackStart; - this->doDelayAndSoundOfStartAction(Start, &timeOfSoundPlaybackStart); + this->doDelayAndSoundOfCurrentStartState(&timeOfSoundPlaybackStart); // perform start @@ -324,9 +318,6 @@ bool ScStwRace::playSoundsAndStartTimers() { if(!this->isStarting()) return true; - nextStartAction = None; - emit this->nextStartActionChanged(); - this->setState(RUNNING); return true; @@ -334,32 +325,48 @@ bool ScStwRace::playSoundsAndStartTimers() { } -bool ScStwRace::doDelayAndSoundOfStartAction(ScStwRace::StartAction action, double *timeOfSoundPlaybackStart) { - if(!this->isStarting()) +bool ScStwRace::doDelayAndSoundOfCurrentStartState(double *timeOfSoundPlaybackStart) { + ScStwSoundPlayer::StartSound sound; + + switch (this->state) { + case PREPAIRING: + sound = ScStwSoundPlayer::AtYourMarks; + break; + case WAITING: + sound = ScStwSoundPlayer::Ready; + break; + case STARTING: + sound = ScStwSoundPlayer::Start; + break; + default: return false; + } - if(this->startActionSettings.contains(action) && this->startActionSettings[action]["Enabled"].toBool()) { + if(this->startSoundSettings.contains(sound) && this->startSoundSettings[sound]["Enabled"].toBool()) { - this->nextStartAction = action; - emit this->nextStartActionChanged(); - if(action != Start && this->startActionSettings[action]["Delay"].toInt() > 0) { + if(sound != ScStwSoundPlayer::Start && this->startSoundSettings[sound]["Delay"].toInt() > 0) { // perform the delay before the start // get delay - int thisActionDelay = this->startActionSettings[action]["Delay"].toInt(); + int thisSoundDelay = this->startSoundSettings[sound]["Delay"].toInt(); // perform next action - if(thisActionDelay > 0) { - this->nextActionTimer->start(thisActionDelay); - this->nextActionLoop->exec(); + if(thisSoundDelay > 0) { + this->startDelayTimer->setInterval(thisSoundDelay); + this->startDelayTimer->start(); + + emit this->currentStartDelayChanged(); + + if(this->startWaitLoop->exec() == LoopCancelExit) + return false; } } if(!this->isStarting()) return false; - if(!this->soundPlayer->play(action, this->soundVolume, timeOfSoundPlaybackStart)) + if(!this->soundPlayer->play(sound, this->soundVolume, timeOfSoundPlaybackStart)) return false; } @@ -374,12 +381,12 @@ void ScStwRace::setState(RaceState newState) { if(this->state == IDLE) { // if we changed to IDLE -> handle timer enable / disable - if(this->allowAutomaticTimerDisableChanged && !this->allowAutomaticTimerDisable) { + if(this->competitionModeChanged && this->competitionMode) { this->enableAllTimers(); - this->allowAutomaticTimerDisableChanged = false; + this->competitionModeChanged = false; } - if(this->allowAutomaticTimerDisable) { + if(!this->competitionMode) { foreach(ScStwTimer* timer, this->timers) { if(timer->getWantsToBeDisabled() && timer->getState() != ScStwTimer::DISABLED) this->handleTimerWantsToBeDisabledChange(timer, timer->getWantsToBeDisabled()); @@ -418,12 +425,64 @@ void ScStwRace::refreshTimerStates() { // --- helper functions --- // ------------------------ +bool ScStwRace::isReadyForNextState() { + switch (this->state) { + case IDLE: { + foreach (ScStwTimer *timer, this->timers) { + if(timer->getState() == ScStwTimer::DISABLED) + continue; + + if(timer->getReadyState() == ScStwTimer::ExtensionIsNotConnected || timer->getReadyState() == ScStwTimer::ExtensionBatteryNotFine) { + + if(!this->competitionMode) { + timer->setDisabled(true); + continue; + } + + qDebug() << "Timer ready state is: " << timer->getReadyState(); + + timer->technicalIncident(); + + foreach (ScStwTimer *subTimer, this->timers) { + if(timer != subTimer && (timer->getReadyState() == ScStwTimer::ExtensionIsNotConnected || timer->getReadyState() == ScStwTimer::ExtensionBatteryNotFine)) + subTimer->technicalIncident(); + else if(timer != subTimer) + subTimer->setState(ScStwTimer::CANCELLED); + } + + this->setState(INCIDENT); + + qDebug() << "[ERROR][RACE] Could not start due to not-ready timers"; + + return false; + } + } + + break; + case WAITING: { + foreach (ScStwTimer *timer, this->timers) { + if(timer->getReadyState() != ScStwTimer::IsReady) + return false; + } + break; + } + + default: + break;; + } + + } + + return true; + +} + /** * @brief ScStwRace::handleTimerEnable function to enable timers at the right moment to prevent them from bricking the state machine * @param {ScStwExtensionControlledTimer*} timer timer to be enabled */ void ScStwRace::handleTimerWantsToBeDisabledChange(ScStwTimer* timer, bool wantsToBeDisabled) { - if(!this->allowAutomaticTimerDisable) + if(this->competitionMode) return; if(this->state == IDLE) { @@ -431,18 +490,17 @@ void ScStwRace::handleTimerWantsToBeDisabledChange(ScStwTimer* timer, bool wants } } - -void ScStwRace::setAllowAutomaticTimerDisable(bool allow) { - if(this->allowAutomaticTimerDisable == allow) +void ScStwRace::setCompetitionMode(bool competitionMode) { + if(this->competitionMode == competitionMode) return; - qDebug() << "Setting allow automatic timer disable to " << allow; + qDebug() << "Setting competition mode to " << competitionMode; - this->allowAutomaticTimerDisable = allow; + this->competitionMode = competitionMode; if(this->state != IDLE) - this->allowAutomaticTimerDisableChanged = true; - else if(!this->allowAutomaticTimerDisable) + this->competitionModeChanged = true; + else if(this->competitionMode) this->enableAllTimers(); } @@ -457,14 +515,14 @@ void ScStwRace::enableAllTimers() { } } -QVariantList ScStwRace::getNextStartActionDetails() { +QVariantList ScStwRace::getCurrentStartDelay() { int nextActionDelay = 0; double nextActionDelayProg = -1; - if(this->nextStartAction == AtYourMarks || this->nextStartAction == Ready) { + if(this->state == PREPAIRING || this->state == WAITING) { // get the total delay and the delay progress of the next action timer - double remaining = this->nextActionTimer->remainingTime(); - nextActionDelay = this->startActionSettings[this->nextStartAction]["Delay"].toInt(); + double remaining = this->startDelayTimer->remainingTime(); + nextActionDelay = this->startDelayTimer->interval();; if(remaining < 0) { remaining = nextActionDelay; } @@ -472,22 +530,21 @@ QVariantList ScStwRace::getNextStartActionDetails() { } return { - this->nextStartAction, nextActionDelay, nextActionDelayProg }; } -bool ScStwRace::writeStartActionSetting(StartAction action, bool enabled, int delay) { - if(action != AtYourMarks && action != Ready) +bool ScStwRace::writeStartSoundSetting(ScStwSoundPlayer::StartSound sound, bool enabled, int delay) { + if(sound != ScStwSoundPlayer::AtYourMarks && sound != ScStwSoundPlayer::Ready) return false; QVariantMap setting = {{"Enabled", enabled}, {"Delay", delay}}; - if(!this->startActionSettings.contains(action)) - this->startActionSettings.insert(action, setting); + if(!this->startSoundSettings.contains(sound)) + this->startSoundSettings.insert(sound, setting); else - this->startActionSettings[action] = setting; + this->startSoundSettings[sound] = setting; return true; } @@ -517,9 +574,9 @@ 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); + connect(timer, &ScStwTimer::readyStateChanged, this->startWaitLoop, &QEventLoop::quit); - if(!this->allowAutomaticTimerDisable && timer->getState() == ScStwTimer::DISABLED) + if(this->competitionMode && timer->getState() == ScStwTimer::DISABLED) timer->setDisabled(false); return true; @@ -530,11 +587,6 @@ ScStwRace::RaceState ScStwRace::getState() { return this->state; } -ScStwRace::StartAction ScStwRace::getNextStartAction() -{ - return this->nextStartAction; -} - QList ScStwRace::getTimers() { return this->timers; }