- major cleanup

- added global static class ScStw
This commit is contained in:
Dorian Zedler 2020-04-06 17:51:20 +02:00
parent d32fe7733a
commit b752bdeed2
Signed by: dorian
GPG key ID: 989DE36109AFA354
6 changed files with 391 additions and 194 deletions

4
ScStwLibraries/ScStw.cpp Normal file
View file

@ -0,0 +1,4 @@
#include <ScStw.hpp>
const char *ScStw::SOCKET_MESSAGE_START_KEY = "<message>";
const char *ScStw::SOCKET_MESSAGE_END_KEY = "</message>";

53
ScStwLibraries/ScStw.hpp Normal file
View file

@ -0,0 +1,53 @@
#ifndef SCSTW_HPP
#define SCSTW_HPP
#include <QObject>
class ScStw : public QObject {
Q_OBJECT
public:
/**
* Some global enums
*/
enum RaceState { IDLE, STARTING, WAITING, RUNNING, STOPPED };
Q_ENUM(RaceState)
enum SignalKey {
RaceStateChanged = 9000,
TimersChanged = 9001,
ExtensionsChanged = 9002,
NextStartActionChanged = 9003 /*, ProfilesChanged*/
};
Q_ENUM(SignalKey)
enum NextStartAction { AtYourMarks, Ready, Start, None };
Q_ENUM(NextStartAction)
enum BaseStationSetting {
ReadySoundEnableSetting,
ReadySoundDelaySetting,
AtYourMarksSoundEnableSetting,
AtYourMarksSoundDelaySetting,
SoundVolumeSetting
};
Q_ENUM(BaseStationSetting)
enum ErrorCode {
Success = 200,
Error = 900,
NotConnectedError = 910,
TimeoutError = 911,
SettingNotAccessableError = 901
};
Q_ENUM(ErrorCode)
static const char* SOCKET_MESSAGE_START_KEY;
static const char* SOCKET_MESSAGE_END_KEY;
private:
ScStw() : QObject(nullptr) {};
};
#endif // SCSTW_HPP

View file

@ -18,9 +18,11 @@ DEFINES += QT_DEPRECATED_WARNINGS
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
ScStw.cpp \
scstwclient.cpp
HEADERS += \
ScStw.hpp \
ScStwLibraries_global.h \
scstwclient.h

View file

@ -9,13 +9,4 @@
# define SCSTWLIBRARIES_EXPORT Q_DECL_IMPORT
#endif
/**
* Some global enums
*
*/
enum raceState { IDLE, STARTING, WAITING, RUNNING, STOPPED };
enum signalKey { RaceStateChanged, TimersChanged, ExtensionsChanged, NextStartActionChanged /*, ProfilesChanged*/ };
enum nextStartAction { AtYourMarks, Ready, Start, None };
#endif // SCSTWCLIENT_GLOBAL_H

View file

@ -2,31 +2,36 @@
ScStwClient * pGlobalScStwClient = nullptr;
ScStwClient::ScStwClient(QObject *parent) : QObject(parent)
ScStwClient::ScStwClient() : QObject(nullptr)
{
pGlobalScStwClient = this;
this->state = DISCONNECTED;
this->nextConnectionId = 1;
this->connections = QVariantList({});
socket = new QTcpSocket(this);
this->socket = new QTcpSocket(this);
this->timeoutTimer = new QTimer(this);
this->timeoutTimer->setSingleShot(true);
this->state = "disconnected";
connect(this->timeoutTimer, &QTimer::timeout,
[=](){this->handleError(QAbstractSocket::ProxyConnectionTimeoutError);});
connect(this->socket, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(gotError(QAbstractSocket::SocketError)));
this, SLOT(handleError(QAbstractSocket::SocketError)));
connect(this->socket, &QAbstractSocket::stateChanged, this, &ScStwClient::socketStateChanged);
connect(this->socket, &QAbstractSocket::stateChanged,
this, &ScStwClient::handleSocketStateChange);
this->nextConnectionId = 1;
this->connections = QVariantList({});
connect(this->socket, &QTcpSocket::readyRead,
this, &ScStwClient::handleReadyRead);
pGlobalScStwClient = this;
}
void ScStwClient::connectToHost() {
qDebug() << "connecting";
setState("connecting");
connect(this->timeoutTimer, SIGNAL(timeout()), this, SLOT(connectionTimeout()));
setState(CONNECTING);
//connect
this->socket->connectToHost(this->ip, this->port);
@ -35,48 +40,52 @@ void ScStwClient::connectToHost() {
}
void ScStwClient::connectionTimeout() {
if(this->state != CONNECTING)
return;
this->socket->abort();
disconnect(this->timeoutTimer, SIGNAL(timeout()), this, SLOT(connectionTimeout()));
}
bool ScStwClient::init() {
disconnect(this->timeoutTimer, SIGNAL(timeout()), this, SLOT(connectionTimeout()));
if(this->state != CONNECTING)
return false;
this->setState(INITIALISING);
this->timeoutTimer->stop();
connect(this->socket, &QTcpSocket::readyRead, this, &ScStwClient::readyRead);
this->setState("connected");
// init remote session
QJsonArray updateSubs = {"onRaceStateChanged", "onTimersChanged", "onExtensionConnectionsChanged", "onNextStartActionChanged"};
QJsonArray updateSubs = {ScStw::RaceStateChanged, ScStw::TimersChanged, ScStw::ExtensionsChanged, ScStw::NextStartActionChanged};
QJsonObject sessionParams = {{"updateSubs", updateSubs}, {"init", true}, {"usingTerminationKeys", true}};
QVariantMap initResponse = this->sendCommand(1, sessionParams, false);
QVariantMap initResponse = this->sendCommand(1, sessionParams, 3000, false);
if(initResponse["status"] != 200) {
this->closeConnection();
return false;
}
this->firmwareVersion = initResponse["data"].toMap()["version"].toString();
this->timeOffset = initResponse["data"].toMap()["time"].toDouble() - this->date->currentMSecsSinceEpoch();
this->firmwareUpToDate = this->isFirmwareUpToDate();
emit this->propertiesChanged();
qDebug() << "[INFO][BaseStation] Init done! firmware: version: " << this->firmwareVersion << " up-to-date: " << this->firmwareUpToDate << " time offset: " << this->timeOffset;
qDebug() << "[INFO][BaseStation] Init done! firmware: version: " << this->firmwareVersion << " up-to-date: " << this->isFirmwareUpToDate() << " time offset: " << this->timeOffset;
this->setState(CONNECTED);
return true;
}
void ScStwClient::deInit() {
this->connections.clear();
emit this->connectionsChanged();
this->setState("disconnected");
if(this->state == DISCONNECTED)
return;
this->setConnections(QVariantList({}));
this->setState(DISCONNECTED);
}
void ScStwClient::closeConnection()
{
this->connections = QVariantList({});
emit this->connectionsChanged();
if(this->getState() == DISCONNECTED)
return;
qDebug() << "closing connection";
switch (socket->state())
@ -90,67 +99,22 @@ void ScStwClient::closeConnection()
default:
socket->abort();
}
setState("disconnected");
}
void ScStwClient::gotError(QAbstractSocket::SocketError err)
{
//qDebug() << "got error";
QString strError = "unknown";
switch (err)
{
case 0:
strError = "Connection was refused";
break;
case 1:
strError = "Remote host closed the connection";
this->closeConnection();
break;
case 2:
strError = "Host address was not found";
break;
case 5:
strError = "Connection timed out";
break;
default:
strError = "Unknown error";
}
emit gotError(strError);
qDebug() << "got socket error: " << strError;
}
// -------------------------------------
// --- socket communication handling ---
// -------------------------------------
void ScStwClient::socketStateChanged(QAbstractSocket::SocketState socketState) {
switch (socketState) {
case QAbstractSocket::UnconnectedState:
{
this->deInit();
break;
}
case QAbstractSocket::ConnectedState:
{
if(!this->init()) {
this->closeConnection();
QVariantMap ScStwClient::sendCommand(int header, QJsonValue data, int timeout) {
if(this->state != CONNECTED)
return {{"status", ScStw::NotConnectedError}, {"data", "not connected"}};
return this->sendCommand(header, data, timeout, false);
}
break;
}
default:
{
//qDebug() << "+ --- UNKNOWN SOCKET STATE: " << socketState;
break;
}
}
}
QVariantMap ScStwClient::sendCommand(int header, QJsonValue data, bool useTerminationKeys, int timeout) {
if(this->state != "connected"){
return {{"status", 910}, {"data", "not connected"}};
QVariantMap ScStwClient::sendCommand(int header, QJsonValue data, int timeout, bool useTerminationKeys) {
if(this->state != CONNECTED && this->state != INITIALISING){
return {{"status", ScStw::NotConnectedError}, {"data", "not connected"}};
}
// generate id and witing requests entry
@ -174,8 +138,6 @@ QVariantMap ScStwClient::sendCommand(int header, QJsonValue data, bool useTermin
timer->setSingleShot(true);
// quit the loop when the timer times out
loop->connect(timer, SIGNAL(timeout()), loop, SLOT(quit()));
// quit the loop when the connection was established
// loop.connect(this, &ScStwClient::gotReply, &loop, &QEventLoop::quit);
// start the timer before starting to connect
timer->start(timeout);
@ -215,7 +177,7 @@ QVariantMap ScStwClient::sendCommand(int header, QJsonValue data, bool useTermin
if(timer->remainingTime() == -1){
//the time has been triggered -> timeout
return {{"status", 911}, {"data", ""}};
return {{"status", ScStw::TimeoutError}, {"data", ""}};
}
delete timer;
@ -223,7 +185,47 @@ QVariantMap ScStwClient::sendCommand(int header, QJsonValue data, bool useTermin
}
void ScStwClient::readyRead() {
void ScStwClient::handleSocketStateChange(QAbstractSocket::SocketState socketState) {
switch (socketState) {
case QAbstractSocket::UnconnectedState:
{
break;
}
case QAbstractSocket::ConnectedState:
{
if(!this->init()) {
this->closeConnection();
}
break;
}
default:
{
//qDebug() << "+ --- UNKNOWN SOCKET STATE: " << socketState;
break;
}
}
}
void ScStwClient::handleError(QAbstractSocket::SocketError err)
{
if(err == QAbstractSocket::ProxyConnectionClosedError)
this->closeConnection();
switch (err) {
case QAbstractSocket::ProxyConnectionTimeoutError:
if(this->state == CONNECTING)
this->closeConnection();
break;
default:
break;
}
emit gotError(err);
qDebug() << "got socket error: " << err;
}
void ScStwClient::handleReadyRead() {
//qDebug() << "ready to ready " << socket->bytesAvailable() << " bytes" ;
QString reply = socket->readAll();
@ -234,15 +236,15 @@ void ScStwClient::readyRead() {
}
void ScStwClient::processSocketMessage(QString message) {
QString startKey = "<message>";
QString endKey = "</message>";
//qWarning() << "... processing message now ... : " << message;
QString startKey = ScStw::SOCKET_MESSAGE_START_KEY;
QString endKey = ScStw::SOCKET_MESSAGE_END_KEY;
if(message == ""){
return;
}
if((message.startsWith(startKey) && message.endsWith(endKey)) && (message.count(startKey) == 1 && message.count(endKey) == 1)){
// non-split message ( e.g.: <message>123456789</message>
}
@ -282,12 +284,12 @@ void ScStwClient::processSocketMessage(QString message) {
}
//qWarning() << "... done processing, message: " << message;
this->socketReplyRecieved(message);
this->handleSocketMessage(message);
}
void ScStwClient::socketReplyRecieved(QString reply) {
reply.replace("<message>", "");
reply.replace("</message>", "");
void ScStwClient::handleSocketMessage(QString reply) {
reply.replace(ScStw::SOCKET_MESSAGE_START_KEY, "");
reply.replace(ScStw::SOCKET_MESSAGE_END_KEY, "");
int id = 0;
@ -299,7 +301,7 @@ void ScStwClient::socketReplyRecieved(QString reply) {
if(id == -1) {
// this message is an update!!
emit this->gotUpdate(replyObj.toVariantMap());
emit this->handleSignal(replyObj.toVariantMap());
return;
}
@ -315,8 +317,28 @@ void ScStwClient::socketReplyRecieved(QString reply) {
}
}
latestReadReply = reply;
emit gotUnexpectedReply(reply);
emit gotUnexpectedMessage(reply);
}
void ScStwClient::handleSignal(QVariantMap data) {
// get the signal type
ScStw::SignalKey signalKey = ScStw::SignalKey(data["header"].toInt());
switch (signalKey) {
case ScStw::ExtensionsChanged:
{
// the extension connections have changed
// -> handle locally
this->setConnections(data["data"].toList());
return;
break;
}
default: {
break;
}
}
// forward to external handlers
emit this->gotSignal(signalKey, data["data"]);
}
// -------------------------
@ -336,12 +358,15 @@ bool ScStwClient::updateTime() {
}
bool ScStwClient::updateFirmware() {
if(this->state != CONNECTED)
return false;
QString file = ":/ScStwBasestation.sb64";
QFile f(file);
if (!f.open(QFile::ReadOnly)) return false;
QString fileContents = f.readAll();
if(this->firmwareUpToDate) {
if(this->isFirmwareUpToDate()) {
return true;
}
@ -375,15 +400,23 @@ bool ScStwClient::isFirmwareUpToDate() {
// --- helper functions ---
// ------------------------
int ScStwClient::writeRemoteSetting(QString key, QString value) {
ScStw::ErrorCode ScStwClient::writeRemoteSetting(ScStw::BaseStationSetting key, QString value) {
QJsonArray requestData;
requestData.append(key);
requestData.append(value);
return this->sendCommand(3000, requestData)["status"].toInt();
return ScStw::ErrorCode(this->sendCommand(3000, requestData)["status"].toInt());
}
void ScStwClient::setIP(QString ipAdress){
this->ip = ipAdress;
QString ScStwClient::readRemoteSetting(ScStw::BaseStationSetting key) {
QVariantMap reply = this->sendCommand(3001, key);
if(reply["status"] != 200){
return "false";
}
return reply["data"].toString();
}
void ScStwClient::setIP(QString newIp){
this->ip = newIp;
}
QString ScStwClient::getIP()
@ -391,60 +424,37 @@ QString ScStwClient::getIP()
return this->ip;
}
QString ScStwClient::getState()
ScStwClient::State ScStwClient::getState()
{
return this->state;
}
void ScStwClient::setState(QString newState){
void ScStwClient::setState(ScStwClient::State newState){
if(this->state != newState) {
qDebug() << "+--- ScStwClient state changed: " << newState;
this->state = newState;
emit stateChanged();
if(this->state == "disconnected") {
if(this->state == DISCONNECTED) {
this->deInit();
}
}
}
bool ScStwClient::refreshConnections() {
/*
"id": "id of the extention (int)",
"type": "type of the extention (can be: 'STARTPAD', 'TOPPAD')",
"name": "name of the extention",
"ip": "ip-adress of he extention (string)",
"state": "state of the extention (can be: 'disconnected', 'connecting', 'connected')"
*/
QVariantMap reply = this->sendCommand(2006);
if(reply["status"] != 200){
//handle Error!!
if(reply["status"] == 910){
this->connections = QVariantList({});
return true;
}
qDebug() << "+ --- error refreshing connections: " << reply["status"];
return false;
}
QVariantList tmpConnections = reply["data"].toList();
if(this->connections != reply["data"].toList()){
this->connections = reply["data"].toList();
emit this->connectionsChanged();
}
return true;
}
QVariant ScStwClient::getConnections() {
QVariantList ScStwClient::getConnections() {
return connections;
}
int ScStwClient::getTimeOffset() {
return this->timeOffset;
}
QString ScStwClient::getFirmwareVersion() {
return this->firmwareVersion;
}
void ScStwClient::setConnections(QVariantList connections) {
if(this->connections != connections){
this->connections = connections;
emit this->connectionsChanged();
emit this->gotSignal(ScStw::ExtensionsChanged, this->getConnections());
}
}

View file

@ -14,6 +14,8 @@
#include <string.h>
#include <QByteArray>
#include <ScStw.hpp>
/**
* This class is used to connect and talk to the ScStw basestation.
*
@ -32,31 +34,25 @@ public:
*/
explicit ScStwClient();
enum State {DISCONNECTED, CONNECTING, INITIALISING, CONNECTED};
private:
// values for the socket connection
QString ip;
ushort port = 3563;
int errors;
int errors_until_disconnect = 4;
const static int ERRORS_UNTIL_DISCONNECT = 4;
QVariant connections;
QString latestReadReply;
QVariantList connections;
//---general status values---//
// some meta data of the base
// some meta data of the base station
QString firmwareVersion;
bool firmwareUpToDate;
double timeOffset;
// the current state
QString state;
// can be:
// - 'disconnected'
// - 'connecting'
// - 'connected'
ScStwClient::State state;
QDateTime *date;
//to get the current time
@ -80,94 +76,235 @@ private:
signals:
/**
* Is emitted, when the connection state changes
*
* @brief stateChanged
* @brief Is emitted, when the connection state changes
*/
void stateChanged();
/**
* Is emitted, whenever a reply is recieved which does not match any requests
* @brief Is emitted, whenever a reply is recieved which does not match any requests
*
* @brief gotUnexpectedReply
* @param reply contains the reply
*/
void gotUnexpectedReply(QString reply);
void gotUnexpectedMessage(QString message);
/**
* Is emitted, when an update signal from the basestation is recieved
* @brief Is emitted, when an update signal from the basestation is recieved
*
* @brief gotUpdate
* @param data
*/
void gotUpdate(QVariant data);
void gotSignal(ScStw::SignalKey key, QVariant data);
void connectionsChanged();
void connectionSlotReleased();
void nextRemoteActionChanged();
void nextRemoteActionDelayProgChanged();
void gotError(QString error);
void propertiesChanged();
/**
* @brief Is emitted, when there is any network error
* @param error
*/
void gotError(QAbstractSocket::SocketError error);
public slots:
/**
* @brief Function to connect to the base station
*/
void connectToHost();
//function to connect to the base station
void connectionTimeout();
/**
* @brief Function to disconnect from the basestation
*/
void closeConnection();
void gotError(QAbstractSocket::SocketError err);
// --- socket communication handling ---
/** socket communication handling */
QVariantMap sendCommand(int header, QJsonValue data = "", bool useTerminationKeys = true, int timeout = 3000);
/**
* @brief Funtion to send a command to the server (for internal use)
* @param header the command to send
* @param data the data to send
* @param timeout the timeout
* @param useTerminationKeys wether to use the termination keys defined in
*
* @return a variant map containing the Keys "status" and "data"
*/
QVariantMap sendCommand(int header, QJsonValue data = "", int timeout = 3000);
// --- updater functions ---
/** updater functions */
/**
* @brief Function to set the timestamp of the base station to match the client
* @see getTimeOffset()
* @return true or false
*/
bool updateTime();
/**
* @brief Function to update the firmware of the basestation to the version stored in the client
* @details will not do anything if the remote firmware is newer or the same as the clients one
* @see isFirmwareUpToDate()
* @see getFirmwareVersion()
* @return true: firmware was updated or is already up-to-date; false: there was an error during the update
*/
bool updateFirmware();
/**
* @brief Function to check wether the firmware of the base station is up-to-date
* @see getFirmwareVersion()
* @see updateFirmware()
* @return true or false
*/
bool isFirmwareUpToDate();
// --- helper functions ---
/** helper functions */
int writeRemoteSetting(QString key, QString value);
/**
* @brief Function to write a setting on the base station
* @param key the key to write to
* @param value the value to write to
* @return the status code returned by the command
*/
ScStw::ErrorCode writeRemoteSetting(ScStw::BaseStationSetting key, QString value);
bool refreshConnections();
/**
* @brief Function to read a setting on the base station
* @param key the key to read from
* @return the value of the key or "false" if the key is not found or an error occured
*/
QString readRemoteSetting(ScStw::BaseStationSetting key);
// functions for the qml adapter
/** Getter fuctions */
/**
* @brief Function to get the ip the client will try to connect to.
* @see setIP()
* @return the ip
*/
QString getIP();
/**
* @brief Function to get the current state of the client
* @return the current state
*/
ScStwClient::State getState();
/**
* @brief Functio to get the extensions and their state from the base station
* @return a list with all configured extensions and their state
*/
QVariantList getConnections();
/**
* @brief Function to get the time offset of the base station relative to the clients time
* @see updateTime()
* @return the time offset in milliseconds
*/
int getTimeOffset();
/**
* @brief Function to get the current firmware version of the base station
* @see updateFirmware()
* @see isFirmwareUpToDate()
* @return Firmware version as string encoded as <major>.<minor>.<patch>
*/
QString getFirmwareVersion();
/** setter functions */
/**
* @brief Function to set the ip to connect to
* @see getIP()
* @param ipAdress
*/
void setIP(QString ipAdress);
QString getState();
void setState(QString newState);
int getProgress();
QVariant getConnections();
private slots:
/**
* @brief called when timeout timer times out
*/
void connectionTimeout();
/**
* @brief Function that is connected to the QAbstractSocket::error slot and used to handle upcoming errors
* @param err the error that occurred
*/
void handleError(QAbstractSocket::SocketError err);
/**
* @brief Function to init a session at the base station
* @return true or false
*/
bool init();
/**
* @brief Function to end a session on the base station
*/
void deInit();
void readyRead();
/**
* @brief Funtion to send a command to the server (for internal use)
* @param header the command to send
* @param data the data to send
* @param timeout the timeout
* @param useTerminationKeys wether to use the termination keys defined in
* ScStw::SOCKET_MESSAGE_START_KEY and ScStw::SOCKET_MESSAGE_END_KEY
* @return a variant map containing the Keys "status" and "data"
*/
QVariantMap sendCommand(int header, QJsonValue data, int timeout, bool useTerminationKeys);
/**
* @brief Function connected to the QAbstractSocket::readyRead signal
*/
void handleReadyRead();
/**
* @brief Function to process an incoming string and parse the messages contained in it.
* @param message the message sting to parse
*/
void processSocketMessage(QString message);
void socketReplyRecieved(QString reply);
/**
* @brief Function that handles a parsed message
* @details This fuction looks up the id of the incoming message and tries to find the according waiting request.
* If it is unable to find an accordin request, the signal ScStwClient::gotUnexpectedMessage(QString message) is called.
* If the recieved message is a signal, the ScStwClient::handleSignal() function is called.
* @see gotUnexpectedMessage()
* @see handleSignal()
* @param reply the massage that needs to be handles
*/
void handleSocketMessage(QString reply);
void socketStateChanged(QAbstractSocket::SocketState socketState);
/**
* @brief Function to handle a change of the state of the tcp socket
* @details it is connected to the QAbstractSocket::stateChanged signal
* @see stateChanged()
* @param socketState
*/
void handleSocketStateChange(QAbstractSocket::SocketState socketState);
/**
* @brief Function to handle a signal from the base station.
* @details Called by the ScStwClient::handleSocketMessage function
* @see handleSocketMessage()
* @param data
*/
void handleSignal(QVariantMap data);
/** Helper Functions */
/**
* @brief Function used to set the local cache of the baseStation connections.
* @details emits ScStwClient::gotSignal() with a ScStw::ExtensionsChanged signal.
* @see gotSignal()
* @param connections the list to set the chache to
*/
void setConnections(QVariantList connections);
/**
* @brief Function to set the local state.
* @details emits ScStwClient::stateChanged() when the new state does not match the old one.
* @see stateChanged()
* @param newState the state to change to
*/
void setState(ScStwClient::State newState);
};
extern ScStwClient * pGlobalScStwClient;
#endif // SCSTWCLIENT_H