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.
shared-libraries/ScStwLibraries/scstwclient.cpp
2020-04-05 14:11:52 +02:00

450 lines
13 KiB
C++

#include "scstwclient.h"
ScStwClient * pGlobalScStwClient = nullptr;
ScStwClient::ScStwClient(QObject *parent) : QObject(parent)
{
pGlobalScStwClient = 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, &ScStwClient::socketStateChanged);
this->nextConnectionId = 1;
this->connections = QVariantList({});
}
void ScStwClient::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 ScStwClient::connectionTimeout() {
this->socket->abort();
disconnect(this->timeoutTimer, SIGNAL(timeout()), this, SLOT(connectionTimeout()));
}
bool ScStwClient::init() {
disconnect(this->timeoutTimer, SIGNAL(timeout()), this, SLOT(connectionTimeout()));
this->timeoutTimer->stop();
connect(this->socket, &QTcpSocket::readyRead, this, &ScStwClient::readyRead);
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 ScStwClient::deInit() {
this->connections.clear();
emit this->connectionsChanged();
this->setState("disconnected");
}
void ScStwClient::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 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();
}
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"}};
}
// 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, &ScStwClient::gotReply, &loop, &QEventLoop::quit);
// start the timer before starting to connect
timer->start(timeout);
//write data
if(useTerminationKeys) {
socket->write("<message>" + jsonRequest.toLatin1() + "</message>");
}
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; i<this->waitingRequests.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 ScStwClient::readyRead() {
//qDebug() << "ready to ready " << socket->bytesAvailable() << " bytes" ;
QString reply = socket->readAll();
//qWarning() << "socket read: " << reply;
processSocketMessage(reply);
}
void ScStwClient::processSocketMessage(QString message) {
QString startKey = "<message>";
QString endKey = "</message>";
//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.: <message>123456789</message>
}
else if(!message.contains(endKey) && (!this->readBuffer.isEmpty() || message.startsWith(startKey))){
// begin of a split message ( e.g.: <message>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</message> )
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.: <message>123456789</message><message>987654321</message> )
// or multiple message fragments in one message ( e.g.: 56789</message><message>987654321</message> or 56789</message><message>98765 )
//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 ScStwClient::socketReplyRecieved(QString reply) {
reply.replace("<message>", "");
reply.replace("</message>", "");
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 ScStwClient::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 ScStwClient::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 ScStwClient::isFirmwareUpToDate() {
QString file = ":/ScStwBasestation.sb64";
QFile f(file);
if (!f.open(QFile::ReadOnly)) return false;
QString fileContents = f.readAll();
QString newFirmwareVersion = fileContents.split("<VER>")[1].split("</VER>")[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 ScStwClient::writeRemoteSetting(QString key, QString value) {
QJsonArray requestData;
requestData.append(key);
requestData.append(value);
return this->sendCommand(3000, requestData)["status"].toInt();
}
void ScStwClient::setIP(QString ipAdress){
this->ip = ipAdress;
}
QString ScStwClient::getIP()
{
return this->ip;
}
QString ScStwClient::getState()
{
return this->state;
}
void ScStwClient::setState(QString newState){
if(this->state != newState) {
qDebug() << "+--- ScStwClient state changed: " << newState;
this->state = newState;
emit stateChanged();
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() {
return connections;
}
void ScStwClient::setConnections(QVariantList connections) {
if(this->connections != connections){
this->connections = connections;
emit this->connectionsChanged();
}
}