// 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-energy-plugin-nymea. * * nymea-energy-plugin-nymea.s 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-energy-plugin-nymea.s 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-energy-plugin-nymea. If not, see . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "spotmarketdataproviderawattar.h" #include "../plugininfo.h" #include #include #include #include SpotMarketDataProviderAwattar::SpotMarketDataProviderAwattar(QNetworkAccessManager *networkManager, AwattarCountry country, QObject *parent) : SpotMarketDataProvider{networkManager, parent} { QString name; QUrl website; switch (country) { case AwattarCountryAustria: name = "aWATTar AT"; m_country = QLocale::Austria; website = QUrl("https://www.awattar.at"); break; case AwattarCountryGermany: name = "aWATTar DE"; m_country = QLocale::Germany; website = QUrl("https://www.awattar.de"); break; } // Insert the provider based on the country m_info = SpotMarketProviderInfo(SpotMarketDataProviderAwattar::providerId(), name, m_country, website); m_refreshTimer.setInterval(60000); m_refreshTimer.setSingleShot(false); connect(&m_refreshTimer, &QTimer::timeout, this, &SpotMarketDataProviderAwattar::onRefreshTimout); evaluateAvailable(); } QUuid SpotMarketDataProviderAwattar::providerId() const { switch (m_country) { case QLocale::Austria: return QUuid("5196b3cc-b2ee-46d6-b63a-7af2cf70ba67"); break; case QLocale::Germany: return QUuid("0ca6ad88-e243-438d-a0f8-986cecf61834"); break; default: break; } return QUuid(); } QLocale::Country SpotMarketDataProviderAwattar::country() const { return m_country; } void SpotMarketDataProviderAwattar::enable() { m_refreshTimer.start(); if (!m_enabled) { m_enabled = true; emit enabledChanged(m_enabled); } ScoreEntries cachedEntries = loadCachedDataEntries(); // CLean up already passed cached scores foreach (const ScoreEntry &score, cachedEntries) { if (score.endDateTime() < QDateTime::currentDateTime()) { cachedEntries.removeAll(score); } } m_scoreEntries = cachedEntries; qCDebug(dcNymeaEnergy()) << this << "having" << m_scoreEntries.count() << "scores available from cache."; emit scoreEntriesChanged(m_scoreEntries); onRefreshTimout(); } void SpotMarketDataProviderAwattar::disable() { m_refreshTimer.stop(); if (m_enabled) { m_enabled = false; emit enabledChanged(m_enabled); } evaluateAvailable(); } void SpotMarketDataProviderAwattar::refreshData() { QUrl requestUrl; switch (m_country) { case QLocale::Austria: requestUrl = QUrl("https://api.awattar.at/v1/marketdata"); break; case QLocale::Germany: requestUrl = QUrl("https://api.awattar.de/v1/marketdata"); break; default: break; } // QDateTime startTime = QDateTime::currentDateTime().addDays(-1); // QDateTime endTime = QDateTime::currentDateTime().addDays(1); // QUrlQuery query; // query.addQueryItem("start", QString::number(startTime.toMSecsSinceEpoch())); // query.addQueryItem("end", QString::number(endTime.toMSecsSinceEpoch())); // requestUrl.setQuery(query); // qCDebug(dcNymeaEnergy()) << m_info << "refresh data from" << startTime.toString("dd.MM.yyyy hh:mm") << "until" << endTime.toString("dd.MM.yyyy hh:mm"); qCDebug(dcNymeaEnergy()) << this << "refresh data"; QNetworkReply *reply = m_networkManager->get(QNetworkRequest(requestUrl)); connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); connect(reply, &QNetworkReply::finished, this, [this, reply](){ if (reply->error() != QNetworkReply::NoError) { qCWarning(dcNymeaEnergy()) << this << "Failed to refresh data. Reply finished with error:" << reply->errorString(); evaluateAvailable(); return; } int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (status != 200) { qCWarning(dcNymeaEnergy()) << this << "Failed to refresh data. HTTP returned status:" << status; evaluateAvailable(); return; } QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll(), &error); if (error.error != QJsonParseError::NoError) { qCWarning(dcNymeaEnergy()) << this << "Failed to refresh data. The payload contains invalid JSON data:" << error.errorString(); evaluateAvailable(); return; } m_lastRefresh = QDateTime::currentDateTime(); // Parse data QVariantList dataList = jsonDoc.toVariant().toMap().value("data").toList(); ScoreEntries scoreEntries; foreach (const QVariant &dataVariant, dataList) { QVariantMap dataMap = dataVariant.toMap(); double currentMarketPrice = dataMap.value("marketprice").toDouble(); ScoreEntry entry; entry.setStartDateTime(QDateTime::fromMSecsSinceEpoch(dataMap.value("start_timestamp").toULongLong())); entry.setEndDateTime(QDateTime::fromMSecsSinceEpoch(dataMap.value("end_timestamp").toULongLong())); entry.setValue(currentMarketPrice); entry.setWeighting(0); // We let the manager weight the scores depending on the selected time window scoreEntries.append(entry); // qCDebug(dcNymeaEnergy()) << "Parsed spot market data:" << entry.startDateTime().toUTC().toString() << entry.startDateTime().toString() << "-->" << entry.value(); } m_scoreEntries = scoreEntries; cacheDataEntries(m_scoreEntries); evaluateAvailable(); emit scoreEntriesChanged(m_scoreEntries); }); } void SpotMarketDataProviderAwattar::onRefreshTimout() { // If the last refresh is longer ago than the an hour, refresh... if (m_lastRefresh < QDateTime::currentDateTime().addSecs(-3600)) { refreshData(); }/* else { qCDebug(dcNymeaEnergy()) << this << "no need to refresh. Last refresh was" << m_lastRefresh.toString("dd.MM.yyyy hh:mm"); }*/ evaluateAvailable(); } void SpotMarketDataProviderAwattar::evaluateAvailable() { bool available = false; int availableFutureSchedules = m_scoreEntries.availableFutureSchedules(QDateTime::currentDateTime()); // Note: in the worst case we have only 10 or 11 schedules into the future. // The data will be refreshed every day at 14:00 for the next 24 hours. // If we refresh the data the first time after 13:00 only 10 or 11 hours in the // future are available, depending on current daylight saving. if (m_enabled && availableFutureSchedules >= 10) available = true; if (m_available == available) return; if (available) { qCDebug(dcNymeaEnergy()) << this << "is now available and has" << availableFutureSchedules << "schedules into the future until" << m_scoreEntries.last().endDateTime().toString("dd.MM.yyyy hh:mm"); } else { qCDebug(dcNymeaEnergy()) << this << "is not available any more."; } m_available = available; emit availableChanged(m_available); }