225 lines
8.0 KiB
C++
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);
|
|
}
|