/**************************************************************************** ** 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(QObject* parent) : ScStwRemoteRace(nullptr, nullptr, parent) { } ScStwRemoteRace::ScStwRemoteRace(ScStwClient *scStwClient, ScStwSettings *settings, QObject *parent) : ScStwRace(settings, parent) { this->isReadyForNextState = true; this->remoteTimers = {}; this->localTimers = {}; this->setScStwClient(scStwClient); } // -------------------------- // --- Main Functionality --- // -------------------------- ScStw::StatusCode ScStwRemoteRace::start(bool asyncronous) { if(this->local()) return ScStwRace::start(asyncronous); 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->local()) return ScStwRace::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->local()) return ScStwRace::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->local()) return ScStwRace::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()); } ScStw::StatusCode ScStwRemoteRace::setTimerDisabled(int timerId, bool disabled) { if(this->state != IDLE && this->state != WAITING) return ScStw::CurrentStateNotVaildForOperationError; if(timerId < 0 || timerId - 1 > this->timers.length()) return ScStw::ItemNotFoundError; qDebug() << "+ --- setting timer " << timerId << " to disabled: " << disabled; QVariantMap requestData = { {"timerId", timerId}, {"disabled", disabled} }; QVariantMap reply = this->scStwClient->sendCommand(ScStw::SetTimerDisabledCommand, QJsonValue::fromVariant(requestData)); qDebug() << "+ --- timer disabled command returned: " << reply; return ScStw::StatusCode(reply["status"].toInt()); } // ------------------------- // --- Base Station sync --- // ------------------------- void ScStwRemoteRace::handleClientStateChange() { switch (this->scStwClient->getState()) { case ScStwClient::INITIALISING: // disconnect all local timers from race this->setTimers({}, false); // delete all obsolete remote timers for(ScStwRemoteTimer* oldRemoteTimer : this->remoteTimers) oldRemoteTimer->deleteLater(); this->remoteTimers.clear(); break; case ScStwClient::DISCONNECTED: this->remoteTimers.clear(); this->setTimers(this->localTimers, true); emit this->timersChanged(); emit this->detailsChanged(); emit this->currentStartDelayChanged(); this->competitionMode = false; this->setState(IDLE); break; default: break; } } void ScStwRemoteRace::setTimers(QList timers, bool deleteOldTimers) { // disconnect all signals of all current timers //qDebug() << "SETTING TIMERS"; foreach(ScStwTimer *existingTimer, this->timers) { disconnect(existingTimer, &ScStwTimer::stateChanged, this, &ScStwRace::handleTimerStateChange); disconnect(existingTimer, &ScStwTimer::stateChanged, this, &ScStwRace::timersChanged); disconnect(existingTimer, &ScStwTimer::wantsToBeDisabledChanged, this, &ScStwRace::handleTimerWantsToBeDisabledChange); disconnect(existingTimer, &ScStwTimer::reactionTimeChanged, this, &ScStwRace::timersChanged); disconnect(existingTimer, &ScStwTimer::readyStateChanged, this, &ScStwRace::handleTimerReadyStateChange); disconnect(existingTimer, &ScStwTimer::readyStateChanged, this, &ScStwRace::isReadyForNextStateChanged); if(deleteOldTimers) existingTimer->deleteLater(); } this->timers.clear(); for(ScStwTimer* timer : timers) { this->addTimer(timer); } } ScStwRemoteRace::RaceMode ScStwRemoteRace::getMode() { if(this->scStwClient == nullptr || this->scStwClient->getState() != ScStwClient::CONNECTED) return LOCAL; else return REMOTE; } bool ScStwRemoteRace::local() { return this->getMode() == LOCAL; } /** * @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) { switch (key) { case ScStw::RaceDetailsChanged: { this->refreshDetails(data.toMap()); break; } default: return; } } void ScStwRemoteRace::refreshDetails(QVariantMap details) { // the details of the race have changed: qDebug() << "RACE DETAILS: " << details; // competition mode if(this->competitionMode != details["competitionMode"].toBool()) { this->competitionMode = details["competitionMode"].toBool(); emit this->competitionModeChanged(); } // ready sound enabled if(this->readySoundEnabled != details["readySoundEnabled"].toBool()) { this->readySoundEnabled = details["readySoundEnabled"].toBool(); emit this->readySoundEnabledChanged(); } // current start delay this->currentStartTotalDelay = details["currentStartDelay"].toMap()["total"].toInt(); this->latestStartDelayProgress = details["currentStartDelay"].toMap()["progress"].toDouble(); this->currentStartDelayStartedAt = QDateTime::currentMSecsSinceEpoch() - (this->currentStartTotalDelay * this->latestStartDelayProgress); emit this->currentStartDelayChanged(); // timers this->refreshRemoteTimers(details["timers"].toList()); // state this->setState(ScStwRace::RaceState(details["state"].toInt())); // isReady if(this->state == WAITING) { this->isReadyForNextState = details["isReadyForNextState"].toBool(); emit this->isReadyForNextStateChanged(); } emit this->detailsChanged(); } void ScStwRemoteRace::rebuildRemoteTimers(QVariantList remoteTimers) { qDebug() << "REBUILDING TIMERS"; // delete all current timers foreach(ScStwTimer * oldTimer, this->timers){ oldTimer->deleteLater(); } this->remoteTimers.clear(); 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); } } bool ScStwRemoteRace::refreshRemoteTimers(QVariantList newRemoteTimers) { qDebug() << "REFRESHING TIMERS"; if(newRemoteTimers.length() != this->remoteTimers.length()){ // local timers are out of sync this->rebuildRemoteTimers(newRemoteTimers); qDebug() << "rebuilding remote timers"; } for(QVariant remoteTimer : newRemoteTimers){ int currId = remoteTimer.toMap()["id"].toInt(); if(this->remoteTimers.length() <= currId) this->rebuildRemoteTimers(newRemoteTimers); ScStwTimer::TimerState newState = ScStwTimer::TimerState(remoteTimer.toMap()["state"].toInt()); qDebug() << "refreshing timers: id: " << currId << " state: " << newState << " readyState: " << remoteTimer.toMap()["readyState"].toInt() << " currentTime: " << remoteTimer.toMap()["currentTime"].toDouble(); 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); } emit this->timersChanged(); return true; } bool ScStwRemoteRace::addTimer(ScStwTimer* timer) { if(this->local()) { this->localTimers.append(timer); return ScStwRace::addTimer(timer); } return false; } QVariantMap ScStwRemoteRace::getCurrentStartDelay() { if(this->local()) return ScStwRace::getCurrentStartDelay(); QVariantMap currentStartDelay = { {"total", -1.0}, {"progress", -1.0} }; if(this->currentStartTotalDelay == -1) { currentStartDelay["progress"] = this->latestStartDelayProgress; } 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; currentStartDelay["total"] = this->currentStartTotalDelay; if(elapsed < 0 || elapsed > currentStartDelay["total"].toDouble()) { elapsed = currentStartDelay["total"].toDouble(); } currentStartDelay["progress"] = elapsed / currentStartDelay["total"].toDouble(); } return currentStartDelay; } bool ScStwRemoteRace::getIsReadyForNextState() { if(this->local()) return ScStwRace::getIsReadyForNextState(); return this->isReadyForNextState; } bool ScStwRemoteRace::getReadySoundEnabled() { if(this->local()) return ScStwRace::getReadySoundEnabled(); return this->readySoundEnabled; } void ScStwRemoteRace::refreshCompetitionMode() { if(this->local()) return ScStwRace::refreshCompetitionMode(); } ScStwClient* ScStwRemoteRace::getScStwClient() { return this->scStwClient; } void ScStwRemoteRace::setScStwClient(ScStwClient* client) { if(client == this->scStwClient) return; this->scStwClient = client; if(this->scStwClient != nullptr) { this->scStwClient->addSignalSubscription(ScStw::RaceDetailsChanged); connect(this->scStwClient, &ScStwClient::stateChanged, this, &ScStwRemoteRace::handleClientStateChange); connect(this->scStwClient, &ScStwClient::gotSignal, this, &ScStwRemoteRace::handleBaseStationSignal); } }