diff --git a/apism/apism_client.cpp b/apism/apism_client.cpp new file mode 100644 index 0000000..e10bd93 --- /dev/null +++ b/apism/apism_client.cpp @@ -0,0 +1,750 @@ +#include "apism_client.h" +//#include "support/VendingData.h" +//#include "support/PersistentData.h" +//#include "support/utils.h" +//#include "ATBHMIconfig.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Q_DECLARE_METATYPE(QAbstractSocket::SocketError) + +ApismClient::ApismClient(QObject *eventReceiver, ATBHMIconfig *config, PersistentData *persistentData, QObject *parent) + : QObject(parent) + , healthEventReceiver(eventReceiver) + , m_config(config) + , persistentData(persistentData) + , lastError(0) + , lastErrorDescription("") + , currentRequestUid("") + , currentRequest(ISMAS::REQUEST::NO_REQUEST) +{ + this->apismTcpSendClient = new ApismTcpClient("127.0.0.1", "7777", this); + this->apismTcpRequestResponseClient = new ApismTcpClient("127.0.0.1", "7778", this); + + connect(apismTcpRequestResponseClient, &ApismTcpClient::receivedData, + this, &ApismClient::onReceivedResponse); + connect(apismTcpRequestResponseClient, &ApismTcpClient::responseTimeout, + this, &ApismClient::onRequestResponseClientResponseTimeout); + + connect(apismTcpSendClient, &ApismTcpClient::responseTimeout, + this, &ApismClient::onSendClientResponseTimeout); + + + + // 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))); +} + +ApismClient::~ApismClient() { +} + + + + + +void ApismClient::restartApism() +{ + QProcess::startDetached("/bin/systemctl", {"restart", "apism"}); +} + + + + +//void ApismClient::onReadyRead() { // parse APISM response +// QByteArray data = m_socket.readAll(); +// qCritical() << "APISM-RESPONSE = (" << endl << data << endl << ")"; +//} + +void ApismClient::sendTransaction(const VendingData *vendingData) { + + +#if 0 + QScopedPointer transferData(new ISMAS::TransferData()); + + PAYMENT_VARIANTS::TYPE paymentType = vendingData->getParameter("PaymentType").value(); + + + ////////////////////// DEVICE ////////////////////////////////// + bool deviceSet = false; + //QJsonValue tariffId("TariffInfo"); + //transferData->device.insert("TARIFID", tariffId); + //QJsonValue group("group"); + //transferData->device.insert("GROUP", group); + //QJsonValue zone("zone"); + //transferData->device.insert("ZONE", zone); + if (deviceSet) { + transferData->insert("DEVICE", transferData->device); + } + + + ////////////////////// TRANSACTION ///////////////////////////// + + + bool transactionSet = false; + + if (vendingData->hasParameter("TRANSACTION_STATE")) { + QVariant tstate = vendingData->getParameter("TRANSACTION_STATE"); + if (tstate.isValid() && tstate.type() == QVariant::Int) { + transferData->transaction.state = tstate.toInt(); + transferData->transaction.insert("STATE", transferData->transaction.state); + transactionSet = true; + } + } + if (vendingData->hasParameter("TRANSACTION_UID")) { + QVariant tuid = vendingData->getParameter("TRANSACTION_UID"); + if (tuid.isValid() && tuid.type() == QVariant::String) { + transferData->transaction.uid = tuid.toString(); + transferData->transaction.insert("UID", transferData->transaction.uid); + this->persistentData->setLastTransactionUID(tuid.toString()); + transactionSet = true; + } + } + if (vendingData->hasParameter("TRANSACTION_TIMESTAMP")) { + QVariant tstamp = vendingData->getParameter("TRANSACTION_TIMESTAMP"); + if (tstamp.isValid() && tstamp.type() == QVariant::String) { + transferData->transaction.timestamp = tstamp.toString(); + transferData->transaction.insert("TIMESTAMP", transferData->transaction.timestamp); + transactionSet = true; + } + } + if (vendingData->hasParameter("TRANSACTION_TICKET_SEQUENCE_NUMBER")) { + QVariant tsn = vendingData->getParameter("TRANSACTION_TICKET_SEQUENCE_NUMBER"); + if (tsn.isValid() && tsn.type() == QVariant::Int) { + transferData->transaction.seq_tick_number = tsn.toInt(); + transferData->transaction.insert("TICKETNU", transferData->transaction.seq_tick_number); + transactionSet = true; + } + } + if (vendingData->hasParameter("LICENSEPLATE")) { + QVariant lp = vendingData->getParameter("LICENSEPLATE"); + transferData->transaction.userText = QJsonValue::fromVariant(lp); + transferData->transaction.insert("USERTEXT", transferData->transaction.userText); + transferData->transaction.userTextType = "license plate"; + transferData->transaction.insert("USERTEXTTYPE", transferData->transaction.userTextType); + transactionSet = true; + } + + if (transactionSet) { + transferData->insert("TRANSACTION", transferData->transaction); + } + + + ////////////////////// ITEM ////////////////////////////////// + bool itemSet = false; + + if (vendingData->hasParameter("PermitType")) { + QVariant idVariant = vendingData->getParameter("PermitType"); + transferData->item.id = idVariant.toString(); + transferData->item.insert("ID", transferData->item.id); + itemSet = true; + } + if (vendingData->hasParameter("Product")) { + QVariant nameVariant = vendingData->getParameter("Product"); + transferData->item.name = nameVariant.toString(); + transferData->item.insert("NAME", transferData->item.name); + itemSet = true; + } + if (vendingData->hasParameter("PRICE_INFO_GROSS")) { + int priceUint = vendingData->getUintParameter("PRICE_INFO_GROSS"); + transferData->item.price = priceUint; + transferData->item.insert("PRICE", transferData->item.price); + itemSet = true; + } + if (vendingData->hasParameter("PERIOD_START")) { + QVariant startTimeVariant = vendingData->getParameter("PERIOD_START"); + transferData->item.startTime = utils::getISODateTimeWithMsAndOffset(startTimeVariant); + transferData->item.insert("STARTTIME", transferData->item.startTime); + itemSet = true; + } + if (vendingData->hasParameter("PERIOD_END")) { + QVariant endTimeVariant = vendingData->getParameter("PERIOD_END"); + transferData->item.endTime = utils::getISODateTimeWithMsAndOffset(endTimeVariant); + transferData->item.insert("ENDTIME", transferData->item.endTime); + itemSet = true; + } + if (vendingData->hasParameter("ITEM_PRINT_TEXT")) { + QVariant textVariant = vendingData->getParameter("ITEM_PRINT_TEXT"); + transferData->item.printText = textVariant.toString(); + transferData->item.insert("PRINTTEXT", transferData->item.printText); + itemSet = true; + } + + // set static data: + + // currency + if (itemSet) { + transferData->item.currency = this->m_config->getPaymentCurrencyISOCode(); + transferData->item.insert("CURRENCY", transferData->item.currency); + } + + if (itemSet) { + transferData->insert("ITEM", transferData->item); + } + + //////////////////////////////////////////////////////////////////////// + + + ////////////////////// PAYMENT ////////////////////////////// + bool paymentSet = false; + + /////////////////////////// PAYMENT.CASH ///////////////////////////// + if (paymentType == PAYMENT_VARIANTS::TYPE::CASH) { + bool cashSet = false; + + if (vendingData->hasParameter("PaymentCashCoins")) { + QVariant coins = vendingData->getParameter("PaymentCashCoins"); + transferData->payment.cash.coins = coins.toInt(); + transferData->payment.cash.insert("COINS", transferData->payment.cash.coins); + cashSet = true; + } + if (vendingData->hasParameter("PaymentCashChange")) { + QVariant change = vendingData->getParameter("PaymentCashChange"); + transferData->payment.cash.change = change.toInt(); + transferData->payment.cash.insert("CHANGE", transferData->payment.cash.change); + cashSet = true; + } + if (vendingData->hasParameter("PaymentCashOverpaid")) { + QVariant overpaid = vendingData->getParameter("PaymentCashOverpaid"); + transferData->payment.cash.overpaid = overpaid.toInt(); + transferData->payment.cash.insert("OVERPAID", transferData->payment.cash.overpaid); + cashSet = true; + } + + // currency + if (cashSet) { + transferData->payment.cash.currency = this->m_config->getPaymentCurrencyISOCode(); + transferData->payment.cash.insert("CURRENCY", transferData->payment.cash.currency); + } + + if (cashSet) { + transferData->payment.insert("CASH", transferData->payment.cash); + paymentSet = true; + } + } + + /////////////////////////// PAYMENT.CARD ///////////////////////////// + if (paymentType == PAYMENT_VARIANTS::TYPE::CARD) { + paymentSet = true; + bool cardSet = true; + + + transferData->payment.card.insert("CARDNU", "unknown"); + + transferData->payment.card.insert("VALUE", transferData->item.price); + + transferData->payment.card.insert("CARDTYPE", "unknown"); + + + transferData->payment.card.currency = this->m_config->getPaymentCurrencyISOCode(); + transferData->payment.card.insert("CURRENCY", transferData->payment.card.currency); + + // transferData->payment.card.insert("TERMINALID", tid); + //transferData->payment.card.insert("TERMINALRESULT", tresult); + + if (cardSet) { + transferData->payment.insert("CARD", transferData->payment.card); + paymentSet = true; + } + } + + if (paymentSet) { + transferData->insert("PAYMENT", transferData->payment); + } + + //////////////////////////////////////////////////////////////////////// + + + ///////////////////////////// RESULT ///////////////////////////////// + bool resultSet = false; + + + if (vendingData->hasParameter("RESULT_DELIVERY")) { + QVariant delVariant = vendingData->getParameter("RESULT_DELIVERY"); + transferData->result.delivery = delVariant.toJsonValue(); + transferData->result.insert("DELIVERY", transferData->result.delivery); + resultSet = true; + } + if (vendingData->hasParameter("RESULT_RESULT")) { + QVariant resVariant = vendingData->getParameter("RESULT_RESULT"); + transferData->result.result = resVariant.toJsonValue(); + transferData->result.insert("RESULT", transferData->result.result); + resultSet = true; + } + if (vendingData->hasParameter("RESULT_ERROR_CODE")) { + QVariant ecVariant = vendingData->getParameter("RESULT_ERROR_CODE"); + transferData->result.errorCode = ecVariant.toJsonValue(); + transferData->result.insert("ERRORCODE", transferData->result.errorCode); + resultSet = true; + } + if (vendingData->hasParameter("RESULT_ERROR_MESSAGE")) { + QVariant emsgVariant = vendingData->getParameter("RESULT_ERROR_MESSAGE"); + transferData->result.errorMsg = emsgVariant.toJsonValue(); + transferData->result.insert("ERRORMSG", transferData->result.errorMsg); + resultSet = true; + } + + if (resultSet) { + transferData->insert("RESULT", transferData->result); + } + + //////////////////////////////////////////////////////////////////////// + + + QJsonDocument jsonDoc(*transferData); + QByteArray data = "#M=APISM#C=CMD_TRANSACTION#J="; + data += jsonDoc.toJson(QJsonDocument::Compact); + + this->apismTcpSendClient->sendData(data); +} + + +void ApismClient::sendAccount(const QHash & accountDataHash) +{ + QScopedPointer accountData(new ISMAS::AccountData()); + + accountData->coinBox.UID = QUuid::createUuid().toString(QUuid::WithoutBraces); // .mid(0, 8) + accountData->insert("UID", accountData->coinBox.UID); + + accountData->coinBox.ChangeNumber = QJsonValue(static_cast(this->persistentData->getNewCoinboxChangeNumber())); + accountData->insert("COINBOX_CHANGE_NUMBER", accountData->coinBox.ChangeNumber); + + accountData->coinBox.Process = "COINBOX_CHANGE"; + accountData->insert("PROCESS", accountData->coinBox.Process); + + accountData->insert("StartTime", utils::getISODateTimeWithMsAndOffset(persistentData->getAccountStartTime())); + accountData->insert("EndTime", utils::getCurrentISODateTimeWithMsAndOffset()); + accountData->insert("StartHash", persistentData->getFirstTransactionUID()); + accountData->insert("EndHash", persistentData->getLastTransactionUID()); + + // coins + int numberOfCoinVariants = accountDataHash["NumberOfCoinVariants"].toInt(); + for (int i=0; i < numberOfCoinVariants;++i) { + accountData->coinBox.coin.value = accountDataHash["COIN_" + QString::number(i) + "_Value"].toInt(); + accountData->coinBox.coin.numberOfCoins = accountDataHash["COIN_" + QString::number(i) + "_Quantity"].toInt(); + accountData->coinBox.coin.insert("VALUE", accountData->coinBox.coin.value); + accountData->coinBox.coin.insert("QUANTITY", accountData->coinBox.coin.numberOfCoins); + + if (accountDataHash.contains("COIN_" + QString::number(i) + "_Currency")) { + accountData->coinBox.coin.currency = accountDataHash["COIN_" + QString::number(i) + "_Currency"].toString(); + accountData->coinBox.coin.insert("CURRENCY", accountData->coinBox.coin.numberOfCoins); + } + + accountData->insert("COIN_" + QString::number(i), accountData->coinBox.coin); + } + + QJsonDocument jsonDoc(*accountData); + QByteArray data = "#M=APISM#C=CMD_CashboxChange#J="; + data += jsonDoc.toJson(QJsonDocument::Compact); + + this->apismTcpSendClient->sendData(data); + + this->persistentData->clearForNewAccount(); + + persistentData->serializeToFile(); +} + + + +void ApismClient::sendEvent(const ATBMachineEvent* machineEvent) +{ + QScopedPointer eventData(new ISMAS::EventData()); + + eventData->machineEvent.eventID = machineEvent->eventId; + eventData->insert("EVENT_ID", eventData->machineEvent.eventID); + + eventData->machineEvent.deviceName = machineEvent->deviceName; + eventData->insert("DeviceName", eventData->machineEvent.deviceName); + + eventData->machineEvent.reason = ATBMachineEvent::getEventClassString(machineEvent->machineEventClass); + eventData->insert("Reason", eventData->machineEvent.reason); + + eventData->machineEvent.event = machineEvent->eventName; + eventData->insert("Event", eventData->machineEvent.event); + + eventData->machineEvent.eventState = machineEvent->eventState; + eventData->insert("EventState", eventData->machineEvent.eventState); + + eventData->machineEvent.timeStamp = machineEvent->timestamString; + eventData->insert("Timestamp", eventData->machineEvent.timeStamp); + + eventData->machineEvent.parameter = machineEvent->parameterString; + eventData->insert("Parameter", eventData->machineEvent.parameter); + + eventData->machineEvent.secondLevelInfo = machineEvent->secondLevelInfoString; + eventData->insert("SecondLevelInfo", eventData->machineEvent.secondLevelInfo); + + + QJsonDocument jsonDoc(*eventData); + QByteArray data = "#M=APISM#C=CMD_EVENT#J="; + data += jsonDoc.toJson(QJsonDocument::Compact); + + this->apismTcpSendClient->sendData(data); + +#endif + +} + + +void ApismClient::sendState(const QString & state, const QString & msg) +{ + qCritical() << "ApismClient::sendState(): "; + qCritical() << " state: " << state; + qCritical() << " msg: " << msg; + + + QJsonParseError parseError; + QJsonDocument document(QJsonDocument::fromJson(msg.toUtf8(), &parseError)); + if (parseError.error != QJsonParseError::NoError) { + qCritical() << "ApismClient::sendState() invalid json msg: Parsing failed:" << parseError.error << parseError.errorString(); + return; + } + + if (!document.isObject()) { + qCritical() << "File is not JSON object!"; + return; + } + + + QScopedPointer stateData(new ISMAS::StateData()); + + QJsonObject stateObject = document.object(); + + QJsonArray statesArray; + statesArray.append(stateObject); + + // stateData->insert("TIMESTAMP", utils::getCurrentISODateTimeWithMsAndOffset()); + // stateData->insert("HW_States", statesArray); + + + QJsonDocument jsonDoc(*stateData); + QByteArray data = "#M=APISM#C=CMD_HW_STATUS#J="; + data += jsonDoc.toJson(QJsonDocument::Compact); + + this->apismTcpSendClient->sendData(data); +} + + + + + + + +void ApismClient::sendMininformStartRequest(const VendingData* vendingData) +{ + this->currentRequest = ISMAS::REQUEST::START; + + struct MininFormTransferData : public QJsonObject { + struct : public QJsonObject { + QJsonValue uid; // MUST: uuid -> vendorId + QJsonValue posid; // terminal-id + QJsonValue authCode; // approval-code + QJsonValue stan; // MUST + QJsonValue lpn; + QJsonValue transactionTime; // MUST: Zeitstempel Verkauf + QJsonValue parkingStartTime; // MUST: Startzeit + QJsonValue preAuthAmount; + QJsonValue hourlyRate; + QJsonValue vehicleCategory; + QJsonValue langCode; + QJsonValue zoneCode; + } startAction; + }; + + QScopedPointer transferData(new MininFormTransferData()); + +#if 0 + transferData->startAction.uid = vendingData->getParameter("START_UID").toJsonValue(); + this->currentRequestUid = vendingData->getParameter("START_UID").toString(); + transferData->startAction.insert("UID", transferData->startAction.uid); + + transferData->startAction.insert("POSID", vendingData->getParameter("START_POSID").toString()); + + transferData->startAction.insert("AUTHCODE", vendingData->getParameter("START_AuthCode").toString()); + + transferData->startAction.insert("STAN", vendingData->getParameter("START_STAN").toString()); + + transferData->startAction.insert("LPN", vendingData->getParameter("LICENSEPLATE").toString()); + + transferData->startAction.insert("TRANSACTIONTIME", utils::getISODateTimeWithMsAndOffset(vendingData->getParameter("START_TRANSACTIONTIME").toString())); + + transferData->startAction.insert("STARTTIME", utils::getISODateTimeWithMsAndOffset(vendingData->getParameter("START_STARTTIME").toString())); + + transferData->startAction.insert("AMOUNT", static_cast(vendingData->getUintParameter("PRICE_INFO_GROSS"))); + + transferData->startAction.insert("RATE_H", static_cast(vendingData->getUintParameter("PRICE_INFO_RATE_H"))); + + transferData->startAction.insert("VEHICLETYPE", "01"); // Fixed value + + transferData->startAction.insert("LANGUAGE", "HUN"); // Fixed value + + transferData->startAction.insert("ZONE", vendingData->getParameter("MININFORM_ZONE").toString()); + + transferData->insert("STARTACTION", transferData->startAction); + + QJsonDocument jsonDoc(*transferData); + QByteArray data = "#M=APISM#C=REQ_START#J="; // REQ_... -> use port 7778 + data += jsonDoc.toJson(QJsonDocument::Compact); + + this->apismTcpRequestResponseClient->sendData(data); +} + +void ApismClient::sendMininformStopRequest(const VendingData* vendingData) +{ + this->currentRequest = ISMAS::REQUEST::STOP; + + struct MininFormTransferData : public QJsonObject { + struct : public QJsonObject { + QJsonObject uid; // MUST: uuid + QJsonObject posid; // terminal-id + QJsonObject stan; // MUST + QJsonObject lpn; // MUST + QJsonObject stopTime; // MUST: Stop-Zeit + QJsonObject langCode; + QJsonObject deviceId; + } stopAction; + }; + + QScopedPointer transferData(new MininFormTransferData()); + + this->currentRequestUid = QUuid::createUuid().toString(QUuid::WithoutBraces).mid(0, 8); + transferData->stopAction.insert("UID", this->currentRequestUid); + + transferData->stopAction.insert("POSID", vendingData->getParameter("STOP_POSID").toString()); + + transferData->stopAction.insert("STAN", vendingData->getParameter("STOP_STAN").toString()); + + transferData->stopAction.insert("LPN", vendingData->getParameter("LICENSEPLATE").toString()); + + transferData->stopAction.insert("STOPTIME", utils::getISODateTimeWithMsAndOffset(vendingData->getParameter("STOP_STOPTIME"))); + + transferData->stopAction.insert("LANGUAGE", "HUN"); // Fixed value + + transferData->stopAction.insert("DEVICE_ID", this->m_config->getMachineNr()); + + transferData->insert("STOPACTION", transferData->stopAction); + + QJsonDocument jsonDoc(*transferData); + QByteArray data = "#M=APISM#C=REQ_STOP#J="; // REQ_ -> use port 7778 + data += jsonDoc.toJson(QJsonDocument::Compact); + + this->apismTcpRequestResponseClient->sendData(data); +#endif +} + + +void ApismClient::sendSelfTest() +{ + this->currentRequest = ISMAS::REQUEST::SELF; + + QByteArray data = "#M=APISM#C=REQ_SELF#J={}"; + + this->apismTcpRequestResponseClient->sendData(data); +} + +void ApismClient::sendMininformPingRequest() +{ + this->currentRequest = ISMAS::REQUEST::PING; + + QByteArray data = "#M=APISM#C=REQ_Ping#J={\"281\":\"PING\"}"; + + this->apismTcpRequestResponseClient->sendData(data); +} + + + +void ApismClient::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(); + 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("^REQ_START.*")) >= 0) { + this->private_handleMininformStartResponse(rootObject["Response"].toObject()); + } + else + if(rootObjectKeys.indexOf(QRegularExpression("^REQ_STOP.*")) >= 0) { + this->private_handleMininformStopResponse(rootObject["Response"].toObject()); + } + else + if(rootObjectKeys.indexOf(QRegularExpression("^REQ_SELF.*")) >= 0) { + this->private_handleReqSelfResponse(rootObject["Response"].toObject()); + } + else + if(rootObjectKeys.indexOf(QRegularExpression("^REQ_PING.*")) >= 0) { + this->private_handleReqPingResponse(rootObject["PING"].toObject()); + } + else { + qCritical() << "ApismClient::onReceivedResponse() for unknown Request: "; + qCritical() << " currentRequestName: " << currentRequest; + qCritical() << " rootObject.keys(): " << rootObjectKeys; + this->handleISMASResponseError(); + return; + } + + this->currentRequest = ISMAS::REQUEST::NO_REQUEST; +} + +void ApismClient::handleISMASResponseError() +{ + switch (this->currentRequest) { + case ISMAS::REQUEST::NO_REQUEST: + qCritical() << "ApismClient::onReceivedResponse() for unknown Request: " << currentRequest; + break; + case ISMAS::REQUEST::START: + emit this->sendMininformStartResponse(nsApismInterface::RESULT_STATE::ERROR_BACKEND, QJsonObject()); + break; + case ISMAS::REQUEST::STOP: + emit this->sendMininformStopResponse(nsApismInterface::RESULT_STATE::ERROR_BACKEND, QJsonObject()); + 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; + } + this->currentRequest = ISMAS::REQUEST::NO_REQUEST; +} + +/* +{\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::onRequestResponseClientResponseTimeout() +{ + switch (this->currentRequest) { + case ISMAS::REQUEST::NO_REQUEST: + qCritical() << "ApismClient::onRequestResponseClientResponseTimeout() for unknown Request: " << currentRequest; + break; + case ISMAS::REQUEST::START: + emit this->sendMininformStartResponse(nsApismInterface::RESULT_STATE::ERROR_TIMEOUT, QJsonObject()); + break; + case ISMAS::REQUEST::STOP: + emit this->sendMininformStopResponse(nsApismInterface::RESULT_STATE::ERROR_TIMEOUT, QJsonObject()); + 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; + } + + this->currentRequest = ISMAS::REQUEST::NO_REQUEST; +} + + +void ApismClient::private_handleMininformStartResponse(QJsonObject response) +{ + emit this->sendMininformStartResponse(nsApismInterface::RESULT_STATE::SUCCESS, response); +} + +void ApismClient::private_handleMininformStopResponse(QJsonObject response) +{ + emit this->sendMininformStopResponse(nsApismInterface::RESULT_STATE::SUCCESS, response); +} + +void ApismClient::private_handleReqSelfResponse(QJsonObject response) +{ + emit this->sendReqSelfResponse(nsApismInterface::RESULT_STATE::SUCCESS, response); +} + +void ApismClient::private_handleReqPingResponse(QJsonObject response) +{ + emit this->sendMininformPingResponse(nsApismInterface::RESULT_STATE::SUCCESS, response); +} + + + + +/************************************************************************************************ + * 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::START: + debug << QString("ISMAS::REQUEST::START"); + break; + case ISMAS::REQUEST::STOP: + debug << QString("ISMAS::REQUEST::STOP"); + break; + case ISMAS::REQUEST::PING: + debug << QString("ISMAS::REQUEST::PING"); + break; + case ISMAS::REQUEST::SELF: + debug << QString("ISMAS::REQUEST::SELF"); + 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::START: + str = QString("ISMAS::REQUEST::START"); + break; + case ISMAS::REQUEST::STOP: + str = QString("ISMAS::REQUEST::STOP"); + break; + case ISMAS::REQUEST::PING: + str = QString("ISMAS::REQUEST::PING"); + break; + case ISMAS::REQUEST::SELF: + str = QString("ISMAS::REQUEST::SELF"); + break; + } + return str; +} + diff --git a/apism/apism_client.h b/apism/apism_client.h new file mode 100644 index 0000000..4ef3f66 --- /dev/null +++ b/apism/apism_client.h @@ -0,0 +1,120 @@ +#ifndef APISM_CLIENT_H_INCLUDED +#define APISM_CLIENT_H_INCLUDED + + +#include "ismas_data.h" +#include "apism_tcp_client.h" + +#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 VendingData; +class ATBHMIconfig; +class PersistentData; +class ATBMachineEvent; +class ApismClient : public QObject { + Q_OBJECT + +public: + explicit ApismClient(QObject *eventReceiver, ATBHMIconfig *config, PersistentData *persistentData, QObject *parent = 0); + ~ApismClient(); + + quint32 getLastError(); + const QString & getLastErrorDescription(); + +public slots: + void sendSelfTest(); + void sendTransaction(const VendingData* vendingData); + void sendAccount(const QHash &accountDataHash); + void sendEvent(const ATBMachineEvent* machineEvent); + + void sendState(const QString & state, const QString & msg); + + + void sendMininformStartRequest(const VendingData* vendingData); + void sendMininformStopRequest(const VendingData* vendingData); + void sendMininformPingRequest(); + + void restartApism(); + +#ifdef USE_SZEGED_START_STOP + +#endif + +signals: + // public signals: + void sendTransactionRespones(nsApismInterface::RESULT_STATE result); + void sendAccountResponse(nsApismInterface::RESULT_STATE result); + + + void sendMininformStartResponse(nsApismInterface::RESULT_STATE result, QJsonObject response); + void sendMininformStopResponse(nsApismInterface::RESULT_STATE result, QJsonObject response); + void sendMininformPingResponse(nsApismInterface::RESULT_STATE result, QJsonObject response); + void sendReqSelfResponse(nsApismInterface::RESULT_STATE result, QJsonObject response); + + +private slots: + // void onSocketError(QAbstractSocket::SocketError socketError); + + void onReceivedResponse(QByteArray response); + + void onSendClientResponseTimeout(); + void onRequestResponseClientResponseTimeout(); + +private: + QObject *healthEventReceiver; + ATBHMIconfig *m_config; + + PersistentData *persistentData; + + ApismTcpClient* apismTcpSendClient; + ApismTcpClient* apismTcpRequestResponseClient; + + quint32 lastError; + QString lastErrorDescription; + + QString currentRequestUid; + ISMAS::REQUEST currentRequest; + + + void private_handleMininformStartResponse(QJsonObject response); + void private_handleMininformStopResponse(QJsonObject response); + void private_handlePingResponse(QJsonObject response); + void private_handleReqSelfResponse(QJsonObject response); + void private_handleReqPingResponse(QJsonObject response); + + void handleISMASResponseError(); + +}; + +// Q_DECLARE_METATYPE(QAbstractSocket::SocketError) + +#endif // APISM_CLIENT_H_INCLUDED diff --git a/apism/apism_tcp_client.cpp b/apism/apism_tcp_client.cpp new file mode 100644 index 0000000..2fde29d --- /dev/null +++ b/apism/apism_tcp_client.cpp @@ -0,0 +1,166 @@ +#include "apism_tcp_client.h" + +#include +#include + +ApismTcpClient::ApismTcpClient(const QString & hostname, + const QString & port, + QObject *parent) + : QObject(parent) + , hostname(hostname) + , port(port) + , responseTimerTimeoutCounter(0) +{ + this->responseTimeoutTimer = new QTimer(this); + this->responseTimeoutTimer->setInterval(10000); + this->responseTimeoutTimer->setSingleShot(true); + + connect(this->responseTimeoutTimer, SIGNAL(timeout()), this, SLOT(onResponseTimeoutTimerTimeout())); + + 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))); +} + + + +void ApismTcpClient::connectToHost() +{ + int portNumber = this->port.toInt(); + this->socket->connectToHost(QHostAddress(this->hostname), portNumber); +} + +void ApismTcpClient::connectToHost(const QString & hostname, const QString & port) +{ + qCritical() << "ApismTcpClient::connectToHost(" << hostname << ", " << port << ")"; + + int portNumber = port.toInt(); + socket->connectToHost(hostname, portNumber); +} + +void ApismTcpClient::closeConnection() +{ + 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; +} + + + + +void ApismTcpClient::sendData(const QByteArray & message) +{ + //qCritical() << "ApismTcpClient::send: " << message; + + this->sendQueue.enqueue(message); + + if (this->isConnected()) { + this->private_sendData(); + + } + else { + this->connectToHost(); + } +} + +/** + * @brief ApismTcpClient::private_sendData + * + * Precondition is that queue is not empty. + */ +void ApismTcpClient::private_sendData() +{ + // take message from queue + QByteArray ba = this->sendQueue.dequeue(); + + qCritical() << "ApismTcpClient::send: " << QString(ba); + + socket->write(ba); + socket->flush(); + + // start timeoutTimer + this->responseTimeoutTimer->start(); +} + +void ApismTcpClient::onSocketConnected() +{ + qCritical() << "ApismTcpClient: Connected!"; + + if (this->sendQueue.size() > 0) { + this->private_sendData(); + } +} + +void ApismTcpClient::onSocketDisconnected() +{ + qCritical() << "ApismTcpClient: Disconnected!"; + qCritical() << " -> SocketErrorString: " << socket->errorString(); + + if (this->sendQueue.size() > 0) { + this->connectToHost(); + } +} + +void ApismTcpClient::onSocketBytesWritten(qint64 bytes) +{ + Q_UNUSED(bytes) +} + +void ApismTcpClient::onSocketReadyRead() +{ + QByteArray readData; + + // stop timeoutTimer + this->responseTimeoutTimer->stop(); + + readData = socket->readAll(); + + qCritical() << "ISMAS received: " << QString(readData); + + emit this->receivedData(readData); + + this->socket->close(); +} + + +void ApismTcpClient::onResponseTimeoutTimerTimeout() +{ + if (this->sendQueue.size() == 0) { + return; + } + emit this->responseTimeout(); + + qCritical() << "ApismTcpClient::onResponseTimeoutTimerTimeout() --> skip this message, send next command, if available."; + + // Try next command + this->sendQueue.removeFirst(); + if (this->sendQueue.size() > 0) this->private_sendData(); +} diff --git a/apism/apism_tcp_client.h b/apism/apism_tcp_client.h new file mode 100644 index 0000000..2b2de10 --- /dev/null +++ b/apism/apism_tcp_client.h @@ -0,0 +1,62 @@ +#ifndef APISMTCPCLIENT_H +#define APISMTCPCLIENT_H + +#include + +#include +#include +#include +#include + +class QTimer; + +class ApismTcpClient : public QObject +{ + Q_OBJECT +public: + explicit ApismTcpClient(const QString & hostname, const QString & port, QObject *parent = nullptr); + bool isConnected(); + + void connectToHost(); + void connectToHost(const QString & hostname, const QString & port); + + // socket is implicitely closed by APISM + void closeConnection(); + + void sendData(const QByteArray & message); + +public slots: + // socket interface + void onSocketConnected(); + void onSocketDisconnected(); + void onSocketBytesWritten(qint64 bytes); + void onSocketReadyRead(); + +signals: + void receivedData(QByteArray response); + + void responseTimeout(); + +private: + QTcpSocket *socket; + + QQueue sendQueue; + + + QString hostname; + QString port; + + QTimer *responseTimeoutTimer; + quint8 responseTimerTimeoutCounter; + + + void private_sendData(); + + +public slots: + void onResponseTimeoutTimerTimeout(); + + +}; + +#endif // APISMTCPCLIENT_H diff --git a/apism/ismas_data.h b/apism/ismas_data.h new file mode 100644 index 0000000..22d8c7f --- /dev/null +++ b/apism/ismas_data.h @@ -0,0 +1,136 @@ +#ifndef ISMASDATA_H +#define ISMASDATA_H + +#include +#include + + + + +namespace ISMAS { + + struct TransferData : public QJsonObject { + struct : public QJsonObject { + QJsonValue tariffId; + QJsonValue group; + QJsonValue zone; + } device; + + struct : public QJsonObject { + QJsonValue state; + QJsonValue uid; + QJsonValue seq_tick_number; + QJsonValue timestamp; + QJsonValue userText; + QJsonValue userTextType; + } transaction; + + struct : public QJsonObject { + // TODO: check what is really used at the moment + QJsonValue id; // unique article id + QJsonValue name; // name + QJsonValue price; // price in cent + QJsonValue currency; // + QJsonValue startTime; // start time + QJsonValue endTime; // end time + QJsonValue userText; // additional info + QJsonValue parkingTime; + QJsonValue printText; + // QJsonValue discount; + } item; + + struct : public QJsonObject { + struct : public QJsonObject { + QJsonValue coins; // total amount of coins value + // QJsonValue notes; // total amount of notes value + QJsonValue overpaid; // in cent + QJsonValue currency; + QJsonValue change; + } cash; + + struct : public QJsonObject { + QJsonValue cardNumber; + QJsonValue value; // buchungsbetrag + QJsonValue cardType; + QJsonValue currency; + QJsonValue tid; + QJsonValue tresult; + } card; + + struct : public QJsonObject { + QJsonValue cardNumber; + QJsonValue cardType; + QJsonValue value; + QJsonValue valueOld; + QJsonValue valueNew; + QJsonValue time; + QJsonValue timeOld; + QJsonValue timeNew; + } prePaidCard; + } payment; + + struct : public QJsonObject { + QJsonValue delivery; // PRINT, OnlineTicket + QJsonValue result; // SUCCESS, ERROR + QJsonValue errorCode; // 0=OK, 1=... + QJsonValue errorMsg; + } result; + }; + + + struct AccountData : public QJsonObject { + struct : public QJsonObject { + QJsonValue UID; + QJsonValue ChangeNumber; + QJsonValue Process; // Vorgang + QJsonValue startDateTime; + QJsonValue endDateTime; + QJsonValue startHash; + QJsonValue endHash; + + struct : public QJsonObject { + QJsonValue value; // coin value + QJsonValue numberOfCoins; // number of coins + QJsonValue currency; + } coin; + + } coinBox; // Münzkasse + }; + + + struct EventData : public QJsonObject { + struct : public QJsonObject { + QJsonValue eventID; + QJsonValue deviceName; + QJsonValue reason; + QJsonValue event; + QJsonValue eventState; + QJsonValue timeStamp; + QJsonValue parameter; + QJsonValue secondLevelInfo; + } machineEvent; // + }; + + 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, + START, + STOP, + PING, + SELF + }; +} + + + +#endif // ISMASDATA_H