// SPDX-License-Identifier: GPL-3.0-or-later /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright (C) 2013 - 2024, nymea GmbH * Copyright (C) 2024 - 2025, chargebyte austria GmbH * * This file is part of nymea-plugins. * * nymea-plugins is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * nymea-plugins is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with nymea-plugins. If not, see . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "integrationpluginawattar.h" #include "plugininfo.h" #include #include #include #include #include #include IntegrationPluginAwattar::IntegrationPluginAwattar() { m_serverUrls[awattarATThingClassId] = "https://api.awattar.com/v1/marketdata"; m_serverUrls[awattarDEThingClassId] = "https://api.awattar.de/v1/marketdata"; m_connectedStateTypeIds[awattarATThingClassId] = awattarATConnectedStateTypeId; m_connectedStateTypeIds[awattarDEThingClassId] = awattarDEConnectedStateTypeId; m_currentMarketPriceStateTypeIds[awattarATThingClassId] = awattarATCurrentMarketPriceStateTypeId; m_currentMarketPriceStateTypeIds[awattarDEThingClassId] = awattarDECurrentMarketPriceStateTypeId; m_validUntilStateTypeIds[awattarATThingClassId] = awattarATValidUntilStateTypeId; m_validUntilStateTypeIds[awattarDEThingClassId] = awattarDEValidUntilStateTypeId; m_averagePriceStateTypeIds[awattarATThingClassId] = awattarATAveragePriceStateTypeId; m_averagePriceStateTypeIds[awattarDEThingClassId] = awattarDEAveragePriceStateTypeId; m_lowestPriceStateTypeIds[awattarATThingClassId] = awattarATLowestPriceStateTypeId; m_lowestPriceStateTypeIds[awattarDEThingClassId] = awattarDELowestPriceStateTypeId; m_highestPriceStateTypeIds[awattarATThingClassId] = awattarATHighestPriceStateTypeId; m_highestPriceStateTypeIds[awattarDEThingClassId] = awattarDEHighestPriceStateTypeId; m_averageDeviationStateTypeIds[awattarATThingClassId] = awattarATAverageDeviationStateTypeId; m_averageDeviationStateTypeIds[awattarDEThingClassId] = awattarDEAverageDeviationStateTypeId; } IntegrationPluginAwattar::~IntegrationPluginAwattar() { } void IntegrationPluginAwattar::setupThing(ThingSetupInfo *info) { qCDebug(dcAwattar) << "Setup thing" << info->thing()->name() << info->thing()->params(); if (!m_pluginTimer) { m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(60 * 60); connect(m_pluginTimer, &PluginTimer::timeout, this, &IntegrationPluginAwattar::onPluginTimer); } requestPriceData(info->thing(), info); } void IntegrationPluginAwattar::thingRemoved(Thing *thing) { Q_UNUSED(thing) if (m_pluginTimer && myThings().isEmpty()) { hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); m_pluginTimer = nullptr; } } void IntegrationPluginAwattar::onPluginTimer() { foreach (Thing *thing, myThings()) { requestPriceData(thing); } } void IntegrationPluginAwattar::requestPriceData(Thing* thing, ThingSetupInfo *setup) { QNetworkRequest request(QUrl(m_serverUrls.value(thing->thingClassId()))); request.setSslConfiguration(QSslConfiguration::defaultConfiguration()); QNetworkReply *reply = hardwareManager()->networkManager()->get(request); connect(reply, &QNetworkReply::finished, thing, [this, reply, thing, setup](){ reply->deleteLater(); // check HTTP status code int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (status != 200) { qCWarning(dcAwattar) << "Update reply HTTP error:" << status << reply->errorString(); if (setup) { setup->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Error getting data from server.")); } else { thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), false); } return; } // check JSON file QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll(), &error); if (error.error != QJsonParseError::NoError) { qCWarning(dcAwattar) << "Update reply JSON error:" << error.errorString(); if (setup) { setup->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The server returned unexpected data.")); } else { thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), false); } return; } if (setup) { setup->finish(Thing::ThingErrorNoError); } thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), true); processPriceData(thing, jsonDoc.toVariant().toMap()); }); } void IntegrationPluginAwattar::processPriceData(Thing *thing, const QVariantMap &data) { QVariantList dataElements = data.value("data").toList(); QDateTime currentTime = QDateTime::currentDateTime(); double sum = 0; double count = 0; double averagePrice = 0; double currentPrice = 0; int deviation = 0; double maxPrice = -1000; double minPrice = 1000; QList prices; for (int i = 0; i < dataElements.count(); i++) { QVariant element = dataElements.at(i); QVariantMap elementMap = element.toMap(); QDateTime startTime = QDateTime::fromMSecsSinceEpoch(elementMap.value("start_timestamp").toLongLong()); QDateTime endTime = QDateTime::fromMSecsSinceEpoch(elementMap.value("end_timestamp").toLongLong()); double price = elementMap.value("marketprice").toDouble(); // check interval [-12h < x < + 12h] if (startTime >= currentTime.addSecs(-3600 * 12) && endTime <= currentTime.addSecs(3600 * 12)) { sum += price; count++; prices.append(price); qCDebug(dcAwattar()) << "Adding price" << startTime.toString() << price; if (price > maxPrice) maxPrice = price; if (price < minPrice) minPrice = price; } else { qCDebug(dcAwattar()) << "Not adding price" << startTime.toString() << price; } if (currentTime >= startTime && currentTime <= endTime) { currentPrice = price; sum += price; count++; if (price > maxPrice) maxPrice = price; if (price < minPrice) minPrice = price; thing->setStateValue(m_currentMarketPriceStateTypeIds.value(thing->thingClassId()), currentPrice / 10.0); thing->setStateValue(m_validUntilStateTypeIds.value(thing->thingClassId()), endTime.toLocalTime().toSecsSinceEpoch()); } } // calculate averagePrice and mean deviation averagePrice = sum / count; if (currentPrice <= averagePrice) { deviation = -1 * qRound(100 + (-100 * (currentPrice - minPrice) / (averagePrice - minPrice))); } else { deviation = qRound(-100 * (averagePrice - currentPrice) / (maxPrice - averagePrice)); } thing->setStateValue(m_averagePriceStateTypeIds.value(thing->thingClassId()), averagePrice / 10.0); thing->setStateValue(m_lowestPriceStateTypeIds.value(thing->thingClassId()), minPrice / 10.0); thing->setStateValue(m_highestPriceStateTypeIds.value(thing->thingClassId()), maxPrice / 10.0); thing->setStateValue(m_averageDeviationStateTypeIds.value(thing->thingClassId()), deviation); qCDebug(dcAwattar()) << "AVG:" << averagePrice << "Min:" << minPrice << "Max:" << maxPrice << "Curr:" << currentPrice; std::sort(prices.begin(), prices.end()); int rank = prices.indexOf(currentPrice); if (rank < 0) { rank = 100; } thing->setStateValue("rank", rank); }