// 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 "manualslotconfig.h" // --------------------------------------------------------------------------- // Free helpers for powerAllocations map serialisation // --------------------------------------------------------------------------- QString manualSlotAllocationKey(LoadSource source) { switch (source) { case LoadSource::SmartCharging: return QStringLiteral("ev"); case LoadSource::Battery: return QStringLiteral("battery"); case LoadSource::DHW: return QStringLiteral("dhw"); case LoadSource::HeatPump: return QStringLiteral("heatpump"); case LoadSource::FeedIn: return QStringLiteral("feedin"); default: return QStringLiteral("external"); } } LoadSource manualSlotSourceFromKey(const QString &key) { if (key == QLatin1String("ev")) return LoadSource::SmartCharging; if (key == QLatin1String("battery")) return LoadSource::Battery; if (key == QLatin1String("dhw")) return LoadSource::DHW; if (key == QLatin1String("heatpump")) return LoadSource::HeatPump; if (key == QLatin1String("feedin")) return LoadSource::FeedIn; return LoadSource::External; } // --------------------------------------------------------------------------- // Expiry check // --------------------------------------------------------------------------- bool ManualSlotConfig::isExpired() const { return expiresAt.isValid() && expiresAt <= QDateTime::currentDateTimeUtc(); } // --------------------------------------------------------------------------- // Slot matching // --------------------------------------------------------------------------- // Compute "minutes since start of week" for repeating-slot comparison. // Qt dayOfWeek(): 1=Mon … 7=Sun. We convert to 0-based (Mon=0). static int minsOfWeek(const QDateTime &dt) { QDateTime utc = dt.toUTC(); int dayIndex = utc.date().dayOfWeek() - 1; // 0=Mon … 6=Sun return dayIndex * 24 * 60 + utc.time().hour() * 60 + utc.time().minute(); } bool ManualSlotConfig::matchesSlotIgnoreExpiry(const QDateTime &slotStart) const { if (!start.isValid() || !end.isValid()) return false; if (!repeating) return slotStart >= start && slotStart < end; // Repeating: compare minutes-of-week for day-of-week + time-of-day int slotMins = minsOfWeek(slotStart); int startMins = minsOfWeek(start); int endMins = minsOfWeek(end); if (startMins <= endMins) { // Normal case — stays within the same calendar week segment return slotMins >= startMins && slotMins < endMins; } else { // Wrap-around case — e.g. Sun 22:00 → Mon 06:00 return slotMins >= startMins || slotMins < endMins; } } bool ManualSlotConfig::matchesSlot(const QDateTime &slotStart) const { if (isExpired()) return false; return matchesSlotIgnoreExpiry(slotStart); } // --------------------------------------------------------------------------- // JSON serialisation // --------------------------------------------------------------------------- QVariantMap ManualSlotConfig::toJson() const { QVariantMap map; if (start.isValid()) map.insert("start", start.toUTC().toString(Qt::ISODateWithMs)); if (end.isValid()) map.insert("end", end.toUTC().toString(Qt::ISODateWithMs)); map.insert("label", label); map.insert("repeating", repeating); if (expiresAt.isValid()) map.insert("expiresAt", expiresAt.toUTC().toString(Qt::ISODateWithMs)); QVariantMap allocs; for (auto it = powerAllocations.constBegin(); it != powerAllocations.constEnd(); ++it) allocs.insert(manualSlotAllocationKey(it.key()), it.value()); map.insert("allocations", allocs); return map; } ManualSlotConfig ManualSlotConfig::fromJson(const QVariantMap &map) { ManualSlotConfig cfg; cfg.start = QDateTime::fromString( map.value("start").toString(), Qt::ISODateWithMs).toUTC(); cfg.end = QDateTime::fromString( map.value("end").toString(), Qt::ISODateWithMs).toUTC(); cfg.label = map.value("label").toString(); cfg.repeating = map.value("repeating", false).toBool(); const QString expiresStr = map.value("expiresAt").toString(); if (!expiresStr.isEmpty()) cfg.expiresAt = QDateTime::fromString(expiresStr, Qt::ISODateWithMs).toUTC(); const QVariantMap allocs = map.value("allocations").toMap(); for (auto it = allocs.constBegin(); it != allocs.constEnd(); ++it) { LoadSource src = manualSlotSourceFromKey(it.key()); if (src != LoadSource::External || it.key() == QLatin1String("external")) cfg.powerAllocations.insert(src, it.value().toDouble()); } return cfg; }