powersync-energy-plugin-etm/energyplugin/types/manualslotconfig.cpp

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;
}