#include "baseconn.h" BaseConn::BaseConn(QObject *parent) : QObject(parent) { socket = new QTcpSocket(this); this->timeoutTimer = new QTimer(this); this->timeoutTimer->setSingleShot(true); this->state = "disconnected"; connect(this->socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(gotError(QAbstractSocket::SocketError))); connect(this->socket, &QAbstractSocket::stateChanged, this, &BaseConn::socketStateChanged); connect(this, &BaseConn::gotUpdate, this, &BaseConn::handleUpdate); this->nextConnectionId = 1; // init refresh timers this->autoConnectRetryTimer = new QTimer(this); this->autoConnectRetryTimer->setInterval(1000); this->autoConnectRetryTimer->setSingleShot(true); connect(this->autoConnectRetryTimer, &QTimer::timeout, this, &BaseConn::doConnectionAttempt); this->autoConnectRetryTimer->start(); this->timerTextRefreshTimer = new QTimer(this); this->timerTextRefreshTimer->setInterval(1); this->timerTextRefreshTimer->setSingleShot(true); connect(this->timerTextRefreshTimer, &QTimer::timeout, this, &BaseConn::refreshTimerTextList); this->timerTextRefreshTimer->start(); } void BaseConn::connectToHost() { qDebug() << "+--- connecting"; setState("connecting"); connect(this->timeoutTimer, SIGNAL(timeout()), this, SLOT(connectionTimeout())); //connect this->socket->connectToHost(this->ip, this->port); timeoutTimer->start(3000); } void BaseConn::connectionTimeout() { this->socket->abort(); disconnect(this->timeoutTimer, SIGNAL(timeout()), this, SLOT(connectionTimeout())); } bool BaseConn::init() { disconnect(this->timeoutTimer, SIGNAL(timeout()), this, SLOT(connectionTimeout())); this->timeoutTimer->stop(); connect(this->socket, &QTcpSocket::readyRead, this, &BaseConn::readyRead); this->setState("connected"); // init remote session QJsonArray updateSubs = {"onTimersChanged", "onRaceStateChanged", "onNextStartActionChanged"}; QJsonObject sessionParams = {{"updateSubs", updateSubs}, {"init", true}}; if(this->sendCommand(1, sessionParams)["status"] != 200) { return false; } return true; } void BaseConn::deInit() { } void BaseConn::closeConnection() { qDebug() << "+--- closing connection"; switch (socket->state()) { case 0: socket->disconnectFromHost(); break; case 2: socket->abort(); break; default: socket->abort(); } setState("disconnected"); } void BaseConn::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); } // ------------------------------------- // --- socket communication handling --- // ------------------------------------- void BaseConn::socketStateChanged(QAbstractSocket::SocketState socketState) { switch (socketState) { case QAbstractSocket::UnconnectedState: { this->setState("disconnected"); break; } case QAbstractSocket::ConnectedState: { if(this->init()) { this->setState("connected"); } else { this->closeConnection(); } break; } default: { //qDebug() << "+ --- UNKNOWN SOCKET STATE: " << socketState; break; } } } QVariantMap BaseConn::sendCommand(int header, QJsonValue data){ if(this->state != "connected"){ return {{"status", 910}, {"data", "not connected"}}; } // generate id and witing requests entry int thisId = nextConnectionId; //qDebug() << "sending command: " << header << " with data: " << data << " and id: " << thisId; nextConnectionId ++; QEventLoop *loop = new QEventLoop(this); QTimer *timer = new QTimer(this); QJsonObject reply; this->waitingRequests.append({thisId, loop, reply}); QJsonObject requestObj; requestObj.insert("id", thisId); requestObj.insert("header", header); requestObj.insert("data", data); QString jsonRequest = QJsonDocument(requestObj).toJson(); 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, &BaseConn::gotReply, &loop, &QEventLoop::quit); // start the timer before starting to connect timer->start(3000); //write data socket->write(jsonRequest.toLatin1()); //wait for an answer to finish (programm gets stuck in here) loop->exec(); bool replyFound = false; // find reply and delete the request from waiting list for(int i = 0; iwaitingRequests.length(); i++){ if(this->waitingRequests[i].id == thisId){ // request was found replyFound = true; // delete event loop if(this->waitingRequests[i].loop != nullptr) { delete this->waitingRequests[i].loop; } // store reply reply = this->waitingRequests[i].reply; // remove reply from waiting list this->waitingRequests.removeAt(i); } } if(!replyFound) { // some internal error occured return {{"status", 900}, {"data", ""}}; } if(timer->remainingTime() == -1){ //the time has been triggered -> timeout return {{"status", 911}, {"data", ""}}; } delete timer; return {{"status", reply.value("header").toInt()}, {"data", reply.value("data").toVariant()}}; } void BaseConn::readyRead() { //qDebug() << "ready to ready " << socket->bytesAvailable() << " bytes" ; QString reply = socket->readAll(); //qWarning() << "socket read: " << reply; processSocketMessage(reply); } void BaseConn::processSocketMessage(QString message){ QString startKey = ""; QString endKey = ""; //qWarning() << "... processing message now ... : " << message; if(message == ""){ return; } if((message.startsWith(startKey) && message.endsWith(endKey)) && (message.count(startKey) == 1 && message.count(endKey) == 1)){ // non-split message ( e.g.: 123456789 } else if(!message.contains(endKey) && (!this->readBuffer.isEmpty() || message.startsWith(startKey))){ // begin of a split message ( e.g.: 123 ) // or middle of a split message ( e.g.: 456 ) //qWarning() << "this is a begin or middle of split a message"; this->readBuffer += message; return; } else if(!message.contains(startKey) && message.endsWith(endKey)) { // end of a split message ( e.g.: 789 ) if(!this->readBuffer.isEmpty()){ message = readBuffer + message; readBuffer.clear(); } } else if((message.count(startKey) > 1 || message.count(endKey) > 1) || (message.contains(endKey) && !message.endsWith(endKey) && message.contains(startKey) && !message.startsWith(startKey))) { // multiple messages in one packet ( e.g.: 123456789987654321 ) // or multiple message fragments in one message ( e.g.: 56789987654321 or 5678998765 ) //qDebug() << "detected multiple messages"; int startOfSecondMessage = message.lastIndexOf(startKey); // process first part of message QString firstMessage = message.left(startOfSecondMessage); this->processSocketMessage(firstMessage); // process second part of message QString secondMessage = message.right(message.length() - startOfSecondMessage); this->processSocketMessage(secondMessage); return; } else { // invalid message return; } //qWarning() << "... done processing, message: " << message; this->socketReplyRecieved(message); } void BaseConn::socketReplyRecieved(QString reply) { reply.replace("", ""); reply.replace("", ""); int id = 0; QJsonDocument jsonReply = QJsonDocument::fromJson(reply.toUtf8()); QJsonObject replyObj = jsonReply.object(); //qDebug() << "got: " << reply; if(!replyObj.isEmpty()){ id = replyObj.value("id").toInt(); if(id == -1) { // this message is an update!! emit this->gotUpdate(replyObj.toVariantMap()); return; } for(int i = 0; i < this->waitingRequests.length(); i++){ if(this->waitingRequests[i].id == id){ this->waitingRequests[i].reply = replyObj; if(this->waitingRequests[i].loop != nullptr){ this->waitingRequests[i].loop->quit(); } return; } } } latestReadReply = reply; emit gotUnexpectedReply(reply); } // ------------------------ // --- helper functions --- // ------------------------ void BaseConn::doConnectionAttempt() { if(this->state == "disconnected") { qDebug() << "+--- trying to connect"; this->connectToHost(); } this->autoConnectRetryTimer->start(); } void BaseConn::setState(QString newState){ if(this->state != newState) { qDebug() << "+--- BaseConn state changed: " << newState; this->state = newState; emit stateChanged(); if(this->state == "disconnected") { this->deInit(); } } } int BaseConn::writeRemoteSetting(QString key, QString value) { QJsonArray requestData; requestData.append(key); requestData.append(value); return this->sendCommand(3000, requestData)["status"].toInt(); } QString BaseConn::readRemoteSetting(QString key) { QVariantMap reply = this->sendCommand(3001, key); if(reply["status"] != 200){ return "false"; } return reply["data"].toString(); } // ------------------ // - for timer sync - // ------------------ void BaseConn::handleUpdate(QVariantMap data) { int header = data["header"].toInt(); switch (header) { case 9000: { // the remote race state changed this->remoteRaceState = data["data"].toInt(); this->raceStateChanged(); break; } case 9001: { // the remote timers have changed this->refreshRemoteTimers(data["data"].toList()); break; } case 9003: { // the next start action has changed this->nextStartActionTotalDelay = data["data"].toMap()["nextActionDelay"].toDouble(); this->nextStartActionDelayStartedAt = this->date->currentMSecsSinceEpoch() - (this->nextStartActionTotalDelay * data["data"].toMap()["nextActionDelayProg"].toDouble()); this->nextStartAction = NextStartAction( data["data"].toMap()["nextAction"].toInt() ); emit this->nextStartActionChanged(); } } } void BaseConn::refreshRemoteTimers(QVariantList timers) { QVariantList remoteTimers; for (int i = 0; i < timers.length(); i++) { QVariantMap thisTimer = timers[i].toMap(); if(thisTimer["state"].toInt() != DISABLED ) { thisTimer.insert("startTime", this->date->currentMSecsSinceEpoch() - thisTimer["currTime"].toDouble()); remoteTimers.append(thisTimer); } } this->remoteTimers = remoteTimers; } void BaseConn::refreshTimerTextList() { QVariantList tmpTimerTextList; for (int i = 0; i < this->remoteTimers.toList().length(); i++) { QString newText; switch (this->remoteTimers.toList()[i].toMap()["state"].toInt()) { case IDLE: newText = "00.000"; break; case STARTING: newText = "00.000"; break; case WAITING: newText = "False Start"; break; case RUNNING: { double currTime = this->date->currentMSecsSinceEpoch() - this->remoteTimers.toList()[i].toMap()["startTime"].toDouble(); QString currTimeString = (currTime < 10000 ? "0":"") + QString::number( currTime / 1000.0, 'f', 3 );//QString::number( (currTime) / 1000.0, 'f', 1 ); newText = currTimeString; break; } case WON: { double currTime = this->remoteTimers.toList()[i].toMap()["currTime"].toDouble(); newText = (currTime < 10000 ? "0":"") + QString::number( currTime / 1000.0, 'f', 3 ); break; } case LOST: { double currTime = this->remoteTimers.toList()[i].toMap()["currTime"].toDouble(); newText = (currTime < 10000 ? "0":"") + QString::number( currTime / 1000.0, 'f', 3 ); break; } case FAILED: newText = "False Start"; break; case CANCELLED: newText = "Cancelled"; break; case DISABLED: newText = "---"; break; } QVariantMap timerMap = {{"text", newText}, {"reactTime", this->remoteTimers.toList()[i].toMap()["reactTime"].toInt()}, {"state", this->remoteTimers.toList()[i].toMap()["state"].toInt()}}; tmpTimerTextList.append(timerMap); } if(tmpTimerTextList != this->timerTextList) { this->timerTextList = tmpTimerTextList; emit this->timerTextChanged(); } // calculate next start action delay progress double nextStartActionRemainingDelay = this->nextStartActionTotalDelay - ( this->date->currentMSecsSinceEpoch() - this->nextStartActionDelayStartedAt ); if(nextStartActionRemainingDelay > 0){ this->nextStartActionDelayProgress = nextStartActionRemainingDelay / this->nextStartActionTotalDelay; emit this->nextStartActionDelayProgressChanged(); } else { this->nextStartActionDelayProgress = 0; emit this->nextStartActionDelayProgressChanged(); } this->timerTextRefreshTimer->start(); } // ----------- // - for qml - // ----------- void BaseConn::setIP(const QString &ipAdress){ this->ip = ipAdress; } QString BaseConn::getIP() const { return(this->ip); } QString BaseConn::getState() const { return(this->state); } QVariant BaseConn::getTimerTextList() { return this->timerTextList; } int BaseConn::getRaceState() { return this->remoteRaceState; } double BaseConn::getNextStartActionDelayProgress() { return this->nextStartActionDelayProgress; } int BaseConn::getNextStartAction() { return this->nextStartAction; }