504 lines
15 KiB
C++
Executable file
504 lines
15 KiB
C++
Executable file
#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; 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 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 = "<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 BaseConn::socketReplyRecieved(QString reply) {
|
|
reply.replace("<message>", "");
|
|
reply.replace("</message>", "");
|
|
|
|
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;
|
|
}
|