From b93b598c80348e3e5a88ba95ccf874985a992637 Mon Sep 17 00:00:00 2001 From: Dorian Zedler Date: Sun, 23 Sep 2018 17:54:20 +0200 Subject: [PATCH] Added functionality to connect to base station --- headers/baseconn.h | 74 +++++++ headers/buzzerconn.h | 4 +- qml/SettingsDialog.qml | 58 ++++++ qml/components/ConnectionDelegate.qml | 1 - qml/main.qml | 106 ++++++---- sources/baseconn.cpp | 138 +++++++++++++ sources/buzzerconn.cpp | 275 ++++++++++++++++---------- sources/main.cpp | 4 +- speedclimbing_stopwatch.pro | 6 +- 9 files changed, 520 insertions(+), 146 deletions(-) create mode 100644 headers/baseconn.h create mode 100644 sources/baseconn.cpp diff --git a/headers/baseconn.h b/headers/baseconn.h new file mode 100644 index 0000000..be00d35 --- /dev/null +++ b/headers/baseconn.h @@ -0,0 +1,74 @@ +#ifndef BASECONN_H +#define BASECONN_H + +#include +#include +#include +#include +#include +#include + +class BaseConn : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString ipAdress WRITE setIP READ getIP) + Q_PROPERTY(QString state READ getState NOTIFY stateChanged) + Q_PROPERTY(int progress READ getProgress NOTIFY progressChanged) +public: + explicit BaseConn(QObject *parent = nullptr); + + // values for the socket connection + int connection_progress; + QString ip; + qint16 port = 3563; + int errors; + int errors_until_disconnect = 4; + + // the current state + QString state; + // can be: + // - 'disconnected' + // - 'connecting' + // - 'connected' + + QString latestReadReply; +private: + QDateTime *date; + //to get the current time + + QTcpSocket *socket; + //socket for communication with the extention + + + +signals: + void stateChanged(); + //is emitted, when the connection state changes + + void progressChanged(); + //is emmited during the connection process when the progress changes + + void gotReply(); + +public slots: + + Q_INVOKABLE bool connectToHost(); + //function to connect to the base station + + Q_INVOKABLE QString sendCommand(QString command); + + // functions for the qml adapter + QString getIP() const; + void setIP(const QString &ipAdress); + + QString getState() const; + void setState(QString newState); + + int getProgress() const; + +private slots: + void readyRead(); +}; + +#endif // BASECONN_H diff --git a/headers/buzzerconn.h b/headers/buzzerconn.h index 5446f51..e2daa70 100644 --- a/headers/buzzerconn.h +++ b/headers/buzzerconn.h @@ -70,9 +70,11 @@ signals: //is emitted, when the connection state changes void progressChanged(); + //is emmited during the connection process when the progress changes void offsetChanged(); + public slots: Q_INVOKABLE signed long sendCommand(QString command, int timeout); @@ -102,7 +104,7 @@ public slots: Q_INVOKABLE bool refresh(); //- refreshes the connection to the buzzer - //- checks if it as triggered if it was 'triggered()' is emitted + //- checks if it was triggered, if so 'triggered()' is emitted //- sends one command from the pending commans list Q_INVOKABLE void appendCommand(QString command); diff --git a/qml/SettingsDialog.qml b/qml/SettingsDialog.qml index e553157..6bcb4f9 100644 --- a/qml/SettingsDialog.qml +++ b/qml/SettingsDialog.qml @@ -261,6 +261,36 @@ Popup { id: connect_col property string title: qsTr("connections") property int delegateHeight: height*0.18 + + ItemDelegate { + id: baseConn_del + text: qsTr("Base Station") + + contentItem: Text { + text: parent.text + color: StyleSettings.textColor + font.pixelSize: options_stack.text_pixelSize + } + + width: parent.width + + Image { + id: connect_del_image + source: StyleSettings.backIcon + rotation: 180 + height: options_stack.text_pixelSize + width: height + anchors { + verticalCenter: parent.verticalCenter + right: parent.right + rightMargin: 10 + } + } + onClicked: { + options_stack.push(baseStation) + } + } + ConnectionDelegate { id: connect_buzz_del @@ -414,6 +444,34 @@ Popup { } } + /*-----Page to connect to and manage the Base Station----*/ + Component { + id: baseStation + Column { + id: baseStation_col + property string title: qsTr("Base Station") + property int delegateHeight: height*0.18 + + ConnectionDelegate { + id: connect_base_del + text: "connect" + contentItem: Text { + text: parent.text + color: StyleSettings.textColor + font.pixelSize: options_stack.text_pixelSize + } + + status: root.connections.baseStation + connect: root.connect + type: "baseStation" + + width: parent.width + font.pixelSize: options_stack.text_pixelSize + } + } + + } + /*-----Custom animations-----*/ pushEnter: Transition { diff --git a/qml/components/ConnectionDelegate.qml b/qml/components/ConnectionDelegate.qml index c9cffa1..0229e51 100644 --- a/qml/components/ConnectionDelegate.qml +++ b/qml/components/ConnectionDelegate.qml @@ -10,7 +10,6 @@ ItemDelegate { enabled: status.status === "disconnected" - onClicked: { connect(type) if(status.status !== "connected"){ diff --git a/qml/main.qml b/qml/main.qml index 312ab4e..02af994 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -56,7 +56,8 @@ Window { //array that contains all connections an their atatus property var connections: { 'buzzer': buzzerConn.status, - 'startpad': startpadConn.status + 'startpad': startpadConn.status, + 'baseStation': baseConn.status } //set default state to IDLE @@ -121,37 +122,46 @@ Window { } } + BaseStationConn { + id: baseConn + ipAdress: "localhost" + property var status: {'status': baseConn.state, 'progress': baseConn.progress} - -/* - BuzzerConn { - id: buzzerConn - onPushed: { - // the buzzer was pushed - root.stop("buzzer") - } - } - - StartpadConn { - id: startpadConn - active: true - onActiveChanged: { - console.log("active changed: "+active) - } - - onPushed: { - active = false - console.log("startpad triggered") - var offset = _cppStartpadConn.get("offset") - var last_pressed = _cppStartpadConn.get("lastpressed") - var trigger_time = (last_pressed + offset) - root.last_run.react_time = trigger_time - root.startTime - if(trigger_time - root.startTime <= 0){ - root.stop("false") + function getTime(type){ + var time = parseInt(sendCommand("GET_CURRTIME")) + if(type === "readable"){ + return(time / 1000).toFixed(3) } + else if(type === "raw"){ + return(time) + } + + } } -*/ + + Timer { + id: baseRefreshTimer + running: baseConn.state === "connected" + repeat: false + interval: 1 + onTriggered: { + if(root.state === "RUNNING"){ + time.text = baseConn.getTime("readable") + " sec" + if(baseConn.sendCommand("GET_TIMER_STATE") === "STOPPED"){ + root.stop("manual") + } + } + if(root.state === "STARTING"){ + if(baseConn.sendCommand("GET_TIMER_STATE") === "RUNNING"){ + root.state = "RUNNING"; + } + } + + start() + } + } + Timer { //timer that updates the currTime variable running: true @@ -251,7 +261,7 @@ Window { } SoundEffect { - //start sound + //false-start sound id: falseSound source: "qrc:/sounds/false.wav" } @@ -326,10 +336,6 @@ Window { leftMargin: 10 } height: root.landscape()? root.height*0.1:root.width*0.1 - - Component.onCompleted: { - console.log(root.connections.buzzer) - } } ConnectionIcon { @@ -343,10 +349,6 @@ Window { leftMargin: 5 + buzzerLogo.width } height: root.landscape()? root.height*0.1:root.width*0.1 - - Component.onCompleted: { - console.log(root.connections.buzzer) - } } Rectangle { @@ -500,6 +502,9 @@ Window { case "startpad": startpadConn.connect() break + case "baseStation": + baseConn.connectToHost() + break } } } @@ -640,7 +645,6 @@ Window { anchors.bottomMargin: root.landscape() ? undefined:parent.height * 0.1; anchors.rightMargin: root.landscape() ? parent.height * 0.05:0 } - PropertyChanges { target: startButt; enabled: true; text: qsTr("start"); @@ -728,6 +732,15 @@ Window { /*----Functions to control the stopwatch----*/ function start(){ + if(baseConn.state === "connected"){ + var ret = baseConn.sendCommand("CMD_START_TIMER") + if(ret === "OK"){ + root.state = "STARTING" + return + } + } + + root.state = "STARTING" if(_cppAppSettings.loadSetting("at_marks_en") === "true"){ next_actionTimer.action = "at_marks" @@ -759,6 +772,15 @@ Window { break case "manual": //the stop button was pressed + + if(baseConn.state === "connected"){ + + root.stoppedTime = baseConn.getTime("raw") + time.text = (root.stoppedTime / 1000).toFixed(3) + " sec" + root.state = "STOPPED" + return + } + root.stoppedTime = new Date().getTime() - root.startTime time.text = ( root.stoppedTime / 1000 ).toFixed(3) + " sec" root.state = "STOPPED" @@ -775,10 +797,16 @@ Window { falseSound.play() break } - //tartpadConn.active = true } function reset(){ + if(baseConn.state === "connected"){ + var ret = baseConn.sendCommand("CMD_RESET_TIMER") + if(ret !== "OK"){ + return + } + } + root.state = "IDLE" root.last_run.react_time = 0 } diff --git a/sources/baseconn.cpp b/sources/baseconn.cpp new file mode 100644 index 0000000..27da595 --- /dev/null +++ b/sources/baseconn.cpp @@ -0,0 +1,138 @@ +#include "headers/baseconn.h" + +BaseConn::BaseConn(QObject *parent) : QObject(parent) +{ + socket = new QTcpSocket(); + this->setState("disconnected"); +} + +bool BaseConn::connectToHost() { + + setState("connecting"); + this->connection_progress = 0; + QEventLoop loop; + QTimer timer; + + 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->socket, SIGNAL(connected()), &loop, SLOT(quit())); + // start the timer before starting to connect + timer.start(3000); + //connect + this->socket->connectToHost(this->ip, this->port); + + //wait for the connection to finish (programm gets stuck in here) + loop.exec(); + + //loop finished + + if(timer.remainingTime() == -1){ + //the time has been triggered -> timeout + this->socket->abort(); + setState("disconnected"); + return(false); + } + + // stop the timer as the connection has been established + timer.stop(); + connect(this->socket, &QTcpSocket::readyRead, this, &BaseConn::readyRead); + this->connection_progress = 100; + setState("connected"); + return(true); +} + +QString BaseConn::sendCommand(QString command){ + QByteArray arrBlock; + QDataStream out(&arrBlock, QIODevice::WriteOnly); + //out.setVersion(QDataStream::Qt_5_10); + out << quint16(0) << command; + + out.device()->seek(0); + out << quint16(arrBlock.size() - sizeof(quint16)); + + QEventLoop loop; + QTimer timer; + + 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(arrBlock); + + //wait for an answer to finish (programm gets stuck in here) + loop.exec(); + + //loop finished + if(timer.remainingTime() == -1){ + //the time has been triggered -> timeout + return("timeout"); + } + + // stop the timer as the connection has been established + timer.stop(); + + + return(this->latestReadReply); +} + +void BaseConn::readyRead() { + qDebug() << "readyRead"; + QDataStream in(socket); + //in.setVersion(QDataStream::Qt_5_10); + qint16 nextBlockSize = 0; + for (;;) + { + if (!nextBlockSize) + { + if (socket->bytesAvailable() < sizeof(quint16)) { break; } + in >> nextBlockSize; + } + + if (socket->bytesAvailable() < nextBlockSize) { break; } + + QString str; in >> str; + +// if (str == "0") +// { +// str = "Connection closed"; +// closeConnection(); +// } + nextBlockSize = 0; + latestReadReply = str; + emit gotReply(); + + } +} + +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){ + this->state = newState; + emit stateChanged(); +} + +int BaseConn::getProgress() const +{ + return(connection_progress); +} + diff --git a/sources/buzzerconn.cpp b/sources/buzzerconn.cpp index 80b17be..1669375 100644 --- a/sources/buzzerconn.cpp +++ b/sources/buzzerconn.cpp @@ -19,20 +19,25 @@ BuzzerConn::BuzzerConn(QObject *parent, QString ip, int port) : QObject(parent) { + // initialize TcpSocket for communication with the extentions this->socket = new QTcpSocket(); + // initialize Qdate this->date = new QDateTime; this->latest_button_pressed = 0; + // set ip and port this->ip = ip; this->port = port; + //standard state: disconnected this->setState("disconnected"); - // "http://192.168.4.1" } bool BuzzerConn::connect() { + // function to connect to buzzer + qDebug() << "connecting..."; setState("connecting"); @@ -42,14 +47,16 @@ bool BuzzerConn::connect() QTimer timer; 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->socket, SIGNAL(connected()), &loop, SLOT(quit())); - + // start the timer before starting to connect timer.start(3000); //connect this->socket->connectToHost(this->ip, this->port); - //wait for the connection to finish + //wait for the connection to finish (programm gets stuck in here) loop.exec(); //loop finished @@ -58,26 +65,33 @@ bool BuzzerConn::connect() setState("disconnected"); return(false); } + + // stop the timer as the connection has been established timer.stop(); - + // get the timestamps ( last_pressed and current_timestamp ) from the extention QList times = gettimes(2000, true); - qDebug() << times; + if(times[0] == 200.0){ + //if the request was successfull, set last_triggered and this->latest_button_pressed = times[2]; for(int i=0;i<=100;i++){ + // middle the offset 100 times this->connection_progress = i; emit this->progressChanged(); if(!calcoffset(this->gettimes(1000, false))){ + // if a request fails, cancle the connection process this->connection_progress = 100; setState("disconnected"); return(false); } } + // after middeling the offset for 100 times set the state to connected and quit setState("connected"); return(true); } else{ + //if not, cancel setState("disconnected"); return(false); } @@ -85,75 +99,107 @@ bool BuzzerConn::connect() bool BuzzerConn::calcoffset(QList times) { + //function that recieves the current time of the extention, + //puts it into the latest offsets list and + //calculates the avarage offset + + //if there are not enoug items in the list (0=status of the request (200 = success ...); 1 = timestamp that has to be added to the list) if(times.length() != 2){ return(false); } - if(times[0] == 200.0){ + if(times[0] == 200.0){ + // if the given request was a successfull request, calculate the offset double offset = date->currentMSecsSinceEpoch() - times[1]; + //check if the list contains more than or 100 values if(this->latest_offsets.length()>=100){ + //if so, delete the first (oldest) one this->latest_offsets.removeFirst(); } + //append the new offset to the list this->latest_offsets.append(offset); - + //variable to store the avarage double mem = 0; for(int i=0;ioffset = mem / double(latest_offsets.length()); - offsetChanged(); + + //emit that the offset has changed + emit offsetChanged(); //qDebug("%20f", this->offset); return(true); } else { - //this->connected = false; + //if the given request was not valid, return false return(false); } } QList BuzzerConn::gettimes(int timeout, bool bothTimes) { + // function to recieve the timestamps (last_triggered, current_timestamp) from the extentions + + //list to store the return code of the request and the timestamps QList times; + //variable to store answer of the extention signed long ret; + //send request to extention ret = this->sendCommand("GET_TIMESTAMP", timeout); if(ret >= 0){ - + // if it is higer than 0 (=success) append the return code times.append(double(200)); + //and the timestamp times.append(double(ret)); if(bothTimes){ + //if both timstamps were requested do the same thing again for the other one (last_triggered) ret = this->sendCommand("GET_LASTPRESSED", timeout); if(ret >= 0){ + //if the reuest was successfull, append the value to the list times.append(double(ret)); } else { + // if not, change the return code times[0] = ret; } } } else { + // if not, append the return code times.append(ret); } + + //return the list return(times); } double BuzzerConn::get(QString key) { + // function to get all kinds of data + if(key == "offset"){ + // get the offset of the extention return(this->offset); } else if (key == "lastpressed") { + // get the last_triggered timestamp of the extention return(this->latest_button_pressed); } else if( key == "currtime") { + // get the current time return(this->date->currentMSecsSinceEpoch()); } else if( key == "connection_progress") { + //get the connection progress return(this->connection_progress); } else if( key == "connected") { + // get the state of the connection if(this->state == "connected"){ return(1); } @@ -162,6 +208,123 @@ double BuzzerConn::get(QString key) return(0); } +bool BuzzerConn::refresh() +{ + // function that has to be called frequently to refresh the connection + // check if the extention has been triggered + // sends one command the pending command list + // and calculates the offset once + + if(this->state != "connected"){ + // if the extention is not connected return + return(false); + } + + //send one of the pending commands + if(pending_commands.length() > 0){ + //get the irst command in the list + QString command = this->pending_commands.first(); + //send the command + signed long retval = this->sendCommand(command, 2000); + + if(retval > 0){ + //if the request has been successfull, remove the command from the list + this->pending_commands.removeFirst(); + } + } + + // get the timestamps from the extention + QList ret = this->gettimes(2000); + + if(ret[0] >= 0){ + //if the request has been successfull check if the last_triggered timestamp has changed + if(ret[2] > this->latest_button_pressed){ + // if it has, set it + this->latest_button_pressed = ret[2]; + //and emit the trggered signal + emit triggered(); + } + // as the requst as been sucessfull, reset the error counter + this->errors = 0; + // calculate the offset on time and return + return(this->calcoffset(ret)); + } + else { + // if not add one to the error conter + this->errors ++; + + if(this->errors > errors_until_disconnect){ + // if the error counter is too high, disconnect + this->socket->disconnectFromHost(); + this->setState("disconnected"); + } + + // return false + return(false); + } + +} + +signed long BuzzerConn::sendCommand(QString command, int timeout){ + //function to send a commnd to the extention + + //if there is any data in the storage, clear it + if(this->socket->bytesAvailable() > 0){ + this->socket->readAll(); + } + + //send request to the socket server + QByteArray arrBlock; + QDataStream out(&arrBlock, QIODevice::WriteOnly); + + out << quint16(0) << command; + + out.device()->seek(0); + out << quint16(arrBlock.size() - sizeof(quint16)); + + this->socket->write(arrBlock); + + //now wait for the extention to answer + QEventLoop loop; + QTimer timer; + + timer.setSingleShot(true); + //quit the loop if the timer times out + loop.connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); + //or if the request is anwered + loop.connect(socket, SIGNAL(readyRead()), &loop, SLOT(quit())); + + //start the timer + timer.start(timeout); + //start the loop + loop.exec(); + + //loop finished + if(timer.remainingTime() == -1){ + //the time has been triggered -> timeout + return(-1); + } + // stop the timer + timer.stop(); + + + //if the data is not 4 bytes long it is invalid -> clear and terminate + if(this->socket->bytesAvailable() != 4){ + this->socket->readAll(); + return(-2); + } + long data = 0; + // read four bytes and cast them as a double + this->socket->read((char*)&data,4); + + //return the data + return data; +} + +void BuzzerConn::appendCommand(QString command){ + this->pending_commands.append(command); +} + void BuzzerConn::setIP(const QString &ipAdress){ this->ip = ipAdress; } @@ -195,95 +358,3 @@ double BuzzerConn::getLastTriggered() const { return(this->latest_button_pressed); } - -bool BuzzerConn::refresh() -{ - if(this->state != "connected"){ - return(false); - } - - //send one of the pending commands - if(pending_commands.length() > 0){ - QString command = this->pending_commands.first(); - - signed long retval = this->sendCommand(command, 2000); - if(retval > 0){ - this->pending_commands.removeFirst(); - } - } - - //refresh the times - QList ret = this->gettimes(2000); - if(ret[0] >= 0){ - if(ret[2] > this->latest_button_pressed){ - this->latest_button_pressed = ret[2]; - emit triggered(); - } - this->errors = 0; - return(this->calcoffset(ret)); - } - else { - this->errors ++; - if(this->errors > errors_until_disconnect){ - this->socket->disconnectFromHost(); - this->setState("disconnected"); - } - return(false); - } - -} - -signed long BuzzerConn::sendCommand(QString command, int timeout){ - - //if there is any data in the storage, clear it - if(this->socket->bytesAvailable() > 0){ - this->socket->readAll(); - } - - //send request to the socket server - QByteArray arrBlock; - QDataStream out(&arrBlock, QIODevice::WriteOnly); - //out.setVersion(QDataStream::Qt_5_10); - out << quint16(0) << command; - - out.device()->seek(0); - out << quint16(arrBlock.size() - sizeof(quint16)); - - this->socket->write(arrBlock); - - //now wait for the server of the sensor to answer - QEventLoop loop; - QTimer timer; - - timer.setSingleShot(true); - loop.connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); - loop.connect(socket, SIGNAL(readyRead()), &loop, SLOT(quit())); - timer.start(timeout); - loop.exec(); - - //loop finished - if(timer.remainingTime() == -1){ - //the time has been triggered -> timeout - return(-1); - } - - timer.stop(); - - - //if the data is not 4 bytes long it is invalid -> clear and terminate - if(this->socket->bytesAvailable() != 4){ - this->socket->readAll(); - return(-2); - } - long data = 0; - this->socket->read((char*)&data,4); - - //qDebug() << data; - //qDebug() << this->socket->bytesAvailable(); - - return data; -} - -void BuzzerConn::appendCommand(QString command){ - this->pending_commands.append(command); -} diff --git a/sources/main.cpp b/sources/main.cpp index 17a3cc8..fd6d55f 100644 --- a/sources/main.cpp +++ b/sources/main.cpp @@ -52,6 +52,7 @@ #include "headers/sqlprofilemodel.h" #include "headers/buzzerconn.h" #include "headers/appsettings.h" +#include "headers/baseconn.h" #include static void connectToDatabase() @@ -64,7 +65,7 @@ static void connectToDatabase() } const QDir writeDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - qDebug() << writeDir; + if (!writeDir.mkpath(".")) qFatal("Failed to create writable directory at %s", qPrintable(writeDir.absolutePath())); @@ -116,6 +117,7 @@ int main(int argc, char *argv[]) //setup the startpad and buzzer conn qml objects qmlRegisterType("com.itsblue.speedclimbingstopwatch", 1, 0, "BuzzerConn"); qmlRegisterType("com.itsblue.speedclimbingstopwatch", 1, 0, "StartpadConn"); + qmlRegisterType("com.itsblue.speedclimbingstopwatch", 1, 0, "BaseStationConn"); //setup translation engine //to the language of the system diff --git a/speedclimbing_stopwatch.pro b/speedclimbing_stopwatch.pro index 52665f7..9d66e58 100644 --- a/speedclimbing_stopwatch.pro +++ b/speedclimbing_stopwatch.pro @@ -24,13 +24,15 @@ SOURCES += \ sources/sqlstoragemodel.cpp \ sources/sqlprofilemodel.cpp \ sources/buzzerconn.cpp \ - sources/appsettings.cpp + sources/appsettings.cpp \ + sources/baseconn.cpp HEADERS += \ headers/sqlstoragemodel.h \ headers/sqlprofilemodel.h \ headers/buzzerconn.h \ - headers/appsettings.h + headers/appsettings.h \ + headers/baseconn.h RESOURCES += \ shared.qrc \