This repository has been archived on 2024-06-03. You can view files and clone it, but cannot push or open issues or pull requests.
app/ScStwSrc/sources/climbingrace.cpp

657 lines
17 KiB
C++
Raw Normal View History

#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();
2020-04-05 14:12:56 +02:00
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;
2019-03-07 22:31:23 +01:00
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:
{
2020-04-05 14:12:56 +02:00
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:
{
2020-04-05 14:12:56 +02:00
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:
{
2020-04-05 14:12:56 +02:00
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 ---
// -------------------------
2019-10-06 19:02:47 +02:00
/**
* @brief ClimbingRace::handleBaseStationUpdate
*
* Function to handle an update, sent by the base station, which indicates
2019-10-06 19:02:47 +02:00
* that some remote value (like a state) has changed
*
* @param data
*/
void ClimbingRace::handleBaseStationSignal(ScStw::SignalKey key, QVariant data) {
2020-04-06 22:54:28 +02:00
qDebug() << "got signal: " << data;
switch (key) {
case ScStw::RaceStateChanged:
2019-10-06 19:02:47 +02:00
{
// the remote race state has changed
2020-04-06 22:54:28 +02:00
this->setState( ScStw::RaceState( data.toInt() ) );
2019-10-06 19:02:47 +02:00
break;
}
case ScStw::TimersChanged:
2019-10-06 19:02:47 +02:00
{
// the remote timers have changed
2020-04-06 22:54:28 +02:00
this->refreshRemoteTimers(data.toList());
2019-10-06 19:02:47 +02:00
break;
}
case ScStw::NextStartActionChanged:
{
// the next start action has changed
2020-04-06 22:54:28 +02:00
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;
}
2020-04-06 22:54:28 +02:00
case ScStw::InvalidSignal:
return;
2019-10-06 19:02:47 +02:00
}
}
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();
2019-10-06 19:02:47 +02:00
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;i<this->speedTimers.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 {
2019-03-07 22:31:23 +01:00
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() {
2020-04-05 14:12:56 +02:00
QVariantMap ret = this->scStwClient->sendCommand(5002, "", 10000);
qDebug() << ret;
return ret["status"] == 200;
}
// - athlete management -
QVariant ClimbingRace::getAthletes() {
2020-04-05 14:12:56 +02:00
QVariantMap reply = this->scStwClient->sendCommand(4003);
if(reply["status"] != 200){
//handle Error!!
qDebug() << "+ --- error getting athletes: " << reply["status"];
return false;
}
2019-05-19 14:06:05 +02:00
QVariantMap tmpAthletes = reply["data"].toMap();
//qDebug() << tmpAthletes;
return tmpAthletes;
}
bool ClimbingRace::createAthlete(QString userName, QString fullName) {
QVariant requestData = QVariantMap({{"fullName", fullName}, {"userName", userName}});
2020-04-05 14:12:56 +02:00
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}});
2020-04-05 14:12:56 +02:00
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}});
2020-04-05 14:12:56 +02:00
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 ){
2020-04-05 14:12:56 +02:00
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) {
2020-04-06 22:54:28 +02:00
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) {
2020-04-06 22:54:28 +02:00
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";
}
2019-08-20 22:55:37 +02:00
void ClimbingRace::connectBaseStation() {
this->reloadBaseStationIpAdress();
2020-04-05 14:12:56 +02:00
this->scStwClient->connectToHost();
}
void ClimbingRace::disconnectBaseStation() {
2020-04-05 14:12:56 +02:00
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() {
2020-04-05 14:12:56 +02:00
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() {
2020-04-05 14:12:56 +02:00
return this->scStwClient->updateFirmware();
}
bool ClimbingRace::updateBasestationTime() {
2020-04-05 14:12:56 +02:00
return this->scStwClient->updateTime();
}
bool ClimbingRace::reloadBaseStationIpAdress() {
if(this->scStwClient->getState() == ScStwClient::DISCONNECTED){
2020-04-05 14:12:56 +02:00
this->scStwClient->setIP(pGlobalAppSettings->loadSetting("baseStationIpAdress"));
return true;
}
return false;
}