#include "headers/baseconn.h" BaseConn * pGlobalBaseConn = nullptr; BaseConn::BaseConn(QObject *parent) : QObject(parent) { pGlobalBaseConn = this; 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); this->nextConnectionId = 1; this->connections = QVariantList({}); } void BaseConn::connectToHost() { qDebug() << "connecting"; setState("connecting"); this->connection_progress = 0; 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->connection_progress = 50; this->setState("connected"); // init remote session QJsonArray updateSubs = {"onRaceStateChanged", "onTimersChanged", "onExtensionConnectionsChanged", "onNextStartActionChanged"}; QJsonObject sessionParams = {{"updateSubs", updateSubs}, {"init", true}, {"usingTerminationKeys", true}}; QVariantMap initResponse = this->sendCommand(1, sessionParams, false); if(initResponse["status"] != 200) { 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; return true; } void BaseConn::deInit() { this->connections.clear(); emit this->connectionsChanged(); this->setState("disconnected"); } void BaseConn::closeConnection() { this->connections = QVariantList({}); emit this->connectionsChanged(); 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); qDebug() << "got socket error: " << strError; } // ------------------------------------- // --- socket communication handling --- // ------------------------------------- void BaseConn::socketStateChanged(QAbstractSocket::SocketState socketState) { switch (socketState) { case QAbstractSocket::UnconnectedState: { this->deInit(); break; } case QAbstractSocket::ConnectedState: { if(!this->init()) { this->closeConnection(); } break; } default: { //qDebug() << "+ --- UNKNOWN SOCKET STATE: " << socketState; break; } } } QVariantMap BaseConn::sendCommand(int header, QJsonValue data, bool useTerminationKeys, int timeout) { 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(timeout); //write data if(useTerminationKeys) { socket->write("" + jsonRequest.toLatin1() + ""); } else { 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(); if(!replyObj.isEmpty()){ id = replyObj.value("id").toInt(); if(id == -1) { // this message is an update!! emit this->gotUpdate(replyObj.toVariantMap()); return; } // this message is the reply to a command! 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); } // ------------------------- // --- updater functions --- // ------------------------- bool BaseConn::updateTime() { if(abs(this->timeOffset) < 10000) { // the time is already up-to-date return true; } QVariantMap ret = this->sendCommand(5001, this->date->currentSecsSinceEpoch()); qDebug() << ret; return ret["status"].toInt() == 200; } bool BaseConn::updateFirmware() { QString file = ":/ScStwBasestation.sb64"; QFile f(file); if (!f.open(QFile::ReadOnly)) return false; QString fileContents = f.readAll(); if(this->firmwareUpToDate) { return true; } QVariantMap ret = this->sendCommand(5000, fileContents, true, 15000); return ret["status"].toInt() == 200; } bool BaseConn::isFirmwareUpToDate() { QString file = ":/ScStwBasestation.sb64"; QFile f(file); if (!f.open(QFile::ReadOnly)) return false; QString fileContents = f.readAll(); QString newFirmwareVersion = fileContents.split("")[1].split("")[0]; int newFirmwareVersionMajor = newFirmwareVersion.split(".")[0].toInt(); int newFirmwareVersionMinor = newFirmwareVersion.split(".")[1].toInt(); int newFirmwareVersionPatch = newFirmwareVersion.split(".")[2].toInt(); qDebug() << "App firmware version is: " << newFirmwareVersion; QString currentFirmwareVersion = this->firmwareVersion; int currentFirmwareVersionMajor = currentFirmwareVersion.split(".")[0].toInt(); int currentFirmwareVersionMinor = currentFirmwareVersion.split(".")[1].toInt(); int currentFirmwareVersionPatch = currentFirmwareVersion.split(".")[2].toInt(); return newFirmwareVersionMajor < currentFirmwareVersionMajor || newFirmwareVersionMinor < currentFirmwareVersionMinor || newFirmwareVersionPatch <= currentFirmwareVersionPatch; } // ------------------------ // --- helper functions --- // ------------------------ int BaseConn::writeRemoteSetting(QString key, QString value) { QJsonArray requestData; requestData.append(key); requestData.append(value); return this->sendCommand(3000, requestData)["status"].toInt(); } void BaseConn::setIP(const QString &ipAdress){ this->ip = ipAdress; } QString BaseConn::getIP() const { return(this->ip); } QString BaseConn::getState() const { return(this->state); } 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::getProgress() const { return(connection_progress); } bool BaseConn::refreshConnections() { 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 BaseConn::getConnections() { return(connections); /* "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 conn = {{"id",0}, {"type","STARTPAD"}, {"name", "startpad1"}, {"ip", "192.168.4.11"}, {"state", "connected"}}; //QVariantMap conn1 = {{"id",0}, {"type","TOPPAD"}, {"name", "buzzer1"}, {"ip", "192.168.4.10"}, {"state", "connected"}}; //QVariantList conns = {conn, conn1}; //return conns; } void BaseConn::setConnections(QVariantList connections) { if(this->connections != connections){ this->connections = connections; emit this->connectionsChanged(); } }