powersync-energy-plugin-etm/energyplugin/spotmarket/spotmarketdataproviderawatt...

225 lines
8.0 KiB
C++

// 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 <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "spotmarketdataproviderawattar.h"
#include "../plugininfo.h"
#include <QUrlQuery>
#include <QNetworkReply>
#include <QJsonDocument>
#include <QJsonParseError>
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);
}