// 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 "schedulersettings.h" #include #include #include #include #include Q_DECLARE_LOGGING_CATEGORY(dcNymeaEnergy) SchedulerSettings::SchedulerSettings(QObject *parent) : QObject(parent) { load(); } QString SchedulerSettings::settingsFilePath() const { return NymeaSettings::settingsPath() + QStringLiteral("/scheduler.conf"); } // --- Strategy --- QString SchedulerSettings::activeStrategyId() const { return m_activeStrategyId; } void SchedulerSettings::setActiveStrategyId(const QString &id) { if (m_activeStrategyId == id) return; m_activeStrategyId = id; save(); } // --- SchedulerConfig --- SchedulerConfig SchedulerSettings::schedulerConfig() const { return m_config; } void SchedulerSettings::setSchedulerConfig(const SchedulerConfig &config) { m_config = config; save(); } // --- LoadConfig --- SchedulerSettings::LoadConfig SchedulerSettings::loadConfig(const ThingId &thingId) const { return m_loadConfigs.value(thingId.toString()); } void SchedulerSettings::setLoadConfig(const ThingId &thingId, const LoadConfig &config) { m_loadConfigs.insert(thingId.toString(), config); save(); } QList SchedulerSettings::configuredLoadIds() const { QList ids; foreach (const QString &key, m_loadConfigs.keys()) ids.append(ThingId(key)); return ids; } // --- Overrides --- QList SchedulerSettings::overrideSlotStarts() const { QList starts; foreach (const QString &key, m_overrides.keys()) starts.append(QDateTime::fromString(key, Qt::ISODateWithMs).toUTC()); return starts; } SchedulerSettings::OverrideEntry SchedulerSettings::overrideEntry(const QDateTime &slotStart) const { return m_overrides.value(slotStart.toUTC().toString(Qt::ISODateWithMs)); } void SchedulerSettings::setOverride(const QDateTime &slotStart, const OverrideEntry &entry) { m_overrides.insert(slotStart.toUTC().toString(Qt::ISODateWithMs), entry); save(); } void SchedulerSettings::removeOverride(const QDateTime &slotStart) { m_overrides.remove(slotStart.toUTC().toString(Qt::ISODateWithMs)); save(); } void SchedulerSettings::clearExpiredOverrides() { QDateTime now = QDateTime::currentDateTimeUtc(); QStringList toRemove; for (auto it = m_overrides.constBegin(); it != m_overrides.constEnd(); ++it) { if (it.value().expiresAt.isValid() && it.value().expiresAt <= now) toRemove.append(it.key()); } foreach (const QString &key, toRemove) m_overrides.remove(key); if (!toRemove.isEmpty()) save(); } // --- Persistence --- void SchedulerSettings::load() { QSettings s(settingsFilePath(), QSettings::IniFormat); s.beginGroup(QStringLiteral("scheduler")); m_activeStrategyId = s.value("strategyId", QStringLiteral("rule-based")).toString(); m_config.chargePriceThreshold = s.value("chargePriceThreshold", m_config.chargePriceThreshold).toDouble(); m_config.solarSurplusThresholdW = s.value("solarSurplusThresholdW", m_config.solarSurplusThresholdW).toDouble(); m_config.planningHorizonHours = s.value("planningHorizonHours", m_config.planningHorizonHours).toInt(); m_config.recomputeIntervalMin = s.value("recomputeIntervalMin", m_config.recomputeIntervalMin).toInt(); m_config.selfSufficiencyTarget = s.value("selfSufficiencyTarget", m_config.selfSufficiencyTarget).toDouble(); s.endGroup(); // Load per-thing load configs int loadCount = s.beginReadArray(QStringLiteral("loads")); for (int i = 0; i < loadCount; ++i) { s.setArrayIndex(i); QString key = s.value("thingId").toString(); if (key.isEmpty()) continue; LoadConfig lc; lc.priority = s.value("priority", 0.5).toDouble(); lc.targetValue = s.value("targetValue", 0.0).toDouble(); lc.enabled = s.value("enabled", true).toBool(); QString dl = s.value("deadline").toString(); if (!dl.isEmpty()) lc.deadline = QDateTime::fromString(dl, Qt::ISODateWithMs).toUTC(); m_loadConfigs.insert(key, lc); } s.endArray(); // Load overrides int overrideCount = s.beginReadArray(QStringLiteral("overrides")); for (int i = 0; i < overrideCount; ++i) { s.setArrayIndex(i); QString slotKey = s.value("slotStart").toString(); if (slotKey.isEmpty()) continue; OverrideEntry entry; entry.source = loadSourceFromString(s.value("source").toString()); entry.powerW = s.value("powerW", 0.0).toDouble(); entry.reason = s.value("reason").toString(); QString exp = s.value("expiresAt").toString(); if (!exp.isEmpty()) entry.expiresAt = QDateTime::fromString(exp, Qt::ISODateWithMs).toUTC(); m_overrides.insert(slotKey, entry); } s.endArray(); qCDebug(dcNymeaEnergy()) << "SchedulerSettings: loaded from" << settingsFilePath(); } void SchedulerSettings::save() { QSettings s(settingsFilePath(), QSettings::IniFormat); s.beginGroup(QStringLiteral("scheduler")); s.setValue("strategyId", m_activeStrategyId); s.setValue("chargePriceThreshold", m_config.chargePriceThreshold); s.setValue("solarSurplusThresholdW", m_config.solarSurplusThresholdW); s.setValue("planningHorizonHours", m_config.planningHorizonHours); s.setValue("recomputeIntervalMin", m_config.recomputeIntervalMin); s.setValue("selfSufficiencyTarget", m_config.selfSufficiencyTarget); s.endGroup(); s.beginWriteArray(QStringLiteral("loads"), m_loadConfigs.size()); int idx = 0; for (auto it = m_loadConfigs.constBegin(); it != m_loadConfigs.constEnd(); ++it, ++idx) { s.setArrayIndex(idx); s.setValue("thingId", it.key()); s.setValue("priority", it.value().priority); s.setValue("targetValue", it.value().targetValue); s.setValue("enabled", it.value().enabled); if (it.value().deadline.isValid()) s.setValue("deadline", it.value().deadline.toUTC().toString(Qt::ISODateWithMs)); } s.endArray(); s.beginWriteArray(QStringLiteral("overrides"), m_overrides.size()); idx = 0; for (auto it = m_overrides.constBegin(); it != m_overrides.constEnd(); ++it, ++idx) { s.setArrayIndex(idx); s.setValue("slotStart", it.key()); s.setValue("source", loadSourceToString(it.value().source)); s.setValue("powerW", it.value().powerW); s.setValue("reason", it.value().reason); if (it.value().expiresAt.isValid()) s.setValue("expiresAt", it.value().expiresAt.toUTC().toString(Qt::ISODateWithMs)); } s.endArray(); }