#include "headers/climbingrace.h" /* * manages: * - global state * - timers * - sounds * - next start action * - next start action delay progress * - settings (remote and local) */ ClimbingRace::ClimbingRace(QObject *parent) : QObject(parent) { this->state = ScStw::ScStw::IDLE; this->mode = LOCAL; this->appSettings = new AppSettings(this); this->scStwClient = new ScStwClient(); this->scStwClient->setIP(pGlobalAppSettings->loadSetting("baseStationIpAdress")); connect(this->scStwClient, &ScStwClient::stateChanged, this, &ClimbingRace::baseStationStateChanged); connect(this->scStwClient, &ScStwClient::stateChanged, this, &ClimbingRace::refreshMode); connect(this->scStwClient, &ScStwClient::gotSignal, this, &ClimbingRace::handleBaseStationSignal); connect(this, &ClimbingRace::baseStationStateChanged, this, &ClimbingRace::baseStationPropertiesChanged); this->speedTimers.append( new SpeedTimer(this) ); this->player = new QMediaPlayer; this->date = new QDateTime; this->nextStartActionTimer = new QTimer(this); nextStartActionTimer->setSingleShot(true); this->timerTextRefreshTimer = new QTimer(this); this->timerTextRefreshTimer->setInterval(1); this->timerTextRefreshTimer->setSingleShot(true); this->timerTextRefreshTimer->connect(this->timerTextRefreshTimer, &QTimer::timeout, this, &ClimbingRace::refreshTimerText); this->refreshTimerText(); } // -------------------------- // --- Main Functionality --- // -------------------------- int ClimbingRace::startRace() { if(this->state != ScStw::IDLE) { return 904; } qDebug() << "+ --- starting race"; int returnCode = 900; switch (this->mode) { case LOCAL: { this->setState(ScStw::STARTING); this->nextStartAction = ScStw::None; this->playSoundsAndStartRace(); returnCode = 200; break; } case REMOTE: { QVariantMap reply = this->scStwClient->sendCommand(1000); if(reply["status"] != 200){ //handle Error!! returnCode = reply["status"].toInt(); } else { returnCode = 200; } break; } } return returnCode; } int ClimbingRace::stopRace(int type) { if(this->state != ScStw::RUNNING && this->state != ScStw::STARTING) { return 904; } // type can be: // 0: stopp // 1: cancel // 2: fail (fase start) qDebug() << "+ --- stopping race"; int returnCode = 900; switch (this->mode) { case LOCAL: { if(type == 1){ this->nextStartActionTimer->stop(); this->player->stop(); this->nextStartAction = ScStw::None; } returnCode = this->speedTimers[0]->stop(type) ? 200:904; if(returnCode == 200) { this->setState(ScStw::STOPPED); } break; } case REMOTE: { QVariantMap reply = this->scStwClient->sendCommand(1001); if(reply["status"] != 200){ returnCode = reply["status"].toInt(); } else { returnCode = 200; } break; } } return returnCode; } int ClimbingRace::resetRace() { if(this->state != ScStw::STOPPED) { return 904; } qDebug() << "+ --- resetting race"; int returnCode = 900; switch (this->mode) { case LOCAL: { returnCode = this->speedTimers[0]->reset() ? 200:904; if(returnCode == 200){ this->setState(ScStw::IDLE); } break; } case REMOTE: { QVariantMap reply = this->scStwClient->sendCommand(1002); if(reply["status"] != 200){ //handle Error!! returnCode = reply["status"].toInt(); } else { returnCode = 200; } break; } } return returnCode; } // ------------------------- // --- Base Station sync --- // ------------------------- /** * @brief ClimbingRace::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 ClimbingRace::handleBaseStationSignal(ScStw::SignalKey key, QVariant data) { qDebug() << "got signal: " << data; switch (key) { case ScStw::RaceStateChanged: { // the remote race state has changed this->setState( ScStw::RaceState( data.toInt() ) ); break; } case ScStw::TimersChanged: { // the remote timers have changed this->refreshRemoteTimers(data.toList()); break; } case ScStw::NextStartActionChanged: { // the next start action has changed this->nextStartActionTotalDelay = data.toMap()["nextActionDelay"].toDouble(); this->nextStartActionDelayStartedAt = this->date->currentMSecsSinceEpoch() - (this->nextStartActionTotalDelay * data.toMap()["nextActionDelayProg"].toDouble()); this->nextStartAction = ScStw::NextStartAction( data.toMap()["nextAction"].toInt() ); emit this->nextStartActionChanged(); break; } case ScStw::ExtensionsChanged: { emit this->baseStationConnectionsChanged(); break; } case ScStw::InvalidSignal: return; } } bool ClimbingRace::refreshRemoteTimers(QVariantList timers) { if(timers.length() != speedTimers.length()){ // local timers are out of sync // delete all current timers foreach(SpeedTimer * locTimer, this->speedTimers){ delete locTimer; } speedTimers.clear(); foreach(QVariant remTimer, timers){ // create a local timer for each remote timer this->speedTimers.append(new SpeedTimer(this)); } } foreach(QVariant remTimer, timers){ int currId = remTimer.toMap()["id"].toInt(); speedTimers[currId]->startTime = this->date->currentMSecsSinceEpoch() - remTimer.toMap()["currentTime"].toDouble(); speedTimers[currId]->stoppedTime = remTimer.toMap()["currentTime"].toDouble(); speedTimers[currId]->reactionTime = remTimer.toMap()["reactionTime"].toDouble(); speedTimers[currId]->setState(SpeedTimer::timerState(remTimer.toMap()["state"].toInt())); } return true; } // ------------------------ // --- helper functions --- // ------------------------ void ClimbingRace::playSoundsAndStartRace() { qDebug() << "next Action: " << nextStartAction; nextStartActionTimer->disconnect(nextStartActionTimer, SIGNAL(timeout()), this, SLOT(playSoundsAndStartRace())); switch (this->nextStartAction) { case ScStw::AtYourMarks: { if(!playSound("qrc:/sounds/at_marks_1.wav")){ return; } if(appSettings->loadSetting("ready_en") == "true"){ nextStartAction = ScStw::Ready; nextStartActionTimer->setInterval(appSettings->loadSetting("ready_delay").toInt() <= 0 ? 1:appSettings->loadSetting("ready_delay").toInt()); } else{ nextStartAction = ScStw::Start; nextStartActionTimer->setInterval(1); } break; } case ScStw::Ready: { if(!playSound("qrc:/sounds/ready_1.wav")){ return; } nextStartAction = ScStw::Start; nextStartActionTimer->setInterval(1); break; } case ScStw::Start: { if(!playSound("qrc:/sounds/IFSC_STARTSIGNAL_SINE.wav")){ return; } nextStartAction = ScStw::None; nextStartActionTimer->disconnect(nextStartActionTimer, SIGNAL(timeout()), this, SLOT(playSoundsAndStartRace())); this->setState(ScStw::RUNNING); speedTimers[0]->start(); emit this->nextStartActionChanged(); return; } case ScStw::None: { this->speedTimers[0]->setState(SpeedTimer::STARTING); if(appSettings->loadSetting("at_marks_en") == "true"){ nextStartAction = ScStw::AtYourMarks; nextStartActionTimer->setInterval(appSettings->loadSetting("at_marks_delay").toInt() <= 0 ? 1:appSettings->loadSetting("at_marks_delay").toInt()); } else if(appSettings->loadSetting("ready_en") == "true"){ nextStartAction = ScStw::Ready; nextStartActionTimer->setInterval(appSettings->loadSetting("ready_delay").toInt() <= 0 ? 1:appSettings->loadSetting("ready_delay").toInt()); } else{ nextStartAction = ScStw::Start; nextStartActionTimer->setInterval(1); } break; } } emit this->nextStartActionChanged(); nextStartActionTimer->connect(nextStartActionTimer, SIGNAL(timeout()), this, SLOT(playSoundsAndStartRace())); nextStartActionTimer->start(); } bool ClimbingRace::playSound(QString path) { player->setMedia(QUrl(path)); player->setVolume(50); player->play(); QTimer timer; timer.setInterval(1); timer.setSingleShot(true); QEventLoop loop; loop.connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); while (player->mediaStatus() == QMediaPlayer::LoadingMedia || player->mediaStatus() == QMediaPlayer::BufferingMedia || player->mediaStatus() == QMediaPlayer::BufferedMedia) { timer.start(); loop.exec(); } if(player->mediaStatus() == QMediaPlayer::EndOfMedia){ return true; } else { return false; } } void ClimbingRace::setState(ScStw::RaceState newState) { if(newState != this->state) { this->state = newState; this->stateChanged(newState); } } void ClimbingRace::refreshMode() { RaceMode newMode; if(this->scStwClient->getState() == ScStwClient::CONNECTED){ newMode = REMOTE; } else { newMode = LOCAL; } if(this->mode != newMode){ if(newMode == LOCAL){ // if the new mode is local -> connection to base station has been lost // reset race // reset state this->setState(ScStw::IDLE); // reset timers // go back to one timer for (int i = 0;ispeedTimers.length();i++) { delete this->speedTimers[i]; } this->speedTimers.clear(); this->speedTimers.append(new SpeedTimer); } this->mode = newMode; emit this->modeChanged(); } } void ClimbingRace::refreshTimerText() { // --- refresh timer text --- QVariantList newTimerTextList; foreach(SpeedTimer * timer, this->speedTimers){ QVariantMap timerMap = {{"text",timer->getText()}, {"reacttime", timer->reactionTime}, {"state", timer->getState()}, {"id", this->speedTimers.indexOf(timer)}}; newTimerTextList.append(timerMap); } if(newTimerTextList != this->qmlTimers){ this->qmlTimers = newTimerTextList; emit timerTextChanged(); } // --- refresh next start action delay progress --- double nextStartActionRemainingDelay = 0; switch (this->mode) { case LOCAL: { // get remaining and total next start action delay time if(nextStartAction == 0){ this->nextStartActionTotalDelay = appSettings->loadSetting("at_marks_delay").toDouble(); } else if (nextStartAction == 1) { this->nextStartActionTotalDelay = appSettings->loadSetting("ready_delay").toDouble(); } nextStartActionRemainingDelay = this->nextStartActionTimer->remainingTime(); break; } case REMOTE: { // calculate remaining next start action delay time nextStartActionRemainingDelay = this->nextStartActionTotalDelay - ( this->date->currentMSecsSinceEpoch() - this->nextStartActionDelayStartedAt ); break; } } // calculate next start action delay progress if(nextStartActionRemainingDelay > 0){ this->nextStartActionDelayProgress = nextStartActionRemainingDelay / this->nextStartActionTotalDelay; emit this->nextStartActionDelayProgressChanged(); } else { this->nextStartActionDelayProgress = 0; emit this->nextStartActionDelayProgressChanged(); } /*if (this->mode == REMOTE && this->state == ScStw::IDLE) { this->nextStartActionDelayProgress = 0; emit this->nextStartActionDelayProgressChanged(); }*/ this->timerTextRefreshTimer->start(); } bool ClimbingRace::pairConnectedUsbExtensions() { QVariantMap ret = this->scStwClient->sendCommand(5002, "", 10000); qDebug() << ret; return ret["status"] == 200; } // - athlete management - QVariant ClimbingRace::getAthletes() { QVariantMap reply = this->scStwClient->sendCommand(4003); if(reply["status"] != 200){ //handle Error!! qDebug() << "+ --- error getting athletes: " << reply["status"]; return false; } QVariantMap tmpAthletes = reply["data"].toMap(); //qDebug() << tmpAthletes; return tmpAthletes; } bool ClimbingRace::createAthlete(QString userName, QString fullName) { QVariant requestData = QVariantMap({{"fullName", fullName}, {"userName", userName}}); QVariantMap reply = this->scStwClient->sendCommand(4001, requestData.toJsonValue()); if(reply["status"] != 200){ //handle Error!! qDebug() << "+ --- error creating athlete: " << reply["status"]; return false; } return true; } bool ClimbingRace::deleteAthlete( QString userName ){ QVariant requestData = QVariantMap({{"userName", userName}}); QVariantMap reply = this->scStwClient->sendCommand(4002, requestData.toJsonValue()); if(reply["status"] != 200){ //handle Error!! qDebug() << "+ --- error deleting athlete: " << reply["status"]; return false; } return true; } bool ClimbingRace::selectAthlete( QString userName, int timerId ){ QVariant requestData = QVariantMap({{"userName", userName}, {"timerId", timerId}}); QVariantMap reply = this->scStwClient->sendCommand(4000, requestData.toJsonValue()); if(reply["status"] != 200){ //handle Error!! qDebug() << "+ --- error selecting athlete: " << reply["status"]; return false; } return true; } QVariant ClimbingRace::getResults( QString userName ){ QVariantMap reply = this->scStwClient->sendCommand(4004, userName); if(reply["status"] != 200){ //handle Error!! qDebug() << "+ --- error getting results: " << reply["status"]; return false; } QVariantList tmpAthletes = reply["data"].toList(); //qDebug() << tmpAthletes; return tmpAthletes; } // ------------------------- // --- functions for qml --- // ------------------------- int ClimbingRace::getState() { return this->state; } int ClimbingRace::getMode() { return this->mode; } QVariant ClimbingRace::getTimerTextList() { return this->qmlTimers; } double ClimbingRace::getNextStartActionDelayProgress() { return this->nextStartActionDelayProgress; } int ClimbingRace::getNextStartAction() { return this->nextStartAction; } void ClimbingRace::writeSetting(QString key, QVariant value) { if(this->mode == REMOTE && ScStw::baseStationSettingFromString(key) != ScStw::InvalidSetting ){ this->scStwClient->writeRemoteSetting(ScStw::baseStationSettingFromString(key), value.toString()); } else { this->appSettings->writeSetting(key, value); } } void ClimbingRace::writeSetting(ScStw::BaseStationSetting key, QVariant value) { if(ScStw::baseStationSettingToString(key) != "Invalid" ){ this->writeSetting(ScStw::baseStationSettingToString(key), value); } } QString ClimbingRace::readSetting(QString key) { if(this->mode == REMOTE && ScStw::baseStationSettingFromString(key) != ScStw::InvalidSetting){ return this->scStwClient->readRemoteSetting(ScStw::baseStationSettingFromString(key)); } else { return this->appSettings->loadSetting(key); } } QString ClimbingRace::readSetting(ScStw::BaseStationSetting key) { if(ScStw::baseStationSettingToString(key) != "Invalid") { return this->readSetting(ScStw::baseStationSettingToString(key)); } return "false"; } void ClimbingRace::connectBaseStation() { this->reloadBaseStationIpAdress(); this->scStwClient->connectToHost(); } void ClimbingRace::disconnectBaseStation() { this->scStwClient->closeConnection(); } QString ClimbingRace::getBaseStationState() { switch (this->scStwClient->getState()) { case ScStwClient::CONNECTED: return "connected"; case ScStwClient::CONNECTING: return "connecting"; case ScStwClient::DISCONNECTED: return "disconnected"; case ScStwClient::INITIALISING: return "initialising"; } return ""; } QVariant ClimbingRace::getBaseStationConnections() { return scStwClient->getConnections(); } QVariantMap ClimbingRace::getBaseStationProperties() { QVariantMap firmware = {{"version", this->scStwClient->getFirmwareVersion()}, {"upToDate", this->scStwClient->isFirmwareUpToDate()}}; return {{"firmware", firmware}, {"timeOffset", this->scStwClient->getTimeOffset()}}; } bool ClimbingRace::updateBasestationFirmware() { return this->scStwClient->updateFirmware(); } bool ClimbingRace::updateBasestationTime() { return this->scStwClient->updateTime(); } bool ClimbingRace::reloadBaseStationIpAdress() { if(this->scStwClient->getState() == ScStwClient::DISCONNECTED){ this->scStwClient->setIP(pGlobalAppSettings->loadSetting("baseStationIpAdress")); return true; } return false; }