// 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 "spotmarketdataprovidermock.h" #include "../../../energyplugin/plugininfo.h" #include #include #include #include SpotMarketDataProviderMock::SpotMarketDataProviderMock(QNetworkAccessManager *networkManager, QObject *parent) : SpotMarketDataProvider{networkManager, parent} { m_info = SpotMarketProviderInfo(SpotMarketDataProviderMock::providerId(), "Mock provider", QLocale::Austria, QUrl("https://nymea.io")); } QUuid SpotMarketDataProviderMock::providerId() const { return QUuid("ad20a785-34a6-4326-aa20-c2f0d202ef64"); } void SpotMarketDataProviderMock::setDataEntries(const ScoreEntries &scoreEntries) { if (m_scoreEntries == scoreEntries) return; m_scoreEntries = scoreEntries; emit scoreEntriesChanged(m_scoreEntries); cacheDataEntries(m_scoreEntries); } QDateTime SpotMarketDataProviderMock::currentDateTime() const { return m_currentDateTime; } void SpotMarketDataProviderMock::setCurrentDataTime(const QDateTime ¤tDateTime) { m_currentDateTime = currentDateTime; // Update every hour if (m_enabled && m_currentDateTime.time().minute() == 0) { refreshData(); } } bool SpotMarketDataProviderMock::prepareResourceData(const QString &resourceName, const QDateTime &startDateTime) { qCDebug(dcNymeaEnergy()) << this << "Loading resource" << resourceName << "and prepare data for start date time" << startDateTime.toUTC().toString("yyyy.MM.dd hh:mm:ss"); QFile file(resourceName); if (!file.open(QIODevice::ReadOnly)) { qCWarning(dcNymeaEnergy()) << this << "failed to read from resource file" << resourceName << file.errorString(); return false; } QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(file.readAll(), &error); if (error.error != QJsonParseError::NoError) { qCWarning(dcNymeaEnergy()) << this << "Failed to read resource data. The resource invalid JSON data:" << error.errorString(); return false; } clearCache(); m_preparedScoreEntries.clear(); // Start with today midnight, all data sets start at 00:00 and go for at least 3 days #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) QDateTime temporaryStart = QDateTime(startDateTime.toUTC().date(), QTime(0,0,0), QTimeZone::UTC); #else QDateTime temporaryStart = QDateTime(startDateTime.toUTC().date(), QTime(0,0,0), Qt::UTC); #endif QDateTime originalDataStart; int index = 0; foreach (const QVariant &dataVariant, jsonDoc.toVariant().toList()) { QVariantMap dataMap = dataVariant.toMap(); QDateTime rawStartDateTime = QDateTime::fromMSecsSinceEpoch(dataMap.value("start_timestamp").toULongLong()).toUTC(); QDateTime rawEndDateTime = QDateTime::fromMSecsSinceEpoch(dataMap.value("end_timestamp").toULongLong()).toUTC(); if (index == 0) originalDataStart = rawStartDateTime; QDateTime finalStartDateTime = temporaryStart.addMSecs(rawStartDateTime.toMSecsSinceEpoch() - originalDataStart.toMSecsSinceEpoch()); QDateTime finalEndDateTime = finalStartDateTime.addMSecs(rawEndDateTime.toMSecsSinceEpoch() - rawStartDateTime.toMSecsSinceEpoch()); ScoreEntry entry; entry.setStartDateTime(finalStartDateTime.toUTC()); entry.setEndDateTime(finalEndDateTime.toUTC()); entry.setValue(dataMap.value("marketprice").toDouble()); entry.setWeighting(0); // Not known yet, will depend on the current available window and will be filled in from the refresh method depending on current date time m_preparedScoreEntries.append(entry); index++; // qCDebug(dcNymeaEnergy()) << "Parsed spot market data:" << entry.startDateTime().toUTC().toString() << entry.startDateTime().toString() << "-->" << entry.value(); } qCDebug(dcNymeaEnergy()) << this << "Prepared successfully resource data. Scores available:" << m_preparedScoreEntries.count() << "from" << m_preparedScoreEntries.first().startDateTime().toUTC().toString("yyyy.MM.dd hh:mm:ss") << m_preparedScoreEntries.count() << "from" << m_preparedScoreEntries.last().endDateTime().toUTC().toString("yyyy.MM.dd hh:mm:ss"); // foreach (const ScoreEntry &score, m_preparedScoreEntries) // qCDebug(dcNymeaEnergy()) << score; return true; } void SpotMarketDataProviderMock::setAvailable(bool available) { m_available = available; emit availableChanged(m_available); } void SpotMarketDataProviderMock::enable() { m_enabled = true; emit enabledChanged(m_enabled); } void SpotMarketDataProviderMock::disable() { m_enabled = false; emit enabledChanged(m_enabled); emit availableChanged(false); } void SpotMarketDataProviderMock::refreshData() { if (m_currentDateTime.isNull()) return; // Use the specified time and provide the data as if they would have been fetched from a server (including the range of the data) qCDebug(dcNymeaEnergy()) << this << "Refresh data for the current date time" << m_currentDateTime.toUTC().toString("yyyy.MM.dd hh:mm:ss"); // Note: we get every day at 12:00 UTC the data for the next day int startIndex = 0; foreach (const ScoreEntry &entry, m_preparedScoreEntries) { if (m_currentDateTime >= entry.startDateTime() && m_currentDateTime < entry.endDateTime()) { // Found current entry, let's add data from here until availabe, depending on the time startIndex = m_preparedScoreEntries.indexOf(entry); break; } } int entriesAvailable = 0; // Before 12:00 know data only until 24:00, after 12:00 we get the date for the entire next day if (m_currentDateTime.time() < QTime(12, 0)) { entriesAvailable = 24 - m_currentDateTime.time().hour(); } else { entriesAvailable = 36 - (m_currentDateTime.time().hour() - 12); } // Make sure we have enougth data if (startIndex + entriesAvailable >= m_preparedScoreEntries.count()) { qCWarning(dcNymeaEnergy()) << this << "Could not refresh data, not enough prepared data available."; return; } ScoreEntries newEntries; for (int i = 0; i < entriesAvailable; i++) newEntries.append(m_preparedScoreEntries.at(startIndex + i)); if (newEntries.count() >= 12) setAvailable(true); setDataEntries(newEntries); }