154 lines
5.7 KiB
C++
154 lines
5.7 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 "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;
|
|
}
|