diff --git a/ScStwSrc/ScStwApp.pro b/ScStwSrc/ScStwApp.pro index 7521ca5..6975494 100644 --- a/ScStwSrc/ScStwApp.pro +++ b/ScStwSrc/ScStwApp.pro @@ -24,17 +24,19 @@ TARGET = speedclimbing_stw include($$PWD/../shared-libraries/ScStwLibraries/ScStwLibraries.pri) SOURCES += \ + sources/scstwappbackend.cpp \ sources/main.cpp \ sources/appsettings.cpp \ - sources/speedtimer.cpp \ - sources/climbingrace.cpp \ + #sources/speedtimer.cpp \ + #sources/climbingrace.cpp \ sources/apptheme.cpp HEADERS += \ headers/appsettings.h \ - headers/speedtimer.h \ - headers/climbingrace.h \ - headers/apptheme.h + #headers/speedtimer.h \ + #headers/climbingrace.h \ + headers/apptheme.h \ + headers/scstwappbackend.h RESOURCES += \ resources/shared/shared.qrc \ diff --git a/ScStwSrc/headers/climbingrace.h b/ScStwSrc/headers/climbingrace.h index f1725e8..0c2258c 100644 --- a/ScStwSrc/headers/climbingrace.h +++ b/ScStwSrc/headers/climbingrace.h @@ -44,9 +44,6 @@ private: QList speedTimers; - ScStw::NextStartAction nextStartAction; - ScStw::RaceState state; - double nextStartActionDelayProgress; // only used in remote mode: double nextStartActionDelayStartedAt; diff --git a/ScStwSrc/headers/scstwappbackend.h b/ScStwSrc/headers/scstwappbackend.h new file mode 100644 index 0000000..354196b --- /dev/null +++ b/ScStwSrc/headers/scstwappbackend.h @@ -0,0 +1,98 @@ +#ifndef SCSTWAPPBACKEND_H +#define SCSTWAPPBACKEND_H + +#include + +#include +#include +#include + +#include "headers/appsettings.h" + +class ScStwAppBackend : public QObject +{ + Q_OBJECT + + //Q_PROPERTY(int state READ getState NOTIFY stateChanged) + Q_PROPERTY(int mode READ getMode NOTIFY modeChanged) + Q_PROPERTY(QVariant timers READ getTimerTextList NOTIFY timerTextChanged) + //Q_PROPERTY(QString baseStationState READ getBaseStationState NOTIFY baseStationStateChanged) + Q_PROPERTY(ScStwRace* race READ getRace NOTIFY raceChanged) + Q_PROPERTY(QVariant baseStationConnections READ getBaseStationConnections NOTIFY baseStationConnectionsChanged) + Q_PROPERTY(double nextStartActionDelayProgress READ getNextStartActionDelayProgress NOTIFY nextStartActionDelayProgressChanged) + Q_PROPERTY(int nextStartAction READ getNextStartAction NOTIFY nextStartActionChanged) + Q_PROPERTY(QVariantMap baseStationProperties READ getBaseStationProperties NOTIFY baseStationPropertiesChanged) + +public: + explicit ScStwAppBackend(QObject *parent = nullptr); + + enum RaceMode { LOCAL, REMOTE }; + +private: + RaceMode mode; + + AppSettings * appSettings; + ScStwClient * scStwClient; + + QTimer * timerTextRefreshTimer; + // TODO: DOINEED? QTimer * nextStartActionTimer; + + ScStwRace * localRace; + +public slots: + // base station sync + //void handleBaseStationSignal(ScStw::SignalKey key, QVariant data); + Q_INVOKABLE bool pairConnectedUsbExtensions(); + + // functions for qml + Q_INVOKABLE ScStwRace *getRace(); + //Q_INVOKABLE int getState(); + Q_INVOKABLE int getMode(); + Q_INVOKABLE QVariant getTimerTextList(); + Q_INVOKABLE double getNextStartActionDelayProgress(); + Q_INVOKABLE int getNextStartAction(); + + Q_INVOKABLE void writeSetting(QString key, QVariant value); + Q_INVOKABLE void writeSetting(ScStw::BaseStationSetting key, QVariant value); + Q_INVOKABLE QString readSetting(QString key); + Q_INVOKABLE QString readSetting(ScStw::BaseStationSetting key); + + Q_INVOKABLE void connectBaseStation(); + Q_INVOKABLE void disconnectBaseStation(); + Q_INVOKABLE QString getBaseStationState(); + Q_INVOKABLE QVariant getBaseStationConnections(); + Q_INVOKABLE QVariantMap getBaseStationProperties(); + + Q_INVOKABLE bool updateBasestationFirmware(); + Q_INVOKABLE bool updateBasestationTime(); + + // athlete management + Q_INVOKABLE QVariant getAthletes(); + Q_INVOKABLE bool createAthlete( QString userName, QString fullName ); + Q_INVOKABLE bool deleteAthlete( QString userName ); + Q_INVOKABLE bool selectAthlete( QString userName, int timerId ); + Q_INVOKABLE QVariant getResults( QString userName ); + + Q_INVOKABLE bool reloadBaseStationIpAdress(); + + +private slots: + void refreshTimerText(); + void refreshMode(); + +signals: + void modeChanged(); + void raceChanged(); + + void nextStartActionChanged(); + void nextStartActionDelayProgressChanged(); + + void timerTextChanged(); + void baseStationStateChanged(); + void baseStationConnectionsChanged(); + void baseStationPropertiesChanged(); + + +}; + +#endif // SCSTWAPPBACKEND_H diff --git a/ScStwSrc/resources/qml/main.qml b/ScStwSrc/resources/qml/main.qml index 687abc8..a242435 100644 --- a/ScStwSrc/resources/qml/main.qml +++ b/ScStwSrc/resources/qml/main.qml @@ -57,7 +57,10 @@ Window { SpeedBackend { id: speedBackend + } + Connections { + target: speedBackend.race onStateChanged: { var stateString console.log("race state changed to: " + state) @@ -87,6 +90,7 @@ Window { } app.state = stateString } + } AppTheme { @@ -837,9 +841,9 @@ Window { /*----Functions to control the stopwatch----*/ function start(){ - var ret = speedBackend.startRace() - if(ret !== 200){ - console.log("+ --- error starting race: "+ret) + var ret = speedBackend.race.start() + if(!ret){ + console.log("+ --- error starting race!") } } @@ -847,8 +851,8 @@ Window { var ret = speedBackend.stopRace(type) - if(ret !== 200){ - console.log("+ --- error stopping race: "+ret) + if(!ret){ + console.log("+ --- error stopping race: ") } } diff --git a/ScStwSrc/sources/climbingrace.cpp b/ScStwSrc/sources/climbingrace.cpp index 6b6c522..cd16ef7 100644 --- a/ScStwSrc/sources/climbingrace.cpp +++ b/ScStwSrc/sources/climbingrace.cpp @@ -187,7 +187,7 @@ int ClimbingRace::resetRace() { /** * @brief ClimbingRace::handleBaseStationUpdate * - * Function to handle a update, sent by the base station, which indicates + * Function to handle an update, sent by the base station, which indicates * that some remote value (like a state) has changed * * @param data @@ -248,9 +248,9 @@ bool ClimbingRace::refreshRemoteTimers(QVariantList timers) { 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]->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())); } diff --git a/ScStwSrc/sources/main.cpp b/ScStwSrc/sources/main.cpp index 89170bc..c4d8345 100644 --- a/ScStwSrc/sources/main.cpp +++ b/ScStwSrc/sources/main.cpp @@ -50,9 +50,11 @@ #endif #include "headers/appsettings.h" -#include "headers/speedtimer.h" -#include "headers/climbingrace.h" +//#include "headers/speedtimer.h" +//#include "headers/climbingrace.h" #include "headers/apptheme.h" +#include "headers/scstwappbackend.h" +#include #include int main(int argc, char *argv[]) @@ -82,7 +84,8 @@ int main(int argc, char *argv[]) AppSettings * pAppSettings = new AppSettings(); // setup speed backend and App themes - qmlRegisterType("com.itsblue.speedclimbingstopwatch", 2, 0, "SpeedBackend"); + qmlRegisterType("com.itsblue.speedclimbingstopwatch", 2, 0, "SpeedBackend"); + qmlRegisterType("com.itsblue.speedclimbingstopwatch", 2, 0, "ScStwRace"); qmlRegisterType("com.itsblue.speedclimbingstopwatch", 2, 0, "AppTheme"); QQmlApplicationEngine engine; diff --git a/ScStwSrc/sources/scstwappbackend.cpp b/ScStwSrc/sources/scstwappbackend.cpp new file mode 100644 index 0000000..29f8cec --- /dev/null +++ b/ScStwSrc/sources/scstwappbackend.cpp @@ -0,0 +1,350 @@ +#include "../headers/scstwappbackend.h" + +ScStwAppBackend::ScStwAppBackend(QObject *parent) : QObject(parent) +{ + this->appSettings = new AppSettings(this); + this->scStwClient = new ScStwClient(); + this->localRace = new ScStwRace(this); + + this->scStwClient->setIP(pGlobalAppSettings->loadSetting("baseStationIpAdress")); + connect(this->scStwClient, &ScStwClient::stateChanged, this, &ScStwAppBackend::baseStationStateChanged); + connect(this->scStwClient, &ScStwClient::stateChanged, this, &ScStwAppBackend::refreshMode); + //connect(this->scStwClient, &ScStwClient::gotSignal, this, &ScStwAppBackend::handleBaseStationSignal); + connect(this, &ScStwAppBackend::baseStationStateChanged, this, &ScStwAppBackend::baseStationPropertiesChanged); + + this->localRace->addTimer(new ScStwTimer(this)); + + this->timerTextRefreshTimer = new QTimer(this); + this->timerTextRefreshTimer->setInterval(1); + this->timerTextRefreshTimer->setSingleShot(true); + this->timerTextRefreshTimer->connect(this->timerTextRefreshTimer, &QTimer::timeout, this, &ScStwAppBackend::refreshTimerText); + this->refreshTimerText(); +} + +// -------------------------- +// --- Main Functionality --- +// -------------------------- + + +// ------------------------- +// --- Base Station sync --- +// ------------------------- + +/** + * @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 ScStwAppBackend::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 ScStwAppBackend::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 ScStwAppBackend::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->getRace()->reset(); + } + + this->mode = newMode; + emit this->modeChanged(); + } + +} + +void ScStwAppBackend::refreshTimerText() { + + // --- refresh timer text --- + + QVariantList newTimerTextList; + + newTimerTextList = this->getRace()->getTimerDetailList(); + + // --- refresh next start action delay progress --- + double nextStartActionDelayProgress = 0; + + nextStartActionDelayProgress = this->getRace()->getNextStartActionDetails()["nextActionDelayProg"].toDouble(); + + if(nextStartActionDelayProgress < 1.0) + emit this->nextStartActionDelayProgressChanged(); + + this->timerTextRefreshTimer->start(); +} + +bool ScStwAppBackend::pairConnectedUsbExtensions() { + QVariantMap ret = this->scStwClient->sendCommand(5002, "", 10000); + qDebug() << ret; + return ret["status"] == 200; +} + +// - athlete management - + +QVariant ScStwAppBackend::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 ScStwAppBackend::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 ScStwAppBackend::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 ScStwAppBackend::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 ScStwAppBackend::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 --- +// ------------------------- + +ScStwRace* ScStwAppBackend::getRace() { + switch (this->mode) { + case LOCAL: + return this->localRace; + default: + return nullptr; + } +} + +int ScStwAppBackend::getMode() { + return this->mode; +} + +QVariant ScStwAppBackend::getTimerTextList() { + return this->getRace()->getTimerDetailList(); +} + +double ScStwAppBackend::getNextStartActionDelayProgress() { + return this->getRace()->getNextStartActionDetails()["nextActionDelayProg"].toDouble(); +} + +int ScStwAppBackend::getNextStartAction() { + return this->getRace()->getNextStartActionDetails()["nextAction"].toInt(); +} + +void ScStwAppBackend::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 ScStwAppBackend::writeSetting(ScStw::BaseStationSetting key, QVariant value) { + if(ScStw::baseStationSettingToString(key) != "Invalid" ){ + this->writeSetting(ScStw::baseStationSettingToString(key), value); + } +} + +QString ScStwAppBackend::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 ScStwAppBackend::readSetting(ScStw::BaseStationSetting key) { + if(ScStw::baseStationSettingToString(key) != "Invalid") { + return this->readSetting(ScStw::baseStationSettingToString(key)); + } + return "false"; +} + +void ScStwAppBackend::connectBaseStation() { + this->reloadBaseStationIpAdress(); + this->scStwClient->connectToHost(); +} + +void ScStwAppBackend::disconnectBaseStation() { + this->scStwClient->closeConnection(); +} + +QString ScStwAppBackend::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 ScStwAppBackend::getBaseStationConnections() { + return scStwClient->getConnections(); +} + +QVariantMap ScStwAppBackend::getBaseStationProperties() { + QVariantMap firmware = {{"version", this->scStwClient->getFirmwareVersion()}, {"upToDate", this->scStwClient->isFirmwareUpToDate()}}; + return {{"firmware", firmware}, {"timeOffset", this->scStwClient->getTimeOffset()}}; +} + +bool ScStwAppBackend::updateBasestationFirmware() { + return this->scStwClient->updateFirmware(); +} + +bool ScStwAppBackend::updateBasestationTime() { + return this->scStwClient->updateTime(); +} + +bool ScStwAppBackend::reloadBaseStationIpAdress() { + if(this->scStwClient->getState() == ScStwClient::DISCONNECTED){ + this->scStwClient->setIP(pGlobalAppSettings->loadSetting("baseStationIpAdress")); + return true; + } + return false; +} diff --git a/shared-libraries b/shared-libraries index b350f46..654b05d 160000 --- a/shared-libraries +++ b/shared-libraries @@ -1 +1 @@ -Subproject commit b350f4636a898e6364181023f7bb51e9390794e9 +Subproject commit 654b05dec9ab8f5940fe2c7d338c6fa50704c80f