#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 = IDLE; this->mode = LOCAL; this->appSettings = new AppSettings(this); this->baseConn = new BaseConn(this); this->baseConn->setIP(pGlobalAppSettings->loadSetting("baseStationIpAdress")); connect(this->baseConn, &BaseConn::stateChanged, this, &ClimbingRace::baseStationStateChanged); connect(this->baseConn, &BaseConn::stateChanged, this, &ClimbingRace::refreshMode); connect(this->baseConn, &BaseConn::connectionsChanged, this, &ClimbingRace::baseStationConnectionsChanged); connect(this->baseConn, &BaseConn::gotUpdate, this, &ClimbingRace::handleBaseStationUpdate); connect(this->baseConn, &BaseConn::propertiesChanged, 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 != IDLE) { return 904; } qDebug() << "+ --- starting race"; int returnCode = 900; switch (this->mode) { case LOCAL: { this->setState(STARTING); this->nextStartAction = None; this->playSoundsAndStartRace(); returnCode = 200; break; } case REMOTE: { QVariantMap reply = this->baseConn->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 != RUNNING && this->state != 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 = None; } returnCode = this->speedTimers[0]->stop(type) ? 200:904; if(returnCode == 200) { this->setState(STOPPED); } break; } case REMOTE: { QVariantMap reply = this->baseConn->sendCommand(1001); if(reply["status"] != 200){ returnCode = reply["status"].toInt(); } else { returnCode = 200; } break; } } return returnCode; } int ClimbingRace::resetRace() { if(this->state != 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(IDLE); } break; } case REMOTE: { QVariantMap reply = this->baseConn->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 a update, sent by the base station, which indicates * that some remote value (like a state) has changed * * @param data */ void ClimbingRace::handleBaseStationUpdate(QVariant data) { //qDebug() << "got update: " << data; int header = data.toMap()["header"].toInt(); switch (header) { case 9000: { // the remote race state has changed this->setState( raceState( data.toMap()["data"].toInt() ) ); break; } case 9001: { // the remote timers have changed this->refreshRemoteTimers(data.toMap()["data"].toList()); break; } case 9002: { // the extension connections have changed this->baseConn->setConnections(data.toMap()["data"].toList()); break; } case 9003: { // the next start action has changed this->nextStartActionTotalDelay = data.toMap()["data"].toMap()["nextActionDelay"].toDouble(); this->nextStartActionDelayStartedAt = this->date->currentMSecsSinceEpoch() - (this->nextStartActionTotalDelay * data.toMap()["data"].toMap()["nextActionDelayProg"].toDouble()); this->nextStartAction = NextStartAction( data.toMap()["data"].toMap()["nextAction"].toInt() ); emit this->nextStartActionChanged(); } } } 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()["currTime"].toDouble(); speedTimers[currId]->stoppedTime = remTimer.toMap()["currTime"].toDouble(); speedTimers[currId]->reactionTime = remTimer.toMap()["reactTime"].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 AtYourMarks: { if(!playSound("qrc:/sounds/at_marks_1.wav")){ return; } if(appSettings->loadSetting("ready_en") == "true"){ nextStartAction = Ready; nextStartActionTimer->setInterval(appSettings->loadSetting("ready_delay").toInt() <= 0 ? 1:appSettings->loadSetting("ready_delay").toInt()); } else{ nextStartAction = Start; nextStartActionTimer->setInterval(1); } break; } case Ready: { if(!playSound("qrc:/sounds/ready_1.wav")){ return; } nextStartAction = Start; nextStartActionTimer->setInterval(1); break; } case Start: { if(!playSound("qrc:/sounds/IFSC_STARTSIGNAL_SINE.wav")){ return; } nextStartAction = None; nextStartActionTimer->disconnect(nextStartActionTimer, SIGNAL(timeout()), this, SLOT(playSoundsAndStartRace())); this->setState(RUNNING); speedTimers[0]->start(); emit this->nextStartActionChanged(); return; } case None: { this->speedTimers[0]->setState(SpeedTimer::STARTING); if(appSettings->loadSetting("at_marks_en") == "true"){ nextStartAction = 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 = Ready; nextStartActionTimer->setInterval(appSettings->loadSetting("ready_delay").toInt() <= 0 ? 1:appSettings->loadSetting("ready_delay").toInt()); } else{ nextStartAction = 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(raceState newState) { if(newState != this->state) { this->state = newState; this->stateChanged(newState); } } void ClimbingRace::refreshMode() { raceMode newMode; if(this->baseConn->state == "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(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); // reset base conn // clear extensions this->baseConn->connections.clear(); } 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 == IDLE) { this->nextStartActionDelayProgress = 0; emit this->nextStartActionDelayProgressChanged(); }*/ this->timerTextRefreshTimer->start(); } bool ClimbingRace::pairConnectedUsbExtensions() { QVariantMap ret = this->baseConn->sendCommand(5002, "", 10000); qDebug() << ret; return ret["status"] == 200; } // - athlete management - QVariant ClimbingRace::getAthletes() { QVariantMap reply = this->baseConn->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->baseConn->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->baseConn->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->baseConn->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->baseConn->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) { this->refreshMode(); if(this->mode == REMOTE && ( this->remoteSettings.contains(key) || this->remoteOnlySettings.contains(key) ) ){ this->baseConn->writeRemoteSetting(key, value.toString()); } else if(!this->remoteOnlySettings.contains(key)){ this->appSettings->writeSetting(key, value); } } QString ClimbingRace::readSetting(QString key) { this->refreshMode(); if(this->mode == REMOTE && ( this->remoteSettings.contains(key) || this->remoteOnlySettings.contains(key) )){ QVariantMap reply = this->baseConn->sendCommand(3001, key); if(reply["status"] != 200){ return "false"; } return reply["data"].toString(); } else if(!this->remoteOnlySettings.contains(key)){ return this->appSettings->loadSetting(key); } else { return "false"; } } void ClimbingRace::connectBaseStation() { this->reloadBaseStationIpAdress(); this->baseConn->connectToHost(); } void ClimbingRace::disconnectBaseStation() { this->baseConn->closeConnection(); } QString ClimbingRace::getBaseStationState() { return this->baseConn->getState(); } QVariant ClimbingRace::getBaseStationConnections() { return baseConn->getConnections(); } QVariantMap ClimbingRace::getBaseStationProperties() { QVariantMap firmware = {{"version", this->baseConn->firmwareVersion}, {"upToDate", this->baseConn->firmwareUpToDate}}; return {{"firmware", firmware}, {"timeOffset", this->baseConn->timeOffset}}; } bool ClimbingRace::updateBasestationFirmware() { return this->baseConn->updateFirmware(); } bool ClimbingRace::updateBasestationTime() { return this->baseConn->updateTime(); } bool ClimbingRace::reloadBaseStationIpAdress() { if(this->baseConn->state == "disconnected"){ this->baseConn->setIP(pGlobalAppSettings->loadSetting("baseStationIpAdress")); return true; } return false; }