/**************************************************************************** ** ScStw Libraries ** Copyright (C) 2020 Itsblue development ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** 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 General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . ****************************************************************************/ #include "scstwremoterace.h" ScStwRemoteRace::ScStwRemoteRace(ScStwClient *monitorClient, QObject *parent) : ScStwRace(parent) { this->currentlyWaitingForClimbers = false; this->isReadyForNextState = true; this->scStwClient = monitorClient; this->scStwClient->addSignalSubscription(ScStw::RaceDetailsChanged); connect(this->scStwClient, &ScStwClient::stateChanged, this, &ScStwRemoteRace::handleClientStateChanged); connect(this->scStwClient, &ScStwClient::gotSignal, this, &ScStwRemoteRace::handleBaseStationSignal); } // -------------------------- // --- Main Functionality --- // -------------------------- ScStw::StatusCode ScStwRemoteRace::start(bool) { return this->start(); } ScStw::StatusCode ScStwRemoteRace::start() { if(this->getState() != ScStwRace::IDLE && this->getState() != ScStwRace::WAITING) return ScStw::CurrentStateNotVaildForOperationError; qDebug() << "+ --- starting race"; QVariantMap reply = this->scStwClient->sendCommand(ScStw::StartRaceCommand); return ScStw::StatusCode(reply["status"].toInt()); } ScStw::StatusCode ScStwRemoteRace::cancel() { if(this->getState() != PREPAIRING && this->getState() != WAITING && this->getState() != STARTING && this->getState() != RUNNING) return ScStw::CurrentStateNotVaildForOperationError; qDebug() << "+ --- stopping race"; QVariantMap reply = this->scStwClient->sendCommand(ScStw::CancelRaceCommand); return ScStw::StatusCode(reply["status"].toInt()); } ScStw::StatusCode ScStwRemoteRace::stop() { if(this->getState() != ScStwRace::RUNNING && this->getState() != ScStwRace::STARTING) return ScStw::CurrentStateNotVaildForOperationError; qDebug() << "+ --- stopping race"; QVariantMap reply = this->scStwClient->sendCommand(ScStw::StopRaceCommand); return ScStw::StatusCode(reply["status"].toInt()); } ScStw::StatusCode ScStwRemoteRace::reset() { if(this->getState() != ScStwRace::STOPPED && this->getState() != ScStwRace::INCIDENT) return ScStw::CurrentStateNotVaildForOperationError; qDebug() << "+ --- resetting race"; QVariantMap reply = this->scStwClient->sendCommand(ScStw::ResetRaceCommand); return ScStw::StatusCode(reply["status"].toInt()); } // ------------------------- // --- Base Station sync --- // ------------------------- void ScStwRemoteRace::handleClientStateChanged() { // TODO switch (this->scStwClient->getState()) { case ScStwClient::CONNECTED: break; default: this->timers.clear(); this->setState(IDLE); break; } } /** * @brief ScStwAppBackend::handleBaseStationUpdate * * Function to handle an update, sent by the base station, which indicates * that some remote value (like a state) has changed * * @param data */ void ScStwRemoteRace::handleBaseStationSignal(ScStw::SignalKey key, QVariant data) { //qDebug() << "got signal: " << data; switch (key) { case ScStw::RaceDetailsChanged: { this->refreshDetails(data.toMap()); break; } default: return; } } void ScStwRemoteRace::refreshDetails(QVariantMap details) { // the details of the race have changed: // state this->setState(ScStwRace::RaceState(details["state"].toInt())); // competition mode this->competitionMode = details["competitionMode"].toBool(); // ready sound enabled this->writeStartSoundSetting(ScStwSoundPlayer::Ready, details["readySoundEnabled"].toBool(), 0); // current start delay this->currentStartTotalDelay = details["currentStartDelay"].toList()[ScStwRace::CurrentStartStateTotalDelay].toInt(); this->currentStartDelayStartedAt = QDateTime::currentMSecsSinceEpoch() - (this->currentStartTotalDelay * details["currentStartDelay"].toList()[ScStwRace::CurrentStartStateDelayProgress].toDouble()); this->currentlyWaitingForClimbers = details["currentStartDelay"].toList()[ScStwRace::CurrentStartStateDelayProgress].toDouble() == 0 && this->currentStartTotalDelay == -1; emit this->currentStartDelayChanged(); // timers this->refreshRemoteTimers(details["timers"].toList()); // isReady if(this->state == WAITING) { this->isReadyForNextState = details["isReadyForNextState"].toBool(); emit this->isReadyForNextStateChanged(); } emit this->detailsChanged(); } bool ScStwRemoteRace::refreshRemoteTimers(QVariantList remoteTimers) { if(remoteTimers.length() != this->timers.length()){ // local timers are out of sync // delete all current timers foreach(ScStwTimer * locTimer, this->timers){ delete locTimer; } this->timers.clear(); foreach(QVariant remoteTimer, remoteTimers){ // create a local timer for each remote timer ScStwRemoteTimer * timer = new ScStwRemoteTimer(this); this->timers.append(timer); this->remoteTimers.append(timer); connect(timer, &ScStwTimer::stateChanged, this, &ScStwRace::timersChanged); connect(timer, &ScStwTimer::reactionTimeChanged, this, &ScStwRace::timersChanged); } } foreach(QVariant remoteTimer, remoteTimers){ int currId = remoteTimer.toMap()["id"].toInt(); ScStwTimer::TimerState newState = ScStwTimer::TimerState(remoteTimer.toMap()["state"].toInt()); qDebug() << "refreshing timers: id: " << currId << " state: " << newState << " readyState: " << remoteTimer.toMap()["readyState"].toInt(); double currentMSecsSinceEpoch = QDateTime::currentMSecsSinceEpoch(); this->remoteTimers[currId]->setStartTime(currentMSecsSinceEpoch - remoteTimer.toMap()["currentTime"].toDouble()); if(newState >= ScStwTimer::WAITING) this->remoteTimers[currId]->setStopTime(currentMSecsSinceEpoch); this->remoteTimers[currId]->setReactionTime(remoteTimer.toMap()["reactionTime"].toDouble()); this->remoteTimers[currId]->setLetter(remoteTimer.toMap()["letter"].toString()); this->remoteTimers[currId]->setReadyState(ScStwTimer::ReadyState(remoteTimer.toMap()["readyState"].toInt())); this->remoteTimers[currId]->setState(newState); } return true; } bool ScStwRemoteRace::addTimer(ScStwTimer* timer) { Q_UNUSED(timer) return false; } QVariantList ScStwRemoteRace::getCurrentStartDelay() { int nextActionDelay = -1; double nextActionDelayProg = -1; if(this->getState() == WAITING && this->currentlyWaitingForClimbers) { nextActionDelayProg = 0; } else if(this->getState() == PREPAIRING || this->getState() == WAITING) { // get the total delay and the delay progress of the next action timer double elapsed = QDateTime::currentMSecsSinceEpoch() - this->currentStartDelayStartedAt; nextActionDelay = this->currentStartTotalDelay; if(elapsed < 0 || elapsed > nextActionDelay) { elapsed = nextActionDelay; } nextActionDelayProg = elapsed / nextActionDelay; } return { nextActionDelay, nextActionDelayProg }; } bool ScStwRemoteRace::getIsReadyForNextState() { return this->isReadyForNextState; }