diff --git a/common/include/utils_internal.h b/common/include/utils_internal.h index eaf3c75..070f38a 100644 --- a/common/include/utils_internal.h +++ b/common/include/utils_internal.h @@ -31,6 +31,16 @@ namespace internal { static constexpr const char *GIT_MARKER{""}; static constexpr const char *ISMAS_MARKER{""}; + static constexpr const int PERCENT_CHECK_ISMAS_CONNECIVITY{10}; + static constexpr const int PERCENT_CHECK_UPDATE_REQUEST{20}; + static constexpr const int PERCENT_CHECK_CUSTOMER_REPOSITORY{30}; + static constexpr const int PERCENT_INSTALL_SW_PACKETS_NOACTION{40}; + static constexpr const int PERCENT_INSTALL_SW_PACKETS{50}; + static constexpr const int PERCENT_INSTALL_DC_CONFIGURATION{60}; + static constexpr const int PERCENT_SYNCHRONIZE_REPO_AND_FILESYS{70}; + static constexpr const int PERCENT_UPDATE_DC{80}; + static constexpr const int PERCENT_SHOW_FINAL_STATUS{90}; + int read1stLineOfFile(QString fileName); QString customerRepoRoot(); QString customerRepoDir(); diff --git a/common/ismas/ApismClient.cpp b/common/ismas/ApismClient.cpp new file mode 100644 index 0000000..1c5d4b5 --- /dev/null +++ b/common/ismas/ApismClient.cpp @@ -0,0 +1,336 @@ +#include "ApismClient.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +ApismClient::ApismClient(QObject *parent) + : QObject(parent) + , lastError(0) + , lastErrorDescription("") + , currentRequest(ISMAS::REQUEST::NO_REQUEST) + , retryCounter(0) + , flagValidParameter(false) +{ + this->apismTcpSendClient.reset(new ApismTcpClient("127.0.0.1", "7777", ApismTcpClient::ExpectedResponse::STATE, this)); + this->apismTcpRequestResponseClient.reset(new ApismTcpClient("127.0.0.1", "7778", ApismTcpClient::ExpectedResponse::JSON, this)); + + this->apismTcpRequestResponseClient->setResponseTimeout(30000); + + connect(apismTcpRequestResponseClient.get(), &ApismTcpClient::receivedData, + this, &ApismClient::onReceivedResponse); + connect(apismTcpRequestResponseClient.get(), &ApismTcpClient::responseTimeout, + this, &ApismClient::onRequestResponseClientResponseTimeout); + connect(apismTcpRequestResponseClient.get(), &ApismTcpClient::connectTimeout, + this, &ApismClient::onRequestResponseClientConnectTimeout); + connect(apismTcpRequestResponseClient.get(), &ApismTcpClient::connectionClosedByRemoteHost, + this, &ApismClient::onRequestResponseClientConnectionClosedByRemoteHost); + connect(apismTcpRequestResponseClient.get(), &ApismTcpClient::connectionRefusedError, + this, &ApismClient::restartApism); + + connect(apismTcpSendClient.get(), &ApismTcpClient::responseTimeout, + this, &ApismClient::onSendClientResponseTimeout); + connect(apismTcpSendClient.get(), &ApismTcpClient::connectionRefusedError, + this, &ApismClient::restartApism); + + + // not needed as APISM closes the socket after we send data, so readyRead() + // might not even fire + // connect(&m_socket, SIGNAL(readyRead()), this, SLOT(onReadyRead())); + + // defined for Qt >= 5.15, we have 5.12 + // qRegisterMetaType(); + // connect(&m_socket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), + // this, SLOT(onSocketError(&QAbstractSocket::SocketError))); + + // switch of DEBUG: + this->apismTcpSendClient->setDebug(true); + this->apismTcpRequestResponseClient->setDebug(true); +} + +ApismClient::~ApismClient() { +} + +void ApismClient::restartApism() { + QProcess::startDetached("/bin/systemctl", {"restart", "apism"}); +} + + +void ApismClient::sendSelfTest() { + this->currentRequest = ISMAS::REQUEST::SELF; + this->apismTcpRequestResponseClient->sendData("#M=APISM#C=REQ_SELF#J={}"); +} + +void ApismClient::sendRequestParameter() { + this->currentRequest = ISMAS::REQUEST::PARAMETER; + this->apismTcpRequestResponseClient->sendData("#M=APISM#C=REQ_ISMASPARAMETER#J={}"); +} + +void ApismClient::handleISMASResponseError() +{ + switch (this->currentRequest) { + case ISMAS::REQUEST::NO_REQUEST: + qCritical() << "ApismClient::onReceivedResponse() for unknown Request: " << currentRequest; + break; + case ISMAS::REQUEST::PING: + //emit this->sendMininformPingResponse(nsApismInterface::RESULT_STATE::ERROR_BACKEND, QJsonObject()); + break; + case ISMAS::REQUEST::SELF: + //emit this->sendReqSelfResponse(nsApismInterface::RESULT_STATE::ERROR_BACKEND, QJsonObject()); + break; + case ISMAS::REQUEST::PARAMETER: + //emit this->sendReqParameterResponse(nsApismInterface::RESULT_STATE::ERROR_BACKEND, QJsonObject()); + break; + } + this->currentRequest = ISMAS::REQUEST::NO_REQUEST; +} + + +void ApismClient::handleISMASOfflineResponseError() +{ + nsApismInterface::RESULT_STATE resultState; + + if (this->retryCounter < 50) { + resultState = nsApismInterface::RESULT_STATE::ERROR_RETRY; + this->retryCounter++; + } + else { + resultState = nsApismInterface::RESULT_STATE::ERROR_BACKEND; + this->retryCounter = 0; + } + + qCritical() << "ApismClient::handleISMASOfflineResponseError(): currentRequest is " << this->currentRequest; + + + switch (this->currentRequest) { + case ISMAS::REQUEST::NO_REQUEST: + break; + case ISMAS::REQUEST::PING: + //emit this->sendMininformPingResponse(resultState, QJsonObject()); + //break; + case ISMAS::REQUEST::SELF: + emit this->sendReqSelfResponse(resultState, QJsonObject()); + break; + case ISMAS::REQUEST::PARAMETER: + emit this->sendReqParameterResponse(resultState, QJsonObject()); + break; + } + this->currentRequest = ISMAS::REQUEST::NO_REQUEST; + + // note: this does not work here, because a new request within this error handling method + // must use a new socket connection! + // This does not work with an allready open socket connection. + /* Communication to APISM must be in the following way: + * 1) establish socket connection + * 2) send your request to this socket + * 3) wait for a possible answer + * 4) receive the answer + * 5) close socket connection + * + * => a request can only be sent to apsim if the socket is closed! + * => apism (socket) is blocked systemwide until socket is closed! + * => + */ + + //this->sendSelfTest(); +} + + + +void ApismClient::private_handleErrorResponse(QString errorString) +{ + qCritical() << "------------------------------------------------------"; + qCritical() << "ApismClient::private_handleErrorResponse(" << errorString << ")"; + qCritical() << " this->retryCounter = " << this->retryCounter; + qCritical() << "------------------------------------------------------"; + + this->handleISMASOfflineResponseError(); +} + + +/* +{\r\n \"REQ_START#53365_Response\": {\r\n \"Aknoledge\": \"OK\"\r\n }, + \r\n \"Response\": {\r\n \"Result\": \"ERR:Ung�ltiger Datums-String: \"\r\n }\r\n} +*/ + +/* +: ISMAS received: "{\r\n \"REQ_PING#30844_Response\": {\r\n \"Aknoledge\": \"OK\"\r\n }, + "PING": { "IsAviable": "TRUE" } + }" +*/ + +//void ApismClient::onSendClientResponseTimeout() +//{ +// +//} + +void ApismClient::private_handleReqSelfResponse(QJsonObject response) +{ + bool ismasConnected = response["ISMAS"].toBool(); + QString brokerConnectedString = response["Broker"].toString(); + + qCritical() << "ApismClient::handleReqSelfResponse() " << endl + << " ismasConnected = " << ismasConnected << endl + << " brokerConnectedString = " << brokerConnectedString; + + /* possible values: "Restart connection" + * "Connected" + * "NOT CONNECTED" + */ + if (brokerConnectedString == "NOT CONNECTED") { + qCritical() << "ApismClient: \"NOT CONNECTED\": restart APISM"; + this->restartApism(); + } + + emit this->sendReqSelfResponse(nsApismInterface::RESULT_STATE::SUCCESS, response); +} + +/** + * sample parameter response: + { + "REQ_ISMASPARAMETER#4210959_Response": { + "Aknoledge": "OK" + }, + "Dev_ID": { + "Device_Type": "2020", + "Custom_ID": 332, + "Device_ID": 99 + }, + "Fileupload": { + "TRG": "" + }, + "Parameter": { + "Location": "An Der Bahn 11", + "Group": "Group1", + "Zone": "Zone1", + "Name": "PSA", + "SecNumber": "0", + "LastAcc": "0", + "GPSLat": "49.6062", + "GPSLon": "12.1244", + "TarifID": "none", + "UserTextID": "1" + "TERMINALID": "", + "TERMINALKEY": "" + }, + "Lock": { + "Locked": "0" + } + } + */ +void ApismClient::private_handleParameterResponse(QJsonObject response) +{ + // extract location info and store location info in persistent data: + QJsonValue jsonSubVal_Location; + jsonSubVal_Location = response["Location"]; + + QString locationString = jsonSubVal_Location.toString("no location"); + + //this->persistentData->setParameter("Location", locationString); + + // feature UserText + QJsonValue jsonSubVal_UserText; + jsonSubVal_UserText = response["UserTextID"]; + QString userTextIdString = jsonSubVal_UserText.toString("0"); + //this->persistentData->setParameter("UserTextID", userTextIdString); + + // TERMINALID + //QJsonValue jsonSubVal_TERMINALID; + //jsonSubVal_TERMINALID = response["TERMINALID"]; + //QString terminalIdString = jsonSubVal_TERMINALID.toString(""); + //QString terminalIdFile = this->m_config->getSystemDataPath() + "TERMINALID"; + //QString terminalIdStringOrigin = ATBSystem::readPSAConfigString(terminalIdFile); + //if ( (terminalIdString.size() > 1) && + // (terminalIdString != terminalIdStringOrigin)) + //{ + // qCritical() << "ApismClient::handleParameterResponse(): new TERMINALID: " << terminalIdString; + // ATBSystem::setPSAConfigString(terminalIdFile, terminalIdString); + //} + + // TERMINALKEY + //QJsonValue jsonSubVal_TERMINALKEY; + //jsonSubVal_TERMINALKEY = response["TERMINALKEY"]; + //QString terminalKeyString = jsonSubVal_TERMINALKEY.toString(""); + //QString terminalJeyFile = this->m_config->getSystemDataPath() + "TERMINALKEY"; + //QString terminalKeyStringOrigin = ATBSystem::readPSAConfigString(terminalJeyFile); + //if ( (terminalKeyString.size() > 1) && + // (terminalKeyString != terminalKeyStringOrigin)) + //{ + // qCritical() << "ApismClient::handleParameterResponse(): new TERMINALKEY: " << terminalKeyString; + // + + // ATBSystem::setPSAConfigString(terminalJeyFile, terminalKeyString); + //} + + + //qCritical() << "ApismClient::handleParameterResponse() " << endl + // << " Location = " << locationString << endl + // << " UserTextID = " << userTextIdString << endl + // << " TERMINALID = " << terminalIdString << endl + // << " TERMINALKEY = " << terminalKeyString; + + + if (locationString != "no location") { + this->flagValidParameter = true; + emit this->sendReqParameterResponse(nsApismInterface::RESULT_STATE::SUCCESS, response); + } +} + +void ApismClient::private_handleFileUploadResponse(QJsonObject response) { + QJsonValue jsonSubVal_TRG{response["TRG"]}; + if (jsonSubVal_TRG.isUndefined()) { + return; + } + + bool updateRequested = (jsonSubVal_TRG.toString("") == "WAIT"); + +} + +/************************************************************************************************ + * operators + */ +QDebug operator<< (QDebug debug, ISMAS::REQUEST request) +{ + switch(request) { + case ISMAS::REQUEST::NO_REQUEST: + debug << QString("ISMAS::REQUEST::NO_REQUEST"); + break; + case ISMAS::REQUEST::PING: + debug << QString("ISMAS::REQUEST::PING"); + break; + case ISMAS::REQUEST::SELF: + debug << QString("ISMAS::REQUEST::SELF"); + break; + case ISMAS::REQUEST::PARAMETER: + debug << QString("ISMAS::REQUEST::PARAMETER"); + break; + } + return debug; +} + +QString& operator<< (QString& str, ISMAS::REQUEST request) +{ + switch(request) { + case ISMAS::REQUEST::NO_REQUEST: + str = QString("ISMAS::REQUEST::NO_REQUEST"); + break; + case ISMAS::REQUEST::PING: + str = QString("ISMAS::REQUEST::PING"); + break; + case ISMAS::REQUEST::SELF: + str = QString("ISMAS::REQUEST::SELF"); + break; + case ISMAS::REQUEST::PARAMETER: + str = QString("ISMAS::REQUEST::PARAMETER"); + break; + } + return str; +} + diff --git a/common/ismas/ApismClient.h b/common/ismas/ApismClient.h new file mode 100644 index 0000000..ff75b39 --- /dev/null +++ b/common/ismas/ApismClient.h @@ -0,0 +1,99 @@ +#ifndef APISM_CLIENT_H_INCLUDED +#define APISM_CLIENT_H_INCLUDED + +#include "ISMASData.h" +#include "ApismTcpClient.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +QDebug operator<<(QDebug debug, ISMAS::REQUEST request); +QString& operator<<(QString& str, ISMAS::REQUEST request); + +namespace nsApismInterface { + enum class RESULT_STATE : quint8 { + SUCCESS = 1, + ERROR_BACKEND = 2, // error from backand (e.g. backend replies with error) + ERROR_NETWORK = 3, // error from network (e.g. host not available) + ERROR_TIMEOUT = 4, // the operation timed out + ERROR_PROCESS = 5, // internal plugin error (e.g. bug in implementation) + ERROR_RETRY = 6, // retry operation + INFO = 7 + }; +} + +// class ISMAS::EventData; +class ApismClient : public QObject { + Q_OBJECT + +public: + explicit ApismClient(QObject *parent = 0); + virtual ~ApismClient() = 0; + + quint32 getLastError(); + const QString & getLastErrorDescription(); + + ISMAS::REQUEST getCurrentRequest() const { return currentRequest; } + void setCurrentRequest(ISMAS::REQUEST r) { currentRequest = r; } + + void resetRetryCounter() { retryCounter = 0; } + int getRetryCounter() const { return retryCounter; } + int incrRetryCounter() { return ++retryCounter; } + + ApismTcpClient *tcpSendClient() { return apismTcpSendClient.get(); } + ApismTcpClient *tcpRequestResponseClient() { return apismTcpRequestResponseClient.get(); } + +public slots: + void sendSelfTest(); + void sendRequestParameter(); + void restartApism(); + +signals: + void sendReqSelfResponse(nsApismInterface::RESULT_STATE result, QJsonObject response); + void sendReqParameterResponse(nsApismInterface::RESULT_STATE result, QJsonObject response); + +private slots: + virtual void onReceivedResponse(QByteArray response) = 0; + virtual void onSendClientResponseTimeout() = 0; + virtual void onRequestResponseClientResponseTimeout() = 0; + virtual void onRequestResponseClientConnectTimeout() = 0; + virtual void onRequestResponseClientConnectionClosedByRemoteHost() = 0; + +private: + QScopedPointer apismTcpSendClient; + QScopedPointer apismTcpRequestResponseClient; + + quint32 lastError; + QString lastErrorDescription; + + ISMAS::REQUEST currentRequest; + + // counter, incremented if we get an offline response from ISMAS + // causes a resend of the currentRequest. + int retryCounter; + + // true, if ISMAS REQ_ISAMASPARAMETER got a valid response + bool flagValidParameter; + + void private_handlePingResponse(QJsonObject response); + void private_handleReqSelfResponse(QJsonObject response); + void private_handleReqPingResponse(QJsonObject response); + void private_handleParameterResponse(QJsonObject response); + void private_handleFileUploadResponse(QJsonObject response); + void private_handleErrorResponse(QString errorString); + +public: + void handleISMASResponseError(); + void handleISMASOfflineResponseError(); +}; + +#endif // APISM_CLIENT_H_INCLUDED diff --git a/common/ismas/ApismClientForUpdate.cpp b/common/ismas/ApismClientForUpdate.cpp new file mode 100644 index 0000000..99fe6e6 --- /dev/null +++ b/common/ismas/ApismClientForUpdate.cpp @@ -0,0 +1,216 @@ +#include "ApismClientForUpdate.h" + +#include + +ApismClientForUpdate::ApismClientForUpdate(QObject *parent) + : ApismClient(parent) { + +} + +ApismClientForUpdate::~ApismClientForUpdate() { + +} + +void ApismClientForUpdate::sendCmdEvent(ISMAS::EventData eventData) { + // QScopedPointer eventData(new ISMAS::EventData()); + /* + struct EventData : public QJsonObject { + struct : public QJsonObject { + QJsonValue reason; + QJsonValue timestamp; + QJsonValue eventID; + QJsonValue event; + QJsonValue eventState; + struct : public QJsonObject { + QJsonValue percent; + QJsonValue resultCode; + QJsonValue step; + QJsonValue stepResult; + QJsonValue Version; + } parameter; + } newsToIsmas; + }; + + "\"REASON\":\"SW_UP\"," + "\"TIMESTAMP\":\"%s\"," + "\"EVENT_ID\":\"0\"," + "\"EVENT\":\"%s\"," + "\"EVENTSTATE\":1," + "\"PARAMETER\": {" + "\"PERCENT\" : %d," + "\"RESULTCODE\" : %d," + "\"STEP\" : \"%s\"," + "\"STEP_RESULT\" : \"%s\"," + "\"VERSION\" : \"%s\"" + "}" + "}", ts.toStdStr + +*/ + //eventData->newsToIsmas.reason = data.newsToIsmas; + eventData.insert("REASON", eventData.newsToIsmas.reason); + eventData.insert("TIMESTAMP", eventData.newsToIsmas.timestamp); + eventData.insert("EVENT_ID", eventData.newsToIsmas.eventID); + eventData.insert("EVENT", eventData.newsToIsmas.event); + eventData.insert("EVENT_STATE", eventData.newsToIsmas.eventState); + + QJsonObject parameterJsonObject; + parameterJsonObject.insert("PERCENT", eventData.newsToIsmas.parameter.percent); + parameterJsonObject.insert("RESULTCODE", eventData.newsToIsmas.parameter.resultCode); + parameterJsonObject.insert("STEP", eventData.newsToIsmas.parameter.step); + parameterJsonObject.insert("STEP_RESULT", eventData.newsToIsmas.parameter.stepResult); + parameterJsonObject.insert("VERSION", eventData.newsToIsmas.parameter.version); + + eventData.insert("PARAMETER", parameterJsonObject); + + qCritical() << __func__ << ":" << __LINE__ << eventData; + + QJsonDocument jsonDoc(eventData); + QByteArray data = "#M=APISM#C=CMD_EVENT#J="; + data += jsonDoc.toJson(QJsonDocument::Compact); + + qCritical() << __func__ << ":" << __LINE__ << data; + + tcpSendClient()->sendData(data); +} + + +void ApismClientForUpdate::onReceivedResponse(QByteArray response) { + // get the root object + QJsonParseError jsonParseError; + QJsonDocument responseDoc = QJsonDocument::fromJson(response, &jsonParseError); + + if (jsonParseError.error != QJsonParseError::NoError) { + qCritical() << "ApismClient::onReceivedResponse() response is no json data:"; + qCritical() << " Error: " << jsonParseError.errorString(); + + // workaround for REQ_SELF and offline + if (this->getCurrentRequest() == ISMAS::REQUEST::SELF) { + if (response.contains("Connecting...")) { + qCritical() << " -> Connecting..."; + return; + } + if (response.contains("ISMAS is offline")) { + this->restartApism(); + qCritical() << " -> Workaround: restart APISM"; + return; + } + } + + if (response.contains("ISMAS is offline")) { + this->handleISMASOfflineResponseError(); + return; + } + else { + this->handleISMASResponseError(); + return; + } + } + + QJsonObject rootObject = responseDoc.object(); + QStringList rootObjectKeys = rootObject.keys(); + + // DEBUG + qCritical() << "ApismClient::onReceivedResponse(): objects: " << rootObjectKeys; + // results to: + // ApismClient::onReceivedResponse(): objects: ("REQ_START#60044_Response", "Response") + + if(rootObjectKeys.indexOf(QRegularExpression("^CMD_GET_APISMSTATUS.*")) >= 0) { + resetRetryCounter(); + //this->private_handleReqSelfResponse(rootObject["CMD_GET_APISMSTATUS_RESPONSE#0"].toObject()); + } + else + if(rootObjectKeys.indexOf(QRegularExpression("^REQ_ISMASPARAMETER.*")) >= 0) { + resetRetryCounter(); + //this->private_handleParameterResponse(rootObject["Parameter"].toObject()); + } + else + if(rootObjectKeys.indexOf(QRegularExpression("^REQ_PING.*")) >= 0) { + resetRetryCounter(); + //this->private_handleReqPingResponse(rootObject["PING"].toObject()); + } + else + if(rootObjectKeys.indexOf(QRegularExpression("^error")) >= 0) { + // handle error objects + //this->private_handleErrorResponse(rootObject["error"].toString()); + } + else { + qCritical() << "ApismClient::onReceivedResponse() for unknown Request: "; + qCritical() << " currentRequestName: " << getCurrentRequest(); + qCritical() << " rootObject.keys(): " << rootObjectKeys; + this->handleISMASResponseError(); + return; + } + + this->setCurrentRequest(ISMAS::REQUEST::NO_REQUEST); +} + +void ApismClientForUpdate::onSendClientResponseTimeout() { + +} + +void ApismClientForUpdate::onRequestResponseClientResponseTimeout() { + qCritical() << "ApismClient::onRequestResponseClientResponseTimeout(): currentRequest is " << this->getCurrentRequest(); + + switch (this->getCurrentRequest()) { + case ISMAS::REQUEST::NO_REQUEST: + break; + case ISMAS::REQUEST::PING: + //emit this->sendMininformPingResponse(nsApismInterface::RESULT_STATE::ERROR_TIMEOUT, QJsonObject()); + break; + case ISMAS::REQUEST::SELF: + //emit this->sendReqSelfResponse(nsApismInterface::RESULT_STATE::ERROR_TIMEOUT, QJsonObject()); + break; + case ISMAS::REQUEST::PARAMETER: + //emit this->sendReqParameterResponse(nsApismInterface::RESULT_STATE::ERROR_TIMEOUT, QJsonObject()); + break; + } + + this->setCurrentRequest(ISMAS::REQUEST::NO_REQUEST); +} + +void ApismClientForUpdate::onRequestResponseClientConnectTimeout() { + + qCritical() << "ApismClient::onRequestResponseClientConnectTimeout(): currentRequest is " << this->getCurrentRequest(); + + nsApismInterface::RESULT_STATE resultState; + + resultState = nsApismInterface::RESULT_STATE::ERROR_BACKEND; + + switch (this->getCurrentRequest()) { + case ISMAS::REQUEST::NO_REQUEST: + qCritical() << "ApismClient::onRequestResponseClientConnectTimeout() for unknown Request: " << getCurrentRequest(); + break; + case ISMAS::REQUEST::PING: + //emit this->sendMininformPingResponse(resultState, QJsonObject()); + break; + case ISMAS::REQUEST::SELF: + //emit this->sendReqSelfResponse(resultState, QJsonObject()); + break; + case ISMAS::REQUEST::PARAMETER: + //emit this->sendReqParameterResponse(resultState, QJsonObject()); + break; + } + + this->setCurrentRequest(ISMAS::REQUEST::NO_REQUEST); +} + +void ApismClientForUpdate::onRequestResponseClientConnectionClosedByRemoteHost() { + nsApismInterface::RESULT_STATE resultState = nsApismInterface::RESULT_STATE::ERROR_BACKEND; + + switch (this->getCurrentRequest()) { + case ISMAS::REQUEST::NO_REQUEST: + qCritical() << "ApismClient::onRequestResponseClientConnectionClosedByRemoteHost() for unknown Request: " << getCurrentRequest(); + break; + case ISMAS::REQUEST::PING: + //emit this->sendMininformPingResponse(resultState, QJsonObject()); + break; + case ISMAS::REQUEST::SELF: + emit this->sendReqSelfResponse(resultState, QJsonObject()); + break; + case ISMAS::REQUEST::PARAMETER: + emit this->sendReqParameterResponse(resultState, QJsonObject()); + break; + } + + this->setCurrentRequest(ISMAS::REQUEST::NO_REQUEST); +} diff --git a/common/ismas/ApismClientForUpdate.h b/common/ismas/ApismClientForUpdate.h new file mode 100644 index 0000000..437b818 --- /dev/null +++ b/common/ismas/ApismClientForUpdate.h @@ -0,0 +1,24 @@ +#ifndef APISM_CLIENT_FOR_UPDATE_H_INCLUDED +#define APISM_CLIENT_FOR_UPDATE_H_INCLUDED + +#include "ApismClient.h" + +class ApismClientForUpdate : public ApismClient { +public: + explicit ApismClientForUpdate(QObject *parent = 0); + virtual ~ApismClientForUpdate(); + +public slots: + void sendCmdEvent(ISMAS::EventData); + +private slots: + virtual void onReceivedResponse(QByteArray response) override; + virtual void onSendClientResponseTimeout() override; + virtual void onRequestResponseClientResponseTimeout() override; + virtual void onRequestResponseClientConnectTimeout() override; + virtual void onRequestResponseClientConnectionClosedByRemoteHost() override; + +}; + +#endif // APISM_CLIENT_FOR_UPDATE_H_INCLUDED + diff --git a/common/ismas/ApismTcpClient.cpp b/common/ismas/ApismTcpClient.cpp new file mode 100644 index 0000000..2d51189 --- /dev/null +++ b/common/ismas/ApismTcpClient.cpp @@ -0,0 +1,564 @@ +#include "ApismTcpClient.h" + +#include +#include + +ApismMessage::ApismMessage() : state(MessageState::INVALID) { } + + +ApismTcpClient::ApismTcpClient(const QString & hostname, + const QString & port, + ExpectedResponse expectedResponse, + QObject *parent) + : QObject(parent) + , hostname(hostname) + , port(port) + , responseTimerTimeoutCounter(0) + , flag_selfClosed(false) + , resendCounter(0) + , connectionRefusedCounter(0) + , expectedResponse(expectedResponse) + , isDebug(false) +{ + this->responseTimeoutTimer = new QTimer(this); + this->responseTimeoutTimer->setInterval(10000); + this->responseTimeoutTimer->setSingleShot(true); + + connect(this->responseTimeoutTimer, SIGNAL(timeout()), this, SLOT(onResponseTimeoutTimerTimeout())); + + this->connectTimeoutTimer = new QTimer(this); + this->connectTimeoutTimer->setInterval(10000); + this->connectTimeoutTimer->setSingleShot(true); + + connect(this->connectTimeoutTimer, SIGNAL(timeout()), this, SLOT(onConnectTimeoutTimerTimeout())); + + socket = new QTcpSocket(this); + connect(socket, SIGNAL(connected()), this, SLOT(onSocketConnected())); + connect(socket, SIGNAL(disconnected()), this, SLOT(onSocketDisconnected())); + connect(socket, SIGNAL(readyRead()), this, SLOT(onSocketReadyRead())); + connect(socket, SIGNAL(bytesWritten(qint64)), this, SLOT(onSocketBytesWritten(qint64))); + // note: from Qt 5.15 onward a new signal "errorOccurred" will be introduced which could be used whithout this static_cast. + // see e.g. https://stackoverflow.com/questions/35655512/compile-error-when-connecting-qtcpsocketerror-using-the-new-qt5-signal-slot + connect(socket, static_cast(&QAbstractSocket::error), this, &ApismTcpClient::onSocketErrorOccured); + connect(socket, &QTcpSocket::stateChanged, this, &ApismTcpClient::onSocketStateChanged); + + this->currentMessage = ApismMessage(); +} + + +void ApismTcpClient::setResponseTimeout(const quint32 timeout_ms) +{ + this->responseTimeoutTimer->setInterval(timeout_ms); +} + +void ApismTcpClient::setDebug(bool debug) +{ + this->isDebug = debug; +} + +void ApismTcpClient::connectToHost() +{ + this->connectTimeoutTimer->start(); + int portNumber = this->port.toInt(); + this->socket->connectToHost(QHostAddress(this->hostname), portNumber); +} + +void ApismTcpClient::connectToHost(const QString & hostname, const QString & port) +{ + qCritical() << "ApismTcpClient(" << this->expectedResponse << ")::connectToHost(" << hostname << ", " << port << ")"; + + this->connectTimeoutTimer->start(); + int portNumber = port.toInt(); + socket->connectToHost(hostname, portNumber); +} + +void ApismTcpClient::closeConnection() +{ + this->flag_selfClosed = true; + socket->close(); +} + +bool ApismTcpClient::isConnected() +{ + bool result = false; + QAbstractSocket::SocketState socketState = socket->state(); + + switch (socketState) { + case QAbstractSocket::UnconnectedState: + /* FALLTHRU */ + case QAbstractSocket::HostLookupState: + /* FALLTHRU */ + case QAbstractSocket::ConnectingState: + result = false; + break; + case QAbstractSocket::ConnectedState: + /* FALLTHRU */ + case QAbstractSocket::BoundState: + result = true; + break; + case QAbstractSocket::ClosingState: + /* FALLTHRU */ + case QAbstractSocket::ListeningState: + result = false; + break; + } + + return result; +} + + + +/** + * @brief ApismTcpClient::sendData + * @param message + * + * Enqueue message, and try to send it + */ +void ApismTcpClient::sendData(const QByteArray &message) +{ + if (this->isDebug) { + qCritical() << "ApismTcpClient::sendData(" << message << ")"; + } + + this->sendQueue.enqueue(message); + + this->sendData(); +} + +/** + * @brief ApismTcpClient::sendData + * + * Check connection and try to send message from queue. + */ +void ApismTcpClient::sendData() +{ + if (this->sendQueue.size() == 0) { + if (this->isDebug) { + qCritical() << "ApismTcpClient::sendData()" << "no messages in send queue"; + } + return; + } + + // DEBUG + if (this->isDebug) { + qCritical() << "ApismTcpClient(" << this->expectedResponse << ")::sendData() sendQueue.size() = " << this->sendQueue.size(); + } + + switch (this->currentMessage.state) { + case ApismMessage::MessageState::INVALID: + /* FALLTHROUGH */ + case ApismMessage::MessageState::NEW: + /* FALLTHROUGH */ + case ApismMessage::MessageState::RESEND: + /* FALLTHROUGH */ + case ApismMessage::MessageState::ANSWERED: + // allow send message + if (this->isConnected()) { + this->private_sendData(); + } + else { + this->connectToHost(); + } + break; + case ApismMessage::MessageState::SENT: + // wait for answer... + if (this->isDebug) { + qCritical() << " ... wait for answer"; + } + break; + } +} + +/** + * @brief ApismTcpClient::private_sendData + * + * Precondition: + * - queue is not empty, + * - socket state is connected + * - current message is ANSWERED or INVALID + */ +void ApismTcpClient::private_sendData() +{ + // take message from queue + this->currentMessage.data = this->sendQueue.dequeue(); + this->currentMessage.state = ApismMessage::MessageState::SENT; + + qCritical() << "ApismTcpClient(" << this->expectedResponse << ")::send: " << QString(this->currentMessage.data); + + socket->write(this->currentMessage.data); + socket->flush(); + + // start timeoutTimer + this->responseTimeoutTimer->start(); +} + + +void ApismTcpClient::onSocketConnected() +{ + qCritical() << "ApismTcpClient(" << this->expectedResponse << "): Connected!"; + + this->connectTimeoutTimer->stop(); + this->connectionRefusedCounter = 0; + + switch (this->currentMessage.state) { + case ApismMessage::MessageState::INVALID: + /* FALLTHROUGH */ + case ApismMessage::MessageState::NEW: + /* FALLTHROUGH */ + case ApismMessage::MessageState::RESEND: + /* FALLTHROUGH */ + case ApismMessage::MessageState::ANSWERED: + // allow send next message + if (this->sendQueue.size() > 0) { + this->private_sendData(); + } + break; + case ApismMessage::MessageState::SENT: + // wait for answer... + break; + } +} + +void ApismTcpClient::onSocketDisconnected() +{ + qCritical() << "ApismTcpClient(" << this->expectedResponse << "): Disconnected!"; + + if (!this->flag_selfClosed) { + qCritical() << " -> SocketErrorString: " << socket->errorString(); + } + this->flag_selfClosed = false; + + if ( (socket->error() == QAbstractSocket::SocketError::RemoteHostClosedError) && + (this->responseTimeoutTimer->isActive()) ) + { + this->responseTimeoutTimer->stop(); + qCritical() << " -> still waiting for response "; + + switch (this->expectedResponse) { + case ApismTcpClient::ExpectedResponse::STATE: + // try resend: + this->currentMessage.state = ApismMessage::MessageState::RESEND; + // enqeue current message for resend: + this->sendQueue.prepend(this->currentMessage.data); + + this->sendData(); + break; + case ApismTcpClient::ExpectedResponse::JSON: + this->currentMessage.state = ApismMessage::MessageState::INVALID; + emit this->connectionClosedByRemoteHost(); + break; + } + } +} + +void ApismTcpClient::onSocketBytesWritten(qint64 bytes) +{ + if (this->isDebug) { + qCritical() << "ApismTcpClient(" << this->expectedResponse << ")::onSocketBytesWritten() -> " << bytes << " bytes written"; + } +} + +void ApismTcpClient::onSocketReadyRead() +{ + QByteArray readData; + + // stop timeoutTimer + this->responseTimeoutTimer->stop(); + + readData = socket->readAll(); + + qCritical() << "ApismTcpClient(" << this->expectedResponse << ")::received: " << QString(readData); + + switch (this->expectedResponse) { + case ApismTcpClient::ExpectedResponse::JSON: + this->private_handleJSONResponse(readData); + break; + case ApismTcpClient::ExpectedResponse::STATE: + this->private_handleStateResponse(readData); + break; + } + + + if (this->sendQueue.size() > 0) { + QTimer::singleShot(1000, + this, + [this]() { this->sendData(); } + ); + } + else { + this->flag_selfClosed = true; + this->socket->close(); + } +} + +/****************************************************************************** + * response handler: + */ +void ApismTcpClient::private_handleJSONResponse(QByteArray & responseMessage) +{ + emit this->receivedData(responseMessage); + + // allow send next message: + this->currentMessage.state = ApismMessage::MessageState::ANSWERED; + this->resendCounter = 0; +} + +/* possible answers: + * "RECORD SAVED" --> everything is ok + * "RECORD WRITE ABORTED" --> initiate a (delayed) resend + * + */ +void ApismTcpClient::private_handleStateResponse(QByteArray & responseMessage) +{ + QString responseMessageString = QString(responseMessage); + + if (responseMessageString.contains("ABORTED")) { + // Try to resend later: + this->currentMessage.state = ApismMessage::MessageState::RESEND; + // enqeue current message for resend: + this->sendQueue.prepend(this->currentMessage.data); + } + else + if (responseMessageString.contains("RECORD SAVED")) { + // allow send next message: + this->currentMessage.state = ApismMessage::MessageState::ANSWERED; + this->resendCounter = 0; + } +} + +/****************************************************************************** + */ + + + + + +void ApismTcpClient::onResponseTimeoutTimerTimeout() +{ + qCritical() << "ApismTcpClient(" << this->expectedResponse << ")::onResponseTimeoutTimerTimeout():"; + + + switch (this->currentMessage.state) { + case ApismMessage::MessageState::INVALID: + /* FALLTHROUGH */ + case ApismMessage::MessageState::NEW: + /* FALLTHROUGH */ + case ApismMessage::MessageState::ANSWERED: + // ignore + qCritical() << " -> ignore timeout"; + return; + break; + case ApismMessage::MessageState::RESEND: + qCritical() << " -> timeout (RESEND)"; + qCritical() << " -> resendCounter = " << this->resendCounter; + switch (this->expectedResponse) { + case ApismTcpClient::ExpectedResponse::STATE: + // try resend: + this->currentMessage.state = ApismMessage::MessageState::RESEND; + // enqeue current message for resend: + this->sendQueue.prepend(this->currentMessage.data); + + this->sendData(); + break; + case ApismTcpClient::ExpectedResponse::JSON: + this->currentMessage.state = ApismMessage::MessageState::INVALID; + emit this->responseTimeout(); + break; + } + break; + case ApismMessage::MessageState::SENT: + // we still do not have a response: + + switch (this->expectedResponse) { + case ApismTcpClient::ExpectedResponse::STATE: + // try resend: + this->currentMessage.state = ApismMessage::MessageState::RESEND; + // enqeue current message for resend: + this->sendQueue.prepend(this->currentMessage.data); + + this->sendData(); + break; + case ApismTcpClient::ExpectedResponse::JSON: + this->currentMessage.state = ApismMessage::MessageState::INVALID; + emit this->responseTimeout(); + break; + } + + + break; + } + + // count resends + this->resendCounter++; +} + + + +void ApismTcpClient::onConnectTimeoutTimerTimeout() +{ + if (this->sendQueue.size() == 0) { + return; + } + + qCritical() << "ApismTcpClient(" << this->expectedResponse << ")::onConnectTimeoutTimerTimeout() -> sendQueue.size() = " << this->sendQueue.size(); + + emit this->connectTimeout(); +} + + +void ApismTcpClient::onSocketStateChanged(QAbstractSocket::SocketState socketState) +{ + QString msg; + + switch (socketState) { + case QAbstractSocket::UnconnectedState: + msg = "UnconnectedState"; + break; + case QAbstractSocket::HostLookupState: + msg = "HostLookupState"; + break; + case QAbstractSocket::ConnectingState: + msg = "ConnectingState"; + break; + case QAbstractSocket::ConnectedState: + msg = "ConnectedState"; + break; + case QAbstractSocket::BoundState: + msg = "BoundState"; + break; + case QAbstractSocket::ClosingState: + msg = "ClosingState"; + break; + case QAbstractSocket::ListeningState: + msg = "ListeningState"; + break; + } + + if (this->isDebug) { + qCritical() << "ApismTcpClient(" << this->expectedResponse << ")::onSocketStateChanged() to: " << msg; + } +} + +void ApismTcpClient::onSocketErrorOccured(QAbstractSocket::SocketError socketError) +{ + QString msg; + bool flagReconnect = false; + + switch (socketError) { + case QAbstractSocket::ConnectionRefusedError: + msg = "ConnectionRefusedError"; + flagReconnect = true; + this->private_handleConnectionRefusedError(); + break; + case QAbstractSocket::RemoteHostClosedError: + msg = "RemoteHostClosedError"; + break; + case QAbstractSocket::HostNotFoundError: + msg = "HostNotFoundError"; + break; + case QAbstractSocket::SocketAccessError: + msg = "SocketAccessError"; + break; + case QAbstractSocket::SocketResourceError: + msg = "SocketResourceError"; + break; + case QAbstractSocket::SocketTimeoutError: + msg = "SocketTimeoutError"; + break; + case QAbstractSocket::DatagramTooLargeError: + msg = "DatagramTooLargeError"; + break; + case QAbstractSocket::NetworkError: + msg = "NetworkError"; + break; + case QAbstractSocket::AddressInUseError: + msg = "AddressInUseError"; + break; + case QAbstractSocket::SocketAddressNotAvailableError: + msg = "SocketAddressNotAvailableError"; + break; + case QAbstractSocket::UnsupportedSocketOperationError: + msg = "UnsupportedSocketOperationError"; + break; + case QAbstractSocket::ProxyAuthenticationRequiredError: + msg = "ProxyAuthenticationRequiredError"; + break; + case QAbstractSocket::SslHandshakeFailedError: + msg = "SslHandshakeFailedError"; + break; + case QAbstractSocket::UnfinishedSocketOperationError: + msg = "UnfinishedSocketOperationError"; + break; + case QAbstractSocket::ProxyConnectionRefusedError: + msg = "ProxyConnectionRefusedError"; + break; + case QAbstractSocket::ProxyConnectionClosedError: + msg = "ProxyConnectionClosedError"; + break; + case QAbstractSocket::ProxyConnectionTimeoutError: + msg = "ProxyConnectionTimeoutError"; + break; + case QAbstractSocket::ProxyNotFoundError: + msg = "ProxyNotFoundError"; + break; + case QAbstractSocket::ProxyProtocolError: + msg = "ProxyProtocolError"; + break; + case QAbstractSocket::OperationError: + msg = "OperationError"; + break; + case QAbstractSocket::SslInternalError: + msg = "SslInternalError"; + break; + case QAbstractSocket::SslInvalidUserDataError: + msg = "SslInvalidUserDataError"; + break; + case QAbstractSocket::TemporaryError: + msg = "TemporaryError"; + break; + case QAbstractSocket::UnknownSocketError: + msg = "UnknownSocketError"; + break; + + } + + qCritical() << "ApismTcpClient(" << this->expectedResponse << ")::ApismTcpClient::onSocketErrorOccured() -> " << msg; + + if (flagReconnect) { + // try to reconnect for sending: + if (this->sendQueue.size() > 0) { + QTimer::singleShot(1000, + this, + [this]() { this->connectToHost(); } + ); + } + } +} + + +void ApismTcpClient::private_handleConnectionRefusedError() +{ + if (this->connectionRefusedCounter > 5) { + qCritical() << "ApismTcpClient(" << this->expectedResponse << ")::ApismTcpClient::connectionRefusedError()"; + this->connectionRefusedCounter = 0; + emit this->connectionRefusedError(); + } + else { + this->connectionRefusedCounter++; + } +} + + + +QDebug operator<<(QDebug debug, ApismTcpClient::ExpectedResponse response) +{ + switch (response) { + case ApismTcpClient::ExpectedResponse::JSON: + debug << "JSON"; + break; + case ApismTcpClient::ExpectedResponse::STATE: + debug << "STATE"; + break; + } + return debug; +} diff --git a/common/ismas/ApismTcpClient.h b/common/ismas/ApismTcpClient.h new file mode 100644 index 0000000..4d82dbf --- /dev/null +++ b/common/ismas/ApismTcpClient.h @@ -0,0 +1,116 @@ +#ifndef APISMTCPCLIENT_H +#define APISMTCPCLIENT_H + +#include + +#include +#include +#include +#include + +class QTimer; + + +class ApismMessage +{ +public: + + enum class MessageState { + INVALID, + NEW, + SENT, + ANSWERED, + RESEND + }; + MessageState state; + + QByteArray data; + + ApismMessage(); +}; + + + + +class ApismTcpClient : public QObject +{ + Q_OBJECT + +public: + enum class ExpectedResponse { + JSON, + STATE + }; + + explicit ApismTcpClient(const QString & hostname, const QString & port, ExpectedResponse expectedResponse, QObject *parent = nullptr); + bool isConnected(); + + void connectToHost(); + void connectToHost(const QString & hostname, const QString & port); + void setResponseTimeout(const quint32 timeout_ms); + + // socket is implicitely closed by APISM + void closeConnection(); + + void sendData(const QByteArray & message); + void sendData(); + + void setDebug(bool debug); + +private slots: + // socket interface + void onSocketConnected(); + void onSocketDisconnected(); + void onSocketBytesWritten(qint64 bytes); + void onSocketReadyRead(); + void onSocketStateChanged(QAbstractSocket::SocketState socketState); + void onSocketErrorOccured(QAbstractSocket::SocketError socketError); + +signals: + void receivedData(QByteArray response); + + void responseTimeout(); + void connectTimeout(); + void connectionClosedByRemoteHost(); + void connectionRefusedError(); + +private: + QTcpSocket *socket; + + QQueue sendQueue; + ApismMessage currentMessage; + + QString hostname; + QString port; + + QTimer *responseTimeoutTimer; + quint8 responseTimerTimeoutCounter; + + QTimer *connectTimeoutTimer; + + void private_sendData(); + bool flag_selfClosed; + + int resendCounter; + int connectionRefusedCounter; + + ExpectedResponse expectedResponse; + + void private_handleJSONResponse(QByteArray & responseMessage); + void private_handleTextResponse(QByteArray & responseMessage); + void private_handleStateResponse(QByteArray & responseMessage); + + void private_handleConnectionRefusedError(); + + bool isDebug; + +public slots: + void onResponseTimeoutTimerTimeout(); + void onConnectTimeoutTimerTimeout(); + +}; + +QDebug operator<<(QDebug debug, ApismTcpClient::ExpectedResponse response); + + +#endif // APISMTCPCLIENT_H diff --git a/common/ismas/ISMASData.h b/common/ismas/ISMASData.h new file mode 100644 index 0000000..f0c650e --- /dev/null +++ b/common/ismas/ISMASData.h @@ -0,0 +1,106 @@ +#ifndef ISMASDATA_H +#define ISMASDATA_H + +#include +#include +#include + +namespace ISMAS { + + static QString computeTimeStamp() { + QDateTime const local(QDateTime::currentDateTime()); + + QDateTime utc(local); + utc.setTimeSpec(Qt::UTC); + + int const diff = (int)local.secsTo(utc); // diff between UTC and local time + + QTime t(0, 0, 0); + t = t.addSecs((uint)diff); + + QString const st(QString("%1%2").arg(diff < 0 ? "-" : "+").arg(t.toString("hh:mm"))); + return QString (local.toString(Qt::ISODateWithMs) + st); + } + +// +// Note: +// ! After U0002 immer ein CMD_SENDVERSION +// ! Only U0002 and U0003 finish the Update process. +// ! U0001: Update finished but not activated +// ! U0002: Update finished and activated +// ! U0003: Update finished but FAILed. +// +// #define _ISMAS_DONE "U0001" // 100%, Check: Resultcode: 0 +// #define _ISMAS_SET_WAIT_OK "U0002" // empty WAIT-button (""), ResultCode: 0 +// #define _ISMAS_NO_UPDATE_NECESSARY "M0100" // empty WAIT-button (""), ResultCode: 0 +// #define _ISMAS_FAILURE "U0003" // FAIL +// #define _ISMAS_CONTINUE "U0010" // %-values: update running, result codes according running step +// #define _ISMAS_RESET_WAIT "ISMAS" // reset WAIT-button to "WAIT" +// #define _ISMAS_TEST_TRIGGER "U0099" // check the WAIT-button + + static constexpr const char *DONE {"U0001"}; + static constexpr const char *SET_WAIT_OK {"U0002"}; + static constexpr const char *NO_UPDATE_NECESSARY{"M0100"}; + static constexpr const char *FAILURE {"U0003"}; + static constexpr const char *CONTINUE {"U0010"}; + static constexpr const char *RESET_WAIT {"ISMAS"}; + static constexpr const char *TEST_TRIGGER {"U0099"}; + + struct EventData : public QJsonObject { + struct : public QJsonObject { + QJsonValue reason; + QJsonValue timestamp; + QJsonValue eventID; + QJsonValue event; + QJsonValue eventState; + struct : public QJsonObject { + QJsonValue percent; + QJsonValue resultCode; + QJsonValue step; + QJsonValue stepResult; + QJsonValue version; + } parameter; + } newsToIsmas; + + explicit EventData(QString const &event, + int percent, + int resultCode, + QString const &step, + QString const &stepResult, + QString const &version = "", + QString const &reason = "SW_UP") { + newsToIsmas.reason = reason; + newsToIsmas.timestamp = computeTimeStamp(); + newsToIsmas.eventID = QString{"0"}; + newsToIsmas.event = event; + newsToIsmas.eventState = 1; + newsToIsmas.parameter.percent = percent; + newsToIsmas.parameter.resultCode = resultCode; + newsToIsmas.parameter.step = step; + newsToIsmas.parameter.stepResult = stepResult; + newsToIsmas.parameter.version = version; + } + }; + + struct StateData : public QJsonObject { + QJsonValue Timestamp; + QJsonArray HW_States; + struct : public QJsonObject { + QJsonValue name; + QJsonValue value; + QJsonValue unit; + } machineState; // + }; + + + enum class REQUEST : quint8 { + NO_REQUEST, + PING, + SELF, + PARAMETER + }; +} + + + +#endif // ISMASDATA_H